找回密码
 立即注册
首页 业界区 业界 Netty源码—7.ByteBuf原理二

Netty源码—7.ByteBuf原理二

鸳剿 4 天前
大纲
9.Netty的内存规格
10.缓存数据结构
11.命中缓存的分配流程
12.Netty里有关内存分配的重要概念
13.Page级别的内存分配
14.SubPage级别的内存分配
15.ByteBuf的回收
 
9.Netty的内存规格
(1)4种内存规格
(2)内存申请单位
 
(1)4种内存规格
一.tiny:表示从0到512字节之间的内存大小
二.small:表示从512字节到8K范围的内存大小
三.normal:表示从8K到16M范围的内存大小
四.huge:表示大于16M的内存大小
 
(2)内存申请单位
Netty里所有的内存申请都是以Chunk为单位向操作系统申请的,后续所有的内存分配都是在这个Chunk里进行对应的操作。比如要分配1M的内存,那么首先要申请一个16M的Chunk,然后在这个16M的Chunk里取出一段1M的连续内存放入到Netty的ByteBuf里。
 
注意:一个Chunk的大小为16M,一个Page的大小为8K,一个SubPage的大小是0~8K,一个Chunk可以分成2048个Page。
1.png
 
10.缓存数据结构
(1)MemoryRegionCache的组成
(2)MemoryRegionCache的类型
(3)MemoryRegionCache的源码
 
(1)MemoryRegionCache的组成
Netty中与缓存相关的数据结构叫MemoryRegionCache,这是内存相关的一个缓存。MemoryRegionCache由三部分组成:queue、sizeClass、size。
 
一.queue
queue是一个队列,里面的每个元素都是MemoryRegionCache内部类Entry的一个实体,每一个Entry实体里都有一个chunk和一个handle。Netty里所有的内存都是以Chunk为单位进行分配的,而每一个handle都指向唯一一段连续的内存。所以一个chunk + 一个指向连续内存的handle,就能确定这块Entry的内存大小和内存位置,然后所有这些Entry组合起来就变成一个缓存的链。
 
二.sizeClass
sizeClass是Netty里的内存规格,其中有三种类型的内存规则。一种是tiny(0~512B),一种是small(512B~8K),一种是normal(8K~16M)。由于huge是直接使用非缓存的内存分配,所以不在该sizeClass范围内。
 
三.size
一个MemoryRegionCache所缓存的一个ByteBuf的大小是固定的。如果MemoryRegionCache里缓存了1K的ByteBuf,那么queue里所有的元素都是1K的ByteBuf。也就是说,同一个MemoryRegionCache它的queue里的所有元素都是固定大小的。这些固定大小分别有:tiny类型规则的是16B的整数倍直到498B,small类型规则的有512B、1K、2K、4K,normal类型规定的有8K、16K、32K。所以对于32K以上是不缓存的。
 
(2)MemoryRegionCache的类型
Netty里所有规格的MemoryRegionCache如下图示,下面的每个节点就相当于一个MemoryRegionCache的数据结构。
2.png
其中tiny类型的内存规格有32种,也就是32个节点,分别是16B、32B、48B、......、496B。这里面的每个节点都是一个MemoryRegionCache,每个MemoryRegionCache里都有一个queue。假设要分配一个16B的ByteBuf:首先会定位到small类型的内存规格里的第二个节点,然后从该节点维护的queue队列里取出一个Entry元素。通过该Entry元素可以拿到它属于哪一个chunk以及哪一个handle,从而进行内存划分。
 
small类型的内存规格有4种,也就是4个节点,分别是512B、1K、2K、4K。每个节点都是一个MemoryRegionCache,每个MemoryRegionCache里都有一个queue。假设要分配一个1K的ByteBuf:首先会定位到small类型的内存规格里的第二个节点,然后从该节点维护的queue里取出一个Entry元素。这样就可以基于这个Entry元素分配出1K内存的ByteBuf,不需要再去Chunk上找一段临时内存了。
 
normal类型的内存规格有3种,也就是3个节点,分别是8K、16K、32K,关于Normal大小的ByteBuf的内存分配也是同样道理。
 
(3)MemoryRegionCache的源码
每个线程都会有一个PoolThreadCache对象,每个PoolThreadCache对象都会有tiny、small、normal三种规格的缓存。每种规格又分heap和direct,所以每个PoolThreadCache对象会有6种缓存。PoolThreadCache类正是使用了6个MemoryRegionCache数组来维护这6种缓存。如:
 
数组tinySubPageHeapCaches拥有32个MemoryRegionCache元素,下标为n的元素用于缓存大小为n * 16B的ByteBuf。
 
数组smallSubPageHeapCaches拥有4个MemoryRegionCache元素,下标为n的元素用于缓存大小为2^n * 512B的ByteBuf。
 
数组normalHeapCaches拥有3个MemoryRegionCache元素,下标为n的元素用于缓存大小为2^n * 8K的ByteBuf。
 
数组tinySubPageHeapCaches里的每个MemoryRegionCache元素,最多可以缓存tinyCacheSize个即512个ByteBuf。
 
数组smallSubPageHeapCaches里的每个MemoryRegionCache元素,最多可以缓存smallCacheSize个即256个ByteBuf。
 
数组normalHeapCaches里的每个MemoryRegionCache元素,最多可以缓存normalCacheSize个即64个ByteBuf。
  1. final class PoolThreadCache {
  2.     //真正要分配的内存其实就是byte[] 或者 ByteBuffer,所以实际的分配就是得到一个数值handle进行定位
  3.     final PoolArena<byte[]> heapArena;
  4.     final PoolArena<ByteBuffer> directArena;
  5.     //Hold the caches for the different size classes, which are tiny, small and normal.
  6.     //有32个MemoryRegionCache元素,分别存放16B、32B、48B、...、480B、496B的SubPage级别的内存
  7.     private final MemoryRegionCache<byte[]>[] tinySubPageHeapCaches;
  8.     //有4个MemoryRegionCache元素,分别存放512B、1K、2K、4K的SubPage级别的内存
  9.     private final MemoryRegionCache<byte[]>[] smallSubPageHeapCaches;
  10.     //有3个MemoryRegionCache元素,分别存放8K、16K、32K的Page级别的内存
  11.     private final MemoryRegionCache<byte[]>[] normalHeapCaches;
  12.     private final MemoryRegionCache<ByteBuffer>[] tinySubPageDirectCaches;
  13.     private final MemoryRegionCache<ByteBuffer>[] smallSubPageDirectCaches;
  14.     private final MemoryRegionCache<ByteBuffer>[] normalDirectCaches;
  15.    
  16.     PoolThreadCache(PoolArena<byte[]> heapArena, PoolArena<ByteBuffer> directArena,
  17.             int tinyCacheSize, int smallCacheSize, int normalCacheSize,
  18.             int maxCachedBufferCapacity, int freeSweepAllocationThreshold) {
  19.         ...
  20.         this.freeSweepAllocationThreshold = freeSweepAllocationThreshold;
  21.         this.heapArena = heapArena;
  22.         this.directArena = directArena;
  23.         if (directArena != null) {
  24.             tinySubPageDirectCaches = createSubPageCaches(tinyCacheSize, PoolArena.numTinySubpagePools, SizeClass.Tiny);
  25.             smallSubPageDirectCaches = createSubPageCaches(smallCacheSize, directArena.numSmallSubpagePools, SizeClass.Small);
  26.             numShiftsNormalDirect = log2(directArena.pageSize);
  27.             normalDirectCaches = createNormalCaches(normalCacheSize, maxCachedBufferCapacity, directArena);
  28.             directArena.numThreadCaches.getAndIncrement();
  29.         } else {
  30.             //No directArea is configured so just null out all caches
  31.             tinySubPageDirectCaches = null;
  32.             smallSubPageDirectCaches = null;
  33.             normalDirectCaches = null;
  34.             numShiftsNormalDirect = -1;
  35.         }
  36.         if (heapArena != null) {
  37.             //Create the caches for the heap allocations
  38.             tinySubPageHeapCaches = createSubPageCaches(tinyCacheSize, PoolArena.numTinySubpagePools, SizeClass.Tiny);
  39.             smallSubPageHeapCaches = createSubPageCaches(smallCacheSize, heapArena.numSmallSubpagePools, SizeClass.Small);
  40.             numShiftsNormalHeap = log2(heapArena.pageSize);
  41.             normalHeapCaches = createNormalCaches(normalCacheSize, maxCachedBufferCapacity, heapArena);
  42.             heapArena.numThreadCaches.getAndIncrement();
  43.         } else {
  44.             //No heapArea is configured so just null out all caches
  45.             tinySubPageHeapCaches = null;
  46.             smallSubPageHeapCaches = null;
  47.             normalHeapCaches = null;
  48.             numShiftsNormalHeap = -1;
  49.         }
  50.         //The thread-local cache will keep a list of pooled buffers which must be returned to the pool when the thread is not alive anymore.
  51.         ThreadDeathWatcher.watch(thread, freeTask);
  52.     }
  53.    
  54.     private static <T> MemoryRegionCache<T>[] createSubPageCaches(int cacheSize, int numCaches, SizeClass sizeClass) {
  55.         if (cacheSize > 0) {
  56.             @SuppressWarnings("unchecked")
  57.             MemoryRegionCache<T>[] cache = new MemoryRegionCache[numCaches];
  58.             for (int i = 0; i < cache.length; i++) {
  59.                 cache[i] = new SubPageMemoryRegionCache<T>(cacheSize, sizeClass);
  60.             }
  61.             return cache;
  62.         } else {
  63.             return null;
  64.         }
  65.     }
  66.     private static <T> MemoryRegionCache<T>[] createNormalCaches(int cacheSize, int maxCachedBufferCapacity, PoolArena<T> area) {
  67.         if (cacheSize > 0) {
  68.             int max = Math.min(area.chunkSize, maxCachedBufferCapacity);
  69.             int arraySize = Math.max(1, log2(max / area.pageSize) + 1);
  70.             @SuppressWarnings("unchecked")
  71.             MemoryRegionCache<T>[] cache = new MemoryRegionCache[arraySize];
  72.             for (int i = 0; i < cache.length; i++) {
  73.                 cache[i] = new NormalMemoryRegionCache<T>(cacheSize);
  74.             }
  75.             return cache;
  76.         } else {
  77.             return null;
  78.         }
  79.     }
  80.    
  81.     private static final class SubPageMemoryRegionCache<T> extends MemoryRegionCache<T> {
  82.         SubPageMemoryRegionCache(int size, SizeClass sizeClass) {
  83.             super(size, sizeClass);
  84.         }
  85.         ...
  86.     }
  87.     private static int log2(int val) {
  88.         int res = 0;
  89.         while (val > 1) {
  90.             val >>= 1;
  91.             res++;
  92.         }
  93.         return res;
  94.     }
  95.    
  96.     ...
  97.    
  98.     private abstract static class MemoryRegionCache<T> {
  99.         private final int size;
  100.         private final Queue<Entry<T>> queue;
  101.         private final SizeClass sizeClass;
  102.         MemoryRegionCache(int size, SizeClass sizeClass) {
  103.             this.size = MathUtil.safeFindNextPositivePowerOfTwo(size);
  104.             queue = PlatformDependent.newFixedMpscQueue(this.size);
  105.             this.sizeClass = sizeClass;
  106.         }
  107.         ...
  108.         
  109.         static final class Entry<T> {
  110.             final Handle<Entry<?>> recyclerHandle;
  111.             PoolChunk<T> chunk;
  112.             long handle = -1;
  113.             Entry(Handle<Entry<?>> recyclerHandle) {
  114.                 this.recyclerHandle = recyclerHandle;
  115.             }
  116.             void recycle() {
  117.                 chunk = null;
  118.                 handle = -1;
  119.                 recyclerHandle.recycle(this);
  120.             }
  121.         }
  122.     }
  123. }
  124. abstract class PoolArena<T> implements PoolArenaMetric {
  125.     enum SizeClass {
  126.         Tiny,
  127.         Small,
  128.         Normal
  129.     }
  130.     ...
  131. }
复制代码
(3)标记连续内存的区段为未使用
标记方式会根据Page级别和SubPage级别进行标记,其中Page级别是根据二叉树来进行标记,SubPage级别是通过位图进行标记。
  1. public class PooledByteBufAllocator extends AbstractByteBufAllocator {
  2.     private final PoolThreadLocalCache threadCache;
  3.     private final PoolArena<byte[]>[] heapArenas;//一个线程会和一个PoolArena绑定
  4.     private final PoolArena<ByteBuffer>[] directArenas;//一个线程会和一个PoolArena绑定
  5.     ...
  6.     @Override
  7.     protected ByteBuf newHeapBuffer(int initialCapacity, int maxCapacity) {
  8.         PoolThreadCache cache = threadCache.get();
  9.         PoolArena<byte[]> heapArena = cache.heapArena;
  10.         ByteBuf buf;
  11.         if (heapArena != null) {
  12.             //分配堆内存
  13.             buf = heapArena.allocate(cache, initialCapacity, maxCapacity);
  14.         } else {
  15.             buf = new UnpooledHeapByteBuf(this, initialCapacity, maxCapacity);
  16.         }
  17.         return toLeakAwareBuffer(buf);
  18.     }
  19.     @Override
  20.     protected ByteBuf newDirectBuffer(int initialCapacity, int maxCapacity) {
  21.         PoolThreadCache cache = threadCache.get();
  22.         PoolArena<ByteBuffer> directArena = cache.directArena;
  23.         ByteBuf buf;
  24.         if (directArena != null) {
  25.             //分配直接内存
  26.             buf = directArena.allocate(cache, initialCapacity, maxCapacity);
  27.         } else {
  28.             if (PlatformDependent.hasUnsafe()) {
  29.                 buf = UnsafeByteBufUtil.newUnsafeDirectByteBuf(this, initialCapacity, maxCapacity);
  30.             } else {
  31.                 buf = new UnpooledDirectByteBuf(this, initialCapacity, maxCapacity);
  32.             }
  33.         }
  34.         return toLeakAwareBuffer(buf);
  35.     }
  36.     ...
  37. }
  38. abstract class PoolArena<T> implements PoolArenaMetric {
  39.     ...
  40.     PooledByteBuf<T> allocate(PoolThreadCache cache, int reqCapacity, int maxCapacity) {
  41.         PooledByteBuf<T> buf = newByteBuf(maxCapacity);//创建ByteBuf对象
  42.         allocate(cache, buf, reqCapacity);//基于PoolThreadCache对ByteBuf对象进行内存分配
  43.         return buf;
  44.     }
  45.    
  46.     private void allocate(PoolThreadCache cache, PooledByteBuf<T> buf, final int reqCapacity) {
  47.         //1.根据reqCapacity进行分段规格化
  48.         final int normCapacity = normalizeCapacity(reqCapacity);
  49.         if (isTinyOrSmall(normCapacity)) {//capacity < pageSize,需要分配的内存小于8K
  50.             int tableIdx;
  51.             PoolSubpage<T>[] table;
  52.             boolean tiny = isTiny(normCapacity);
  53.             if (tiny) {//< 512
  54.                 //2.进行缓存分配
  55.                 if (cache.allocateTiny(this, buf, reqCapacity, normCapacity)) {
  56.                     //命中缓存,was able to allocate out of the cache so move on
  57.                     return;
  58.                 }
  59.                 tableIdx = tinyIdx(normCapacity);
  60.                 table = tinySubpagePools;
  61.             } else {
  62.                 //2.进行缓存分配
  63.                 if (cache.allocateSmall(this, buf, reqCapacity, normCapacity)) {
  64.                     //命中缓存,was able to allocate out of the cache so move on
  65.                     return;
  66.                 }
  67.                 tableIdx = smallIdx(normCapacity);
  68.                 table = smallSubpagePools;
  69.             }
  70.             final PoolSubpage<T> head = table[tableIdx];
  71.             //Synchronize on the head.
  72.             //This is needed as PoolChunk#allocateSubpage(int) and PoolChunk#free(long) may modify the doubly linked list as well.
  73.             synchronized (head) {
  74.                 final PoolSubpage<T> s = head.next;
  75.                 if (s != head) {
  76.                     assert s.doNotDestroy && s.elemSize == normCapacity;
  77.                     long handle = s.allocate();
  78.                     assert handle >= 0;
  79.                     s.chunk.initBufWithSubpage(buf, handle, reqCapacity);
  80.                     if (tiny) {
  81.                         allocationsTiny.increment();
  82.                     } else {
  83.                         allocationsSmall.increment();
  84.                     }
  85.                     return;
  86.                 }
  87.             }
  88.             //没有命中缓存
  89.             allocateNormal(buf, reqCapacity, normCapacity);
  90.             return;
  91.         }
  92.         if (normCapacity <= chunkSize) {//需要分配的内存大于8K,但小于16M
  93.             //2.进行缓存分配
  94.             if (cache.allocateNormal(this, buf, reqCapacity, normCapacity)) {
  95.                 //命中缓存,was able to allocate out of the cache so move on
  96.                 return;
  97.             }
  98.             //没有命中缓存
  99.             allocateNormal(buf, reqCapacity, normCapacity);
  100.         } else {//需要分配的内存大于16M
  101.             //Huge allocations are never served via the cache so just call allocateHuge
  102.             allocateHuge(buf, reqCapacity);
  103.         }
  104.     }
  105.    
  106.     //根据reqCapacity进行分段规格化
  107.     int normalizeCapacity(int reqCapacity) {
  108.         if (reqCapacity < 0) {
  109.             throw new IllegalArgumentException("capacity: " + reqCapacity + " (expected: 0+)");
  110.         }
  111.         if (reqCapacity >= chunkSize) {
  112.             return reqCapacity;
  113.         }
  114.         if (!isTiny(reqCapacity)) { // >= 512
  115.             int normalizedCapacity = reqCapacity;
  116.             normalizedCapacity --;
  117.             normalizedCapacity |= normalizedCapacity >>>  1;
  118.             normalizedCapacity |= normalizedCapacity >>>  2;
  119.             normalizedCapacity |= normalizedCapacity >>>  4;
  120.             normalizedCapacity |= normalizedCapacity >>>  8;
  121.             normalizedCapacity |= normalizedCapacity >>> 16;
  122.             normalizedCapacity ++;
  123.             if (normalizedCapacity < 0) {
  124.                 normalizedCapacity >>>= 1;
  125.             }
  126.             return normalizedCapacity;
  127.         }
  128.         if ((reqCapacity & 15) == 0) {
  129.             return reqCapacity;
  130.         }
  131.         return (reqCapacity & ~15) + 16;
  132.     }
  133.     ...
  134. }
  135. final class PoolThreadCache {
  136.     final PoolArena<byte[]> heapArena;
  137.     final PoolArena<ByteBuffer> directArena;
  138.     //Hold the caches for the different size classes, which are tiny, small and normal.
  139.     //有32个MemoryRegionCache元素,分别存放16B、32B、48B、...、480B、496B的SubPage级别的内存
  140.     private final MemoryRegionCache<byte[]>[] tinySubPageHeapCaches;
  141.     //有4个MemoryRegionCache元素,分别存放512B、1K、2K、4K的SubPage级别的内存
  142.     private final MemoryRegionCache<byte[]>[] smallSubPageHeapCaches;
  143.     //有3个MemoryRegionCache元素,分别存放8K、16K、32K的Page级别的内存
  144.     private final MemoryRegionCache<byte[]>[] normalHeapCaches;
  145.     private final MemoryRegionCache<ByteBuffer>[] tinySubPageDirectCaches;
  146.     private final MemoryRegionCache<ByteBuffer>[] smallSubPageDirectCaches;
  147.     private final MemoryRegionCache<ByteBuffer>[] normalDirectCaches;
  148.     ...
  149.    
  150.     //Try to allocate a tiny buffer out of the cache. Returns true if successful false otherwise
  151.     boolean allocateTiny(PoolArena<?> area, PooledByteBuf<?> buf, int reqCapacity, int normCapacity) {
  152.         //首先调用cacheForTiny()方法找到需要分配的size对应的MemoryRegionCache
  153.         //然后调用allocate()方法基于MemoryRegionCache去给ByteBuf对象分配内存
  154.         return allocate(cacheForTiny(area, normCapacity), buf, reqCapacity);
  155.     }
  156.    
  157.     //找到需要分配的size对应的MemoryRegionCache
  158.     private MemoryRegionCache<?> cacheForTiny(PoolArena<?> area, int normCapacity) {
  159.         int idx = PoolArena.tinyIdx(normCapacity);
  160.         if (area.isDirect()) {
  161.             return cache(tinySubPageDirectCaches, idx);
  162.         }
  163.         return cache(tinySubPageHeapCaches, idx);
  164.     }
  165.    
  166.     //根据索引去缓存数组中返回一个MemoryRegionCache元素
  167.     private static <T> MemoryRegionCache<T> cache(MemoryRegionCache<T>[] cache, int idx) {
  168.         if (cache == null || idx > cache.length - 1) {
  169.             return null;
  170.         }
  171.         return cache[idx];
  172.     }
  173.    
  174.     //基于MemoryRegionCache去给ByteBuf对象分配内存
  175.     private boolean allocate(MemoryRegionCache<?> cache, PooledByteBuf buf, int reqCapacity) {
  176.         if (cache == null) {
  177.             return false;
  178.         }
  179.         //调用MemoryRegionCache的allocate()方法给buf分配大小为reqCapacity的一块内存
  180.         boolean allocated = cache.allocate(buf, reqCapacity);
  181.         if (++ allocations >= freeSweepAllocationThreshold) {
  182.             allocations = 0;
  183.             trim();
  184.         }
  185.         return allocated;
  186.     }
  187.     ...
  188.     private abstract static class MemoryRegionCache<T> {
  189.         private final int size;
  190.         private final Queue<Entry<T>> queue;
  191.         private final SizeClass sizeClass;
  192.         private int allocations;
  193.         ...
  194.         //Allocate something out of the cache if possible and remove the entry from the cache.
  195.         public final boolean allocate(PooledByteBuf<T> buf, int reqCapacity) {
  196.             //步骤一:从queue队列中弹出一个Entry元素
  197.             Entry<T> entry = queue.poll();
  198.             if (entry == null) {
  199.                 return false;
  200.             }
  201.             //步骤二:初始化buf
  202.             initBuf(entry.chunk, entry.handle, buf, reqCapacity);
  203.             //步骤三:将弹出的Entry元素放入对象池中进行复用
  204.             entry.recycle();
  205.             //allocations is not thread-safe which is fine as this is only called from the same thread all time.
  206.             ++ allocations;
  207.             return true;
  208.         }
  209.      
  210.         //Init the PooledByteBuf using the provided chunk and handle with the capacity restrictions.
  211.         protected abstract void initBuf(PoolChunk<T> chunk, long handle, PooledByteBuf<T> buf, int reqCapacity);
  212.       
  213.         static final class Entry<T> {
  214.             final Handle<Entry<?>> recyclerHandle;
  215.             PoolChunk<T> chunk;
  216.             long handle = -1;
  217.             Entry(Handle<Entry<?>> recyclerHandle) {
  218.                 this.recyclerHandle = recyclerHandle;
  219.             }
  220.             void recycle() {
  221.                 chunk = null;
  222.                 handle = -1;
  223.                 recyclerHandle.recycle(this);
  224.             }
  225.         }
  226.     }
  227. }
复制代码
(4)将ByteBuf对象添加到对象池
一开始时,对象池是没有PooledByteBuf对象的,当PooledByteBuf对象被释放时不会被立即销毁,而是会加入到对象池里。
 
这样当Netty每次去拿一个PooledByteBuf对象时,就可以先从对象池里获取,取出对象之后就可以进行内存分配以及初始化了。
 
考虑到PooledByteBuf对象会经常被申请和释放,如果QPS非常高,可能会产生很多PooledByteBuf对象,而且频繁创建和释放PooledByteBuf对象也会比较耗费资源和降低性能。
 
所以Netty便使用了对象池来减少GC:当申请PooledByteBuf对象时,就可以尽可能从对象池里去取。当释放PooledByteBuf对象时,则可以将对象添加到对象池,从而实现对象复用。
  1. final class PoolThreadCache {
  2.     final PoolArena<byte[]> heapArena;
  3.     final PoolArena<ByteBuffer> directArena;
  4.     //Hold the caches for the different size classes, which are tiny, small and normal.
  5.     //有32个MemoryRegionCache元素,分别存放16B、32B、48B、...、480B、496B的SubPage级别的内存
  6.     private final MemoryRegionCache<byte[]>[] tinySubPageHeapCaches;
  7.     //有4个MemoryRegionCache元素,分别存放512B、1K、2K、4K的SubPage级别的内存
  8.     private final MemoryRegionCache<byte[]>[] smallSubPageHeapCaches;
  9.     //有3个MemoryRegionCache元素,分别存放8K、16K、32K的Page级别的内存
  10.     private final MemoryRegionCache<byte[]>[] normalHeapCaches;
  11.     private final MemoryRegionCache<ByteBuffer>[] tinySubPageDirectCaches;
  12.     private final MemoryRegionCache<ByteBuffer>[] smallSubPageDirectCaches;
  13.     private final MemoryRegionCache<ByteBuffer>[] normalDirectCaches;
  14.     ...
  15. }
复制代码
 

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
您需要登录后才可以回帖 登录 | 立即注册