Netty之内存管理

客官°小女子只卖身不卖艺 2023-06-30 09:00 131阅读 0赞

目录

Netty内存规格介绍

内存区间划分

内存分配单位

内存分配

命中缓存分配

申请新内存分配

ByteBuf的释放与回收


Netty内存规格介绍

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTEyMTIzOTQ_size_16_color_FFFFFF_t_70


内存区间划分

  • tiny:0~512B
  • small:512B~8K
  • normal:8K~16M
  • huge:16M以上

内存分配单位

  • Chunk:Netty中所有内存都是以Chunk为单位分配的,一个Chunk有16M,例如当前需要1M内存,那么就需要向系统申请一个Chunk单位的内存,然后再从这个Chunk中进一步划分。
  • Page:Chunk的划分单位为Page,一个Page有8K,那么一个Chunk就可以划分出2048个Page。
  • SubPage:有时候我们需要的内存远达不到一个Page的大小,那么Netty根据实际需要对Page进一步划分成SubPage。

内存分配

内存分配的逻辑在PoolArena的allocate方法中。

  1. private void allocate(PoolThreadCache cache, PooledByteBuf<T> buf, final int reqCapacity) {
  2. final int normCapacity = normalizeCapacity(reqCapacity);
  3. // < 8K
  4. if (isTinyOrSmall(normCapacity)) { // capacity < pageSize
  5. int tableIdx;
  6. PoolSubpage<T>[] table;
  7. boolean tiny = isTiny(normCapacity);
  8. if (tiny) { // < 512
  9. if (cache.allocateTiny(this, buf, reqCapacity, normCapacity)) {
  10. // was able to allocate out of the cache so move on
  11. return;
  12. }
  13. tableIdx = tinyIdx(normCapacity);
  14. table = tinySubpagePools;
  15. } else {
  16. if (cache.allocateSmall(this, buf, reqCapacity, normCapacity)) {
  17. // was able to allocate out of the cache so move on
  18. return;
  19. }
  20. tableIdx = smallIdx(normCapacity);
  21. table = smallSubpagePools;
  22. }
  23. final PoolSubpage<T> head = table[tableIdx];
  24. /**
  25. * Synchronize on the head. This is needed as {@link PoolChunk#allocateSubpage(int)} and
  26. * {@link PoolChunk#free(long)} may modify the doubly linked list as well.
  27. */
  28. synchronized (head) {
  29. final PoolSubpage<T> s = head.next;
  30. if (s != head) {
  31. assert s.doNotDestroy && s.elemSize == normCapacity;
  32. long handle = s.allocate();
  33. assert handle >= 0;
  34. s.chunk.initBufWithSubpage(buf, handle, reqCapacity);
  35. if (tiny) {
  36. allocationsTiny.increment();
  37. } else {
  38. allocationsSmall.increment();
  39. }
  40. return;
  41. }
  42. }
  43. allocateNormal(buf, reqCapacity, normCapacity);
  44. return;
  45. }
  46. // <= 16M
  47. if (normCapacity <= chunkSize) {
  48. if (cache.allocateNormal(this, buf, reqCapacity, normCapacity)) {
  49. // was able to allocate out of the cache so move on
  50. return;
  51. }
  52. allocateNormal(buf, reqCapacity, normCapacity);
  53. } else {
  54. // Huge allocations are never served via the cache so just call allocateHuge
  55. allocateHuge(buf, reqCapacity);
  56. }
  57. }

代码中的逻辑很清晰,根据传入的reqCapacity大小选择对应的内存规格。

tiny or small:< 8KB

tiny:< 512B

small:512B ~ 8KB

normal:<= 16MB

huge:> 16MB


命中缓存分配

通过上面的代码可以发现,PoolArena在分配过程中会先从缓存中寻找,看是否有可用的ByteBuf,如果没有再从新申请。这个缓存的数据结构就是MemoryRegionCache。

MemoryRegionCache数据结构

  1. private abstract static class MemoryRegionCache<T> {
  2. private final int size;
  3. private final Queue<Entry<T>> queue;
  4. private final SizeClass sizeClass;
  5. private int allocations;
  6. MemoryRegionCache(int size, SizeClass sizeClass) {
  7. this.size = MathUtil.safeFindNextPositivePowerOfTwo(size);
  8. queue = PlatformDependent.newFixedMpscQueue(this.size);
  9. this.sizeClass = sizeClass;
  10. }
  11. ......
  12. static final class Entry<T> {
  13. final Handle<Entry<?>> recyclerHandle;
  14. PoolChunk<T> chunk;
  15. long handle = -1;
  16. Entry(Handle<Entry<?>> recyclerHandle) {
  17. this.recyclerHandle = recyclerHandle;
  18. }
  19. void recycle() {
  20. chunk = null;
  21. handle = -1;
  22. recyclerHandle.recycle(this);
  23. }
  24. }
  25. ......
  26. }

queue:由Entry组成的队列,Entry又是于handle和chunk组成,Netty中所有内存都是以chunk为单位进行分配的,handle指向了一段连续的内存,通过chunk与handle就可以确定一段内存的大小和位置。

sizeClass:指的是按大小划分的三种内存规格。tiny(0~512B)、small(512B~8K)、normal(8K~16M)。huge不会被缓存,所以不在这里。

size:在同一个MemoryRegionCache的queue中,所有元素的大小都是一致的。这个size指定的就是这个统一的大小,也可以理解为是在sizeClass基础上进一步划分的结果。
























规格 queue数量 元素大小分类
tiny 32 16B、32B、48B……480B、496B(N*16B)
small 4 512K、1K、2K、4K
normal 3 8K、16、32K

可以看到Netty对内存分配的划分是非常细致的,这样才能尽可能的保证内存的连续性,从而提升效率。


MemoryRegionCache的分类与创建

我们知道每一个线程都会维护一个PoolThreadCache对象,那么对应的MemoryRegionCache也维护在这里。

  1. final class PoolThreadCache {
  2. ......
  3. // Hold the caches for the different size classes, which are tiny, small and normal.
  4. private final MemoryRegionCache<byte[]>[] tinySubPageHeapCaches;
  5. private final MemoryRegionCache<byte[]>[] smallSubPageHeapCaches;
  6. private final MemoryRegionCache<ByteBuffer>[] tinySubPageDirectCaches;
  7. private final MemoryRegionCache<ByteBuffer>[] smallSubPageDirectCaches;
  8. private final MemoryRegionCache<byte[]>[] normalHeapCaches;
  9. private final MemoryRegionCache<ByteBuffer>[] normalDirectCaches;
  10. ......
  11. }

这里heap与direct都分别有tiny、small、normal三种类型的cache数组。

数组初始化的逻辑在PoolThreadCache的构造函数中:

  1. PoolThreadCache(PoolArena<byte[]> heapArena, PoolArena<ByteBuffer> directArena,
  2. int tinyCacheSize, int smallCacheSize, int normalCacheSize,
  3. int maxCachedBufferCapacity, int freeSweepAllocationThreshold) {
  4. ......
  5. if (directArena != null) {
  6. tinySubPageDirectCaches = createSubPageCaches(
  7. tinyCacheSize, PoolArena.numTinySubpagePools, SizeClass.Tiny);
  8. smallSubPageDirectCaches = createSubPageCaches(
  9. smallCacheSize, directArena.numSmallSubpagePools, SizeClass.Small);
  10. numShiftsNormalDirect = log2(directArena.pageSize);
  11. normalDirectCaches = createNormalCaches(
  12. normalCacheSize, maxCachedBufferCapacity, directArena);
  13. directArena.numThreadCaches.getAndIncrement();
  14. } else {
  15. // No directArea is configured so just null out all caches
  16. tinySubPageDirectCaches = null;
  17. smallSubPageDirectCaches = null;
  18. normalDirectCaches = null;
  19. numShiftsNormalDirect = -1;
  20. }
  21. ......
  22. }

命中缓存分配总结:

  1. 找到对应size的MemoryRegionCache。
  2. 从queue中弹出一个entry给ByteBuf初始化。
  3. 将entry放回Recycler对象池进行复用。

申请新内存分配

当无法命中缓存时,就需要去系统申请新的内存,PoolArena会按照不同内存规格范围,根据chunk、page、subpage几个级别按照一定算法去分配。


ByteBuf的释放与回收

  1. 将连续的内存区段加入到缓存。这里连续的内存可理解为上面提到的entry对象,由它的handle与chunk属性确定位置与大小,结合所属的内存规格加入到对应的缓存queue中。
  2. 如果缓存队列已经满了,则将这块内存标记为未使用。
  3. 将使用过的ByteBuf对象加入到Recycler对象池。

发表评论

表情:
评论列表 (有 0 条评论,131人围观)

还没有评论,来说两句吧...

相关阅读

    相关 操作系统内存管理

    虚拟内存 虚拟内存的目的是为了让物理内存扩充成更大的逻辑内存,从而让程序获得更多的可用内存。 为了更好的管理内存,操作系统将内存抽象成地址空间。每个程序拥有自己的地址空