ByteBuf의의 Netty 고성능 분석 소스

원자로의 성능은 인 Netty 추가 NIO 스레드 모델은 앞서 언급 한 고성능의 제로 카피 중요한 이유를 추론.

제로 카피

  • 커널에 사용자 프로세스에서 데이터를 복사 할 필요가 없습니다 (JVM 힙 운영 체제 내에서 데이터를 직접 OS를 사용할 수 있도록하는 데 사용되지 않습니다, 당신은 힙 외부 힙의 데이터를 복사해야합니다)
  • CompositeByteBuf 복잡한 다중 ByteBuf는 그물코 사용은 메모리에 기입하고, 새로운 데이터를 재 적용하는 대신, 외부 액세스를 제공하기 위해 논리, 통합 인터페이스와 연관된 ByteBuf

ByteBuf의의 Netty 유형

  • 풀링 (풀링), 풀링되지 않은 (비 풀링)

  • 다이렉트 (직접 버퍼 / 외부 스택), 힙 JVM (내부)

  • 안전하지 않은 (현지 안전하지 않은 메서드 호출), 안전 (일반적으로는이 JVM 힙을 참조하여, 안전하지 않은 작업과 관련 있음을 언급하지 않았다)

의 Netty의 기본 우선 순위는의 안전 사용을 달성하는 것입니다


풀링 / (풀링 / 풀링되지 않은) 비는 풀링 :

의 Netty 처음 ByteBuf 풀 등의 연속 공간을 적용, 그들은 내부의 풀에 직접 수행하는 데 필요한, 그들은 적용 할 때마다 ByteBuf 사용할 필요없이, 최대 사용 된 풀 ByteBuf으로 돌아왔다. 바깥보다 생성 된 힙 객체를 스택 내에서 소모.

요약 : 풀링의 효과는 목표에 도달 프로그램의 작동 속도를하는 것입니다


힙 꺼짐 /를 (직접 / 힙) 스택 :

JVM을, 응용 프로그램의 힙의 데이터를 의미, 작업은 JVM에 있습니다.

번 복사 할 필요가없는 경우, 기본 신청 방법의 응용 프로그램 메모리 및이 메모리는 OS 메모리 스택에 사용되는 직접 사용할 OS 될 때 JVM 외부 직접 버퍼 스택은 비 - 메모리 힙 지칭 직접 버퍼. 어플리케이션 힙 메모리 밖에, Java 오브젝트 (DirectByteBuf) 단지 약간의 리더 / 라이터 지수 (메모리 (메모리 어드레스), 오프셋 (오프셋) 등)이이 시점에서 수행되는, 기록 데이터 / 데이터를 읽어 이는 스택 외부 기본 데이터의 동작에 의해 수행된다.

요약 : 복사 객체와 외부 메모리 힙 효율을 개선, 방지하는 것입니다


위험한

이 것은 안전하지 않은 sun.misc 클래스를 제공, 클래스가 메모리의 기본 운영 방법으로 직접 사용할 수 있습니다, 물론, 또한 효율성을 향상, 위에서 언급 한 힙 메모리의 외부 응용 프로그램 및 운영 안전하지 않은 완료라는 것을 사용하는 것입니다 말했다 그러나이 안전하지 않은으로 실수를, 그렇지 않으면 매우 쉽게 메모리 동작에 대해 잘 알고, 그래서 왜 전화 안전 관리가 정당화해야합니다.

요약 : 메모리에 직접 액세스 향상 효율성, 오류가 발생하기 쉬운의 사용




풀에 사용되는 일부 범주와 개념

수영장 아레나, PoolChunk, PoolThreadLocalCache, PoolSubpage, 리사이클

  • PoolArena는 : 아레나 무대는 풀 작업에 대한 필요성은 이름에서 알 수 있듯이이 클래스는 환경을 제공, 의미
  • PoolChunk :의 Netty 요청 메모리 블록 스토리지 여기서, ChunkSize 그 남은 공간의 크기가 공식 설명에 따라, 정보를 FREESIZE 오프셋, 적어도 요구를 충족하기 위해 크기의 블록 (덩어리)을 발견하기 위하여는, 완전한 바이너리 트리는 스택 같이 구성되는 (이 노드가 완전한 이진 트리를 형성 할 가장 큰 힙, 덩어리 중 하나입니다)
  • PoolThreadLocalCache는 로컬 변수 스토리지 PoolArena 스레드 -> 청크 (-> 페이지 -> 서브)
  • PoolSubPage : 페이지는 청크의 하단에있는
  • 재활용 : 휴지통 이름이 암시,이 추상 클래스, 휴지통에서 얻을의 ThreadLocal의 주요 역할 ByteBuf

간략한 설명 : PoolThreadLocalCache 및 재활용의 ThreadLocal 변수는 멀티 스레드를위한 경쟁을 줄이고 운영 효율성을 개선하는 데 사용됩니다.


몇 가지 중요한 속성 값.

11 maxOrder 기본값 : 깊이 완전 이진 트리 (루트 수준은 0, 그래서 maxOrder 총 객관적 + 1 층)

pageSize가 기본 8192 (8K) : 리프 노드 페이지 하단 위의 완전한 이진 트리의 기본 크기

기본 pageShifts 13 :이 pageSize가 2의 수이다 ^ pageShifts = pageSize가, 8192 pageSize가 기본적이기 때문에, (13)의 기본값

여기서, ChunkSize 기본 16m (pageSize가 maxOrder *)는이 각 청크의 크기이고, 청크의 크기는도 각 층 이하이다.

16 바이트의 페이지 내부의 최소 분할 단위는 16이 그림은 컴퓨팅을 사용하는 몇 가지 주요 장소에 따라하는 것이 매우 중요합니다


크기 유형 ByteBuf :

  • 크기 <512, 작은
  • 512 <크기 <8192 작은
  • 8192 <크기 <16m 정상
  • 16m <크기, 큰

덩어리 구조

각 층은 16m의 합, 바닥이 모든 물론, 여기에 그려, 2K 노드를 가질 수 있도록, 8192 (8K)에 각 페이지 하단으로 분류 된 서브 페이지 페이지에서 운영됩니다.




힙 외측 / 내측 힙 메모리 할당의 ByteBuffer

간단한 테스트

간단한 테스트를 수행하여 응용 프로그램 내에서 외부 힙 메모리와 시간이 소요되는 응용 프로그램 힙 메모리를 테스트 :
	static void nioAllocTest(){
        int num = 10;
        int cnt = 100;
        int size = 256;
        ByteBuffer buf;

        long start1,end1,start2,end2;
        long sum1,sum2;
        for(int i = 0;i<num;i++){
            sum1=sum2=0;
            int j;
            for(j = 0;j<cnt;j++) {
                start1 = System.nanoTime();
                buf = ByteBuffer.allocateDirect(size);
                end1 = System.nanoTime();
                sum1+=(end1-start1);
//                System.out.println("direct 申请时间: "+(end1-start1));

                start2 = System.nanoTime();
                buf = ByteBuffer.allocate(size);
                end2 = System.nanoTime();
//                System.out.println("heap 申请时间: "+(end2-start2));
//                System.out.println("-----");
                sum2+=(end2-start2);
            }
            System.out.println(String.format("第 %s 轮申请 %s 次 %s 字节平均耗时 [direct: %s , heap: %s].",i,j,size,sum1/cnt, sum2/cnt));
        }
    }
复制代码

출력은 다음과 같습니다

제 애플리케이션 0 100 256 바이트의 평균 시간 [다이렉트 : 4864, 힙 : 1616 ].
라운드 1 100 256 바이트 애플리케이션 평균 시간 [다이렉트 : 5763, 힙 : 1641 ].
처음 두 애플리케이션 (100) [: 4771, 힙 : 직접 1,672 256 바이트의 평균 지속 기간 .]
애플리케이션 (100)의 제 3 바이트 256의 평균 소요 [다이렉트 : 4961를 힙 : 883 ].
응용 프로그램의 처음 4 바이트를 100 배 (256)의 평균했다 [다이렉트 : 3556, 힙 : 870 ].
[: 5159, 힙 : 726 직접 애플리케이션 (100)의 처음 다섯 바이트 (256), 평균 소요 ].
라운드 애플리케이션 6 100 256 바이트 시간 평균 시간 [다이렉트 : 3739, 힙 : 843]
3910 힙 : 7 라운드 직접 100 256 바이트 [평균 소요 221 .]
애플리케이션 (100)의 처음 8 바이트 [다이렉트 256의 평균 소요 : 2191, 힙 : 590 .]
(9) 256 바이트 (100)의 평균 시간 내내 [다이렉트 : 1624 힙 : 615 ].

소모 힙 메모리의 직접적인 응용 프로그램 외부에서 볼 수있는 훨씬 더 많은 시간이 소요되는 응용 프로그램의 JVM 힙, 시간이 많이 걸리는 여기에 여러 번 (테스트의 수, 관심있는 학생들은 / 작은 더 정확한 테스트 할 수 있습니다되지 않을 수 있습니다 많이하지 않습니다 크기는) 일부 "재미있는"일을 찾을 수 있습니다.


池化 / 非池化

간단한 테스트

풀링의 효과를 테스트하기 위해 간단한 테스트를 수행합니다
	static void nettyPooledTest(){
        try {
            int num = 10;
            int cnt = 100;
            int size = 8192;
            ByteBuf direct1, direct2, heap1, heap2;

            long start1, end1, start2, end2, start3, end3, start4, end4;
            long sum1, sum2, sum3, sum4;
            for (int i = 0; i<num; i++) {
                sum1 = sum2 = sum3 = sum4 = 0;
                int j;
                for (j = 0; j<cnt; j++) {

                    start1 = System.nanoTime();
                    direct1 = PooledByteBufAllocator.DEFAULT.directBuffer(size);
                    end1 = System.nanoTime();
                    sum1 += (end1-start1);

                    start2 = System.nanoTime();
                    direct2 = UnpooledByteBufAllocator.DEFAULT.directBuffer(size);
                    end2 = System.nanoTime();
                    sum2 += (end2-start2);

                    start3 = System.nanoTime();
                    heap1 = PooledByteBufAllocator.DEFAULT.heapBuffer(size);
                    end3 = System.nanoTime();
                    sum3 += (end3-start3);

                    start4 = System.nanoTime();
                    heap2 = UnpooledByteBufAllocator.DEFAULT.heapBuffer(size);
                    end4 = System.nanoTime();
                    sum4 += (end4-start4);

                    direct1.release();
                    direct2.release();
                    heap1.release();
                    heap2.release();
                }
                System.out.println(String.format("Netty 第 %s 轮申请 %s 次 [%s] 字节平均耗时 [direct.pooled: [%s] , direct.unpooled: [%s] , heap.pooled: [%s] , heap.unpooled: [%s]].", i, j, size, sum1/cnt, sum2/cnt, sum3/cnt, sum4/cnt));
            }
        }catch(Exception e){
            e.printStackTrace();
        }finally {

        }
    }
复制代码

결과의 최종 출력 :

그물코 라운드 0 100 [8192] 평균 시간 그리고 바이트 [direct.pooled [1784931]을 direct.unpooled [105 (310)] heap.pooled [202 (306)] heap.unpooled [23,317].
그물코 1 라운드 100 신청서 [8192] 평균 시간 바이트 [direct.pooled :] 15457 [heap.pooled : [12,849을 direct.unpooled [12,671] heap.unpooled : 12693.].
의 인 Netty 어플리케이션 2 100 [8192] 평균 시간 바이트 [direct.pooled [13,589]를 direct.unpooled [14,459] heap.pooled [18,783] heap.unpooled [13,803].
의 Netty 3 [8192] 평균 시간 바이트 라운드 100 direct.pooled :] 11644 [heap.pooled : [10185]를 direct.unpooled [9809] heap.unpooled [12,770].
의 Netty 4 라운드 애플리케이션 (100) [8192] 평균 시간 바이트 [direct.pooled을 [15,980]를 direct.unpooled [53,980] heap.pooled : [5641], heap.unpooled [12,467].
그물코 라운드 5 100 [8192] 평균 시간 그리고 바이트 [direct.pooled :] 34215 [heap.pooled : [4903]을 direct.unpooled [6659] heap.unpooled [12,311].
그물코 라운드 6 회 적용 100 [8192] 평균 시간 바이트 [direct.pooled :] 7197 [heap.pooled : [2,445을 direct.unpooled [2,849] heap.unpooled [11010].
의 인 Netty 응용 프로그램 (7) 100 [8192] 평균 시간 바이트 [direct.pooled :] 4750 [heap.pooled :]의 2578을 direct.unpooled [3904] heap.unpooled [255 689].
의 Netty 8 [8192] 평균 시간 바이트 라운드 100 direct.pooled : [1855], direct.unpooled : [3492], heap.pooled [37,822] heap.unpooled [3983].
의 Netty 9 라운드 애플리케이션 (100) [8192] 평균 시간을 바이트 [direct.pooled : [1932], direct.unpooled : [2961], heap.pooled : [1825], heap.unpooled [6098].

여기에 주로 풀링이 시간이 해명하기 시작했다 서버의 역할의 성능이 저하됩니다 DirectByteBuffer, 그 밖에 자주 응용 프로그램 힙 메모리를 볼 수 있습니다. 풀링 단지에만 풀에서 객체의 충분한 메모리, 이후의 획득을 적용하기 시작 그는 항상 외부 공간 응용 프로그램에서 연속 사용 시간이 많이 소요되는 힙에 대한 필요성을 제거, 별도의 신청, 수영장 반환 실행을 제거 하였다.


특정 구현 ByteBuf

여기에 개인적으로 말하는 가장 중요한 느낌, 그것은 기본 유형 사용 그물코입니다 : PooledUnsafeDirectByteBuf우리는 또한 응용 프로그램 PooledByteBufAllocator.DEFAULT.directBuffer ()로 시작에서.

)합니다 (PooledByteBufAllocator.DEFAULT.directBuffer에 아래에서

  // 到第一个要分析的方法
  protected ByteBuf newDirectBuffer(int initialCapacity, int maxCapacity) {
      // 从 threadlLocal 获取一个线程本地缓存池
      PoolThreadCache cache = (PoolThreadCache)this.threadCache.get();
      // 这个缓存池包含 heap 和 direct 两种, 获取直接缓存池
      PoolArena<ByteBuffer> directArena = cache.directArena;
      Object buf;
      if (directArena != null) {
        buf = directArena.allocate(cache, initialCapacity, maxCapacity); // 这里往下 -- 1
      } else {
        // 如果没有堆外缓存池, 直接申请堆外的 ByteBuf, 优先使用 unsafe
        buf = PlatformDependent.hasUnsafe() ? UnsafeByteBufUtil.newUnsafeDirectByteBuf(this, initialCapacity, maxCapacity) : new UnpooledDirectByteBuf(this, initialCapacity, maxCapacity);
      }

      return toLeakAwareBuffer((ByteBuf)buf);
    }

  // 1  directArena.allocate(cache, initialCapacity, maxCapacity);
  PooledByteBuf<T> allocate(PoolThreadCache cache, int reqCapacity, int maxCapacity) {
      // newByteBuf(maxCapacity); 有两种实现, directArena 和 heapArena
      // Pool 的为在 recycle 中重用一个 ByteBuf
      PooledByteBuf<T> buf = newByteBuf(maxCapacity); // -- 2
      allocate(cache, buf, reqCapacity); // -- 7
      return buf;
    }
	
  // 2 newByteBuf(maxCapacity)
  protected PooledByteBuf<ByteBuffer> newByteBuf(int maxCapacity) {
      // 优先使用 PooledUnsafeDirect
      if (HAS_UNSAFE) {
        // PooledUnsafeDirect
        return PooledUnsafeDirectByteBuf.newInstance(maxCapacity); // -- 3
      } else {
        // PooledDirect
        return PooledDirectByteBuf.newInstance(maxCapacity);
      }
    }

  // 3 PooledUnsafeDirectByteBuf.newInstance
  static PooledUnsafeDirectByteBuf newInstance(int maxCapacity) {
      // 从用于回收的 ThreadLocal 中获取一个 ByteBuf
      PooledUnsafeDirectByteBuf buf = RECYCLER.get();	// -- 4
      // 重置 ByteBuf 的下标等
      buf.reuse(maxCapacity);	// -- 6
      return buf;
    }

  // 4 Recycler.get()
  public final T get() {
      if (maxCapacityPerThread == 0) {
        return newObject((Handle<T>) NOOP_HANDLE);
      }
      // 每个线程都有一个栈
      Stack<T> stack = threadLocal.get();
      // 弹出一个 handle
      DefaultHandle<T> handle = stack.pop();
      // 如果 stack 中没有 handle 则新建一个 
      if (handle == null) {
        handle = stack.newHandle();
        // newObject 由调用者实现, 不同的 ByteBuf 创建各自不同的 ByteBuf, 需要由创建者实现
        // handle.value is ByteBuf, 从上面跟下来, 所以这里是 PooledUnsafeDirectByteBuf
        handle.value = newObject(handle); // -- 5
      }
      // 返回一个 ByteBuf
      return (T) handle.value;
    }
		
  // 5 Stack.pop() , 从栈中取出一个 handle
  DefaultHandle<T> pop() {
      int size = this.size;
      if (size == 0) {
        if (!scavenge()) {
          return null;
        }
        size = this.size;
      }
      size --;
      // 取出栈最上面的 handle
      DefaultHandle ret = elements[size];
      elements[size] = null;
      if (ret.lastRecycledId != ret.recycleId) {
        throw new IllegalStateException("recycled multiple times");
      }
      // 重置这个 handle 的信息
      ret.recycleId = 0;
      ret.lastRecycledId = 0;
      this.size = size;
      return ret;
    }

  // 6 重用 ByteBuf 之前需要重置一下之前的下标等
  final void reuse(int maxCapacity) {
      maxCapacity(maxCapacity);
      setRefCnt(1);
      setIndex0(0, 0);
      discardMarks();
    }	
复制代码

1-6 상기 단계 PoolThreadLocalCache 아레나로부터 취득한 외부 스택 및 원하는 RECYCLE의 크기에 따라 스택에서 획득 한 스레드 로컬 ByteBuf는 스택에서 ByteBuf 팝업 첨자 ByteBuf 기록 리셋 좋아.


추적 코드가 두 번째 단계가 끝난 경우에도, 다음 단계 일곱째를 시작하고,이 말하기.

PooledByteBuf<T> allocate(PoolThreadCache cache, int reqCapacity, int maxCapacity) {
      // newByteBuf(maxCapacity); 有两种实现, directArena 和 heapArena
      // Pool 的为在 recycle 中重用一个 ByteBuf
      PooledByteBuf<T> buf = newByteBuf(maxCapacity); // -- 2
      allocate(cache, buf, reqCapacity); // -- 7
      return buf;
    }
复制代码

ByteBuf에 위에서 언급 한 재활용 스레드 로컬 스택에서 획득하고, 읽기 및 쓰기 등. 고려 다음은 핵심하는 인덱스를 재설정합니다. 우리는 갈 코드를 따라 계속

	// allocate(cache, buf, reqCapacity); -- 7
	// 这一段都很重要,代码复制比较多, normal(>8192) 和 huge(>16m) 的暂时不做分析
	private void allocate(PoolThreadCache cache, PooledByteBuf<T> buf, final int reqCapacity) 	{
    		// 计算应该申请的大小
        final int normCapacity = normalizeCapacity(reqCapacity); // -- 8

        // 申请的大小是否小于一页 (默认8192) 的大小
        if (isTinyOrSmall(normCapacity)) { // capacity < pageSize
            int tableIdx;
            PoolSubpage<T>[] table;
            // reqCapacity < 512 
            boolean tiny = isTiny(normCapacity);
            if (tiny) { // < 512 is tiny
                // 申请 tiny 容量的空间
                if (cache.allocateTiny(this, buf, reqCapacity, normCapacity)) {
                    return;
                }
                // 计算属于哪个子页, tiny 以 16B 为单位
                tableIdx = tinyIdx(normCapacity);
                table = tinySubpagePools;
            } else {
                //8192 >  reqCapacity >= 512 is small
                // small 以 1024为单位
                if (cache.allocateSmall(this, buf, reqCapacity, normCapacity)) {
                    return;
                }
                tableIdx = smallIdx(normCapacity);
                table = smallSubpagePools;
            }

            // head 指向自己在 table 中的位置的头
            final PoolSubpage<T> head = table[tableIdx];

            /**
             * Synchronize on the head. This is needed as {@link PoolChunk#allocateSubpage(int)} and
             * {@link PoolChunk#free(long)} may modify the doubly linked list as well.
             */
            synchronized (head) {
                final PoolSubpage<T> s = head.next;
                // 这里判断是否已经添加过 subPage
                // 添加过的话, 直接在该 subPage 上面进行操作, 记录标识位等
                if (s != head) {
                    assert s.doNotDestroy && s.elemSize == normCapacity;
                    // 在 subPage 的 bitmap 中的下标
                    long handle = s.allocate();
                    assert handle >= 0;
                    // 用 已经初始化过的 bytebuf 初始化 subPage 中的信息
                    s.chunk.initBufWithSubpage(buf, handle, reqCapacity);
                    // 计数
                    incTinySmallAllocation(tiny);
                    return;
                }
            }
          
            // 第一次创建该类型大小的 ByteBuf, 需要创建一个subPage
            synchronized (this) {
                allocateNormal(buf, reqCapacity, normCapacity);
            }

            // 增加计数
            incTinySmallAllocation(tiny);
            return;
        }
  }

复制代码

당신의 크기의 계산에 적용해야 ByteBuf


    // 8 以下代码是在 normalizeCapacity(reqCapacity) 中
    // 如果 reqCapacity >= 512 ,则使用 跟hashMap 相同的扩容算法
    // reqCapacity < 512(tiny类型) 则将 reqCapacity 变成 16 的倍数	
	if (!isTiny(reqCapacity)) { 
    // 是不是很熟悉, 有没有印象 HashMap 的扩容, 找一个不小于原数的2的指数次幂大小的数
    int normalizedCapacity = reqCapacity;
    normalizedCapacity --;
    normalizedCapacity |= normalizedCapacity >>>  1;
    normalizedCapacity |= normalizedCapacity >>>  2;
    normalizedCapacity |= normalizedCapacity >>>  4;
    normalizedCapacity |= normalizedCapacity >>>  8;
    normalizedCapacity |= normalizedCapacity >>> 16;
    normalizedCapacity ++;

    //
    if (normalizedCapacity < 0) {
      normalizedCapacity >>>= 1;
    }
    assert directMemoryCacheAlignment == 0 || (normalizedCapacity & directMemoryCacheAlignmentMask) == 0;

    return normalizedCapacity;
  }

	// reqCapacity < 512
	// 已经是16的倍数,不做操作
	if ((reqCapacity & 15) == 0) {
    	return reqCapacity;
  	}
	// 不是16的倍数,转化为16的倍数
	return (reqCapacity & ~15) + 16; 
复制代码

때문에 small 和 tiny더 유사, 그래서 우리가 선택한이있다 tiny용어

// 申请 tiny 容量的空间
if (cache.allocateTiny(this, buf, reqCapacity, normCapacity)) {
  return;
}
// 计算属于哪个子页, tiny 以 16b 为单位
tableIdx = tinyIdx(normCapacity);
table = tinySubpagePools;

// head 指向自己在 table 中的位置的头
final PoolSubpage<T> head = table[tableIdx];
复制代码

당신이 볼 수있는, tinySubPage가있는 생성자에서 초기화 추적, 이름을보고는, tinySubPage 장소를 저장해야합니다 여기 tinySubpagePools를 참조하십시오

tinySubpagePools = newSubpagePoolArray(numTinySubpagePools);
// 初始化 32 种类型的 subPage 的 head , 这里是记录 head
for (int i = 0; i < tinySubpagePools.length; i ++) {
  tinySubpagePools[i] = newSubpagePoolHead(pageSize);
}
// 512 / 16 = 32
static final int numTinySubpagePools = 512 >>> 4;
复制代码

numTinySubpagePools, 이것은 512 작고 소형의 경계 지점이고, 512 >>> 4 = 32 정적 변수 이유 부호 오른쪽 시프트 4이고, 상기 기본 유닛의 서브 페이지는 기본적인 서브 페이지의 분포를 할당 상기 기억 장치는 16 바이트이다 > [16,32,48 .... 512] - tinySubpagePools 그래서 여기 형의 갯수가 ByteBuf 크기의 16 내지 512 유닛 (16)으로부터 산출되는 상기 tinyIdx 상기 (INT의 normCapacity) 계산 이는 ByteBuf 종류는 아래 첨자에 따라 헤드 아니다 속하는 후속 첨자 헤드 대응 풀 생성자 헤드 실제 애플리케이션의 모든 초기화 얻을 수 첨자 tinySubpagePools ByteBuf 유형을 취득 적용 할 수 있지만, 그때 새로운 서브 페이지 또한,이 머리와 이중 연결리스트. 코드의 상기 순서에 따라, 다음에 poolSubPage 걸음 (INIT 또는 할당)한다


액션 서브 페이지의 일부 필드

  final PoolChunk<T> chunk;
  // 当前 subPage 所处的 Page 节点下标
  private final int memoryMapIdx;
  // 当前子页的 head 在 该 chunk 中的偏移值, 单位为 pageSize(default 8192)
  private final int runOffset;
  // default 8192
  private final int pageSize;
  // 默认 8 个 long 的字节长度, long是64位, 8*64 = 512, 512 * 16(subPage最低按照16字节分配) = 8192(one default page)
  // 意思是将 一个page分为 512 个 16byte, 每一个 16byte 用一位(bit)来标记是否使用, 一个long有64bit, 所以一共需要 512 / 64 = 8个long类型来作为标记位
  private final long[] bitmap;
  // 这个是指一个 Page 中最多可以存储多少个 elemSize 大小 ByteBuf
  // maxNumElems = pageSize / elemSize
  private int maxNumElems;
  // 已经容纳多少个 elemSize 大小的 ByteBuf
  private int numAvail;
  // 这个是记录真正能使用到的 bit 的length, 因为你不可能每个 page 中的 elemSize 都是16,肯定是有其他大小的, 在 PoolSubPage 的 init 方法中可以看到: bitmapLength = maxNumElems >>> 6; 
  private int bitmapLength;
  // 所以初始化方法 init(), 只初始化 bitmapLength 个 long 类型
	/**
	* for (int i = 0; i < bitmapLength; i ++) {
  *             bitmap[i] = 0;
  * }
  */          

复制代码

그 결론 사이즈 페이지 8192, 배열 크기의 바이트의 처음 숫자는 크기 계산 전달 (스택 어레이 이외 바이트) maxNumElems을 수용하고 저장할 수있는 최대의 최대 수를 계산한다 비트 맵 볼 마크 페이지가 이미 위치를 사용하는 방법을 오랜 마커 비트 bitmapLength로 사용되는 디지털의 유형, 마지막 초기화 비트 맵 (단위를 16 바이트합니다).

PoolSubPage 또한 매우 중요한 방법이 : toHandle ()이 메소드는 노드 memoryMapIdx있어서 첨자 memoryMapIdx bitmapIdx 함께 넣고, 이것에 의해 기록되어있는 하나의 긴 핸들 값을 얻을 수있는 대응 노드 (행하도록한다. (오프셋 위치에 대응) 및 하부 노드 (페이지) 즉 bitmapIdx * 16)

  private long toHandle(int bitmapIdx) {
        // 后续会用 (int)handle 将这个 handle 值变回为 memoryMapIdx , 即所属节点下标
        return 0x4000000000000000L | (long) bitmapIdx << 32 | memoryMapIdx;
  }
复制代码

도입 서브 페이지의 의미 필드 전체에 걸쳐 후, 위의 코드에 따라 계속 :

이 코드 조각없이! = 헤드가 동일한 크기의 서브 페이지, initBufWithSubpage 일본어 서브 페이지에 직접적인 조작이 있는지의 여부를 결정하기 위해 적용되는 S, 할 수있는 애플리케이션에 대응하는 헤드의 크기에 따라 처리 대상 노드 후 취득 새로운 하위 페이지를 할당, allocateNormal (BUF, reqCapacity, normCapacity를) 다시 전화

  synchronized (head) {
    final PoolSubpage<T> s = head.next;
    // 这里判断是否已经添加过 subPage
    // 添加过的话, 直接在该 subPage 上面进行操作, 记录标识位等
    if (s != head) {
      assert s.doNotDestroy && s.elemSize == normCapacity;
      // 在 subPage 的 bitmap 中的下标 && 节点下标
      long handle = s.allocate();
      assert handle >= 0;
      // 用已经初始化过的 bytebuf 更新 subPage 中的信息
      s.chunk.initBufWithSubpage(buf, handle, reqCapacity);
      // 计数
      incTinySmallAllocation(tiny);
      return;
    }
  }
复制代码

당신이 볼 수 추적하는 initBufWithSubpage 방법 :

buf.init(
        this, handle,
        runOffset(memoryMapIdx) + (bitmapIdx & 0x3FFFFFFF) * subpage.elemSize + offset,
            reqCapacity, subpage.elemSize, arena.parent.threadCache());
复制代码

runOffset (memoryMapIdx는) memoryMapIdx 노드 지수 runOffset는 8192 만대 청크에서 노드의 오프셋을 나타내는 오프셋 노드 오프셋이 나타내는 (bitmapIdx 및 0x3FFFFFFF) * subpage.elemSize bitmapIdx 첨자의 서브 페이지 는 오프셋 오프셋 : 자신의 덩어리 오프셋 나타냅니다 

오프셋은 전체 캐시 풀 특정 오프셋 값을 나타내는 세 첨자의 합 bitmapIdx




이 문서에서는 지적 할 수 희망 잘못된 장소가 무엇인지, 개인적인 이해이다.

추천

출처juejin.im/post/5db8ea506fb9a02061399ab3