Netty13# 池化內存分配器

前言

PooledByteBufAllocator作爲池化內存分配的入口,提供了衆多的配置參數和便捷方法。這篇主要擼下他們大體都啥含義、幹啥用的。爲後面池化內存其他組件做鋪墊。

一、成員變量說明


下面的成員變量基本都提供了默認值,可以通過參數去自定義,下面表格給出具體說明。

成員變量 說明
DEFAULT_NUM_HEAP_ARENA PoolAreana(堆內存)個數,默認爲核數的2倍,可以由參數-Dio.netty.allocator.numHeapArenas指定
DEFAULT_NUM_DIRECT_ARENA PoolAreana(堆外內存)個數默認爲核數的2倍,堆外內存,可以通過-Dio.netty.allocator.numDirectArenas指定
DEFAULT_PAGE_SIZE 默認pageSize=8K,可以通過-Dio.netty.allocator.pageSize,需大於4096且爲2的倍數
DEFAULT_MAX_ORDER 二叉樹最高層數,取值範圍爲0~14,默認爲11,可以通過-Dio.netty.allocator.maxOrder參數指定
DEFAULT_TINY_CACHE_SIZE 默認tiny類型緩存池大小512,可以通過-Dio.netty.allocator.tinyCacheSize指定
DEFAULT_SMALL_CACHE_SIZE 默認small類型緩存池大小爲256,可以通過-Dio.netty.allocator.smallCacheSize指定
DEFAULT_NORMAL_CACHE_SIZE 默認normal類型緩存池大小爲64,可以通過-Dio.netty.allocator.normalCacheSize指定
DEFAULT_MAX_CACHED_BUFFER_CAPACITY 默認爲32KB,用於限制normal緩存數組的長度,可以通過-Dio.netty.allocator.maxCachedBufferCapacity指定
DEFAULT_CACHE_TRIM_INTERVAL 默認8192,分配次數閾值,超過後釋放內存池,可以通過-Dio.netty.allocator.cacheTrimInterval指定
DEFAULT_CACHE_TRIM_INTERVAL_MILLIS 默認0不開啓,定時釋放內存池,可以通過-Dio.netty.allocator.cacheTrimIntervalMillis指定
DEFAULT_USE_CACHE_FOR_ALL_THREADS 默認true,使用線程緩存,可以通過-Dio.netty.allocator.useCacheForAllThread制定
DEFAULT_DIRECT_MEMORY_CACHE_ALIGNMENT 直接內存的校準對齊參數,分配內存時按位與(&)校準。默認0不校準,可以通過-Dio.netty.allocator.directMemoryCacheAlignment指定
DEFAULT_MAX_CACHED_BYTEBUFFERS_PER_CHUNK 默認1023,指定PoolChunk緩存ByteBuffer對象的最大數量,可以通過-Dio.netty.allocator.maxCachedByteBuffersPerChunk指定
MIN_PAGE_SIZE 校驗用的,PageSize不能小於4KB
MAX_CHUNK_SIZE 校驗用的,Chunk的邊界值,(((long) Integer.MAX_VALUE + 1) / 2)
heapArenas Arena數組,元素爲HeapArena
directArenas Arena數組,元素爲DirectArena
PooledByteBufAllocatorMetric metric 暴露統計指標,例如:用了多少堆內存、用了多少堆外直接內存等


二、靜態塊賦值

DEFAULT_PAGE_SIZE

下面是通過static{}靜態塊賦值PageSize,默認爲8KB,可以通過-Dio.netty.allocator.pageSize自定義。

static {
        int defaultPageSize = SystemPropertyUtil.getInt("io.netty.allocator.pageSize"8192);
        Throwable pageSizeFallbackCause = null;
        try {
            validateAndCalculatePageShifts(defaultPageSize);
        } catch (Throwable t) {
            pageSizeFallbackCause = t;
            defaultPageSize = 8192;
        }
        DEFAULT_PAGE_SIZE = defaultPageSize;

PageSize校驗過程,不能小於MIN_PAGE_SIZE(4KB),需要2的倍數。

private static int validateAndCalculatePageShifts(int pageSize) {
        if (pageSize < MIN_PAGE_SIZE) {
            throw new IllegalArgumentException("pageSize: " + pageSize + " (expected: " + MIN_PAGE_SIZE + ")");
        }

        if ((pageSize & pageSize - 1) != 0) {
            throw new IllegalArgumentException("pageSize: " + pageSize + " (expected: power of 2)");
        }

        // Logarithm base 2. At this point we know that pageSize is a power of two.
        return Integer.SIZE - 1 - Integer.numberOfLeadingZeros(pageSize);
    }

DEFAULT_MAX_ORDER

maxOrder默認大小爲11,可以通過-Dio.netty.allocator.maxOrder自定義。

int defaultMaxOrder = SystemPropertyUtil.getInt("io.netty.allocator.maxOrder"11);
Throwable maxOrderFallbackCause = null;
try {
  validateAndCalculateChunkSize(DEFAULT_PAGE_SIZE, defaultMaxOrder);
catch (Throwable t) {
  maxOrderFallbackCause = t;
  defaultMaxOrder = 11;
}
DEFAULT_MAX_ORDER = defaultMaxOrder;

maxOrder校驗過程,最大值不能超過14,同時計算了ChunkSize <<=1 ,即:8192<<=1 爲 16777216(16M),也就是默認ChunkSize大小爲16M。

private static int validateAndCalculateChunkSize(int pageSize, int maxOrder) {
  if (maxOrder > 14) {
    throw new IllegalArgumentException("maxOrder: " + maxOrder + " (expected: 0-14)");
  }
  int chunkSize = pageSize;
  for (int i = maxOrder; i > 0; i --) {
    if (chunkSize > MAX_CHUNK_SIZE / 2) {
      throw new IllegalArgumentException(String.format(
        "pageSize (%d) << maxOrder (%d) must not exceed %d", pageSize, maxOrder, MAX_CHUNK_SIZE));
    }
    chunkSize <<= 1;
  }
  return chunkSize;
}

DEFAULT_NUM_HEAP_ARENA和DEFAULT_NUM_DIRECT_ARENA

final Runtime runtime = Runtime.getRuntime();
final int defaultMinNumArena = NettyRuntime.availableProcessors() * 2;
final int defaultChunkSize = DEFAULT_PAGE_SIZE << DEFAULT_MAX_ORDER;
DEFAULT_NUM_HEAP_ARENA = Math.max(0,
                SystemPropertyUtil.getInt(
                        "io.netty.allocator.numHeapArenas",
                        (int) Math.min(
                                defaultMinNumArena,
                                runtime.maxMemory() / defaultChunkSize / 2 / 3)));


 DEFAULT_NUM_DIRECT_ARENA = Math.max(0,
                SystemPropertyUtil.getInt(
                        "io.netty.allocator.numDirectArenas",
                        (int) Math.min(
                                defaultMinNumArena,
                                PlatformDependent.maxDirectMemory() / defaultChunkSize / 2 / 3)));

解讀: DEFAULT_NUM_HEAP_ARENA與DEFAULT_NUM_DIRECT_ARENA賦值結構相同,默認值也相同。DEFAULT_NUM_HEAP_ARENA通過-Dio.netty.allocator.numHeapArenas自定義;DEFAULT_NUM_DIRECT_ARENA通過-Dio.netty.allocator.numDirectArenas自定義。

參數 含義
defaultMinNumArena 默認爲CPU核數的2倍
defaultChunkSize DEFAULT_PAGE_SIZE << DEFAULT_MAX_ORDER 也就是 8192 << 11 = 16777216(16M)
runtime.maxMemory() Jvm從操作系統獲取的最大內存由參數-Xmx指定
runtime.maxMemory() / defaultChunkSize / 2 / 3 defaultChunkSize=16M,可以簡化爲 runtime.maxMemory()/96M。也就是Jvm最大內存/96M

所以默認DEFAULT_NUM_HEAP_ARENA=DEFAULT_NUM_DIRECT_ARENA,CPU核數2倍與runtime.maxMemory()/96M取最小值,在高配的環境下,通常爲核數的兩倍。

其他

下面這幾個,都是可以通過參數指定,沒啥特別邏輯,具體含義見上面成員變量。

DEFAULT_TINY_CACHE_SIZE = SystemPropertyUtil.getInt("io.netty.allocator.tinyCacheSize"512);
DEFAULT_SMALL_CACHE_SIZE = SystemPropertyUtil.getInt("io.netty.allocator.smallCacheSize"256);
DEFAULT_NORMAL_CACHE_SIZE = SystemPropertyUtil.getInt("io.netty.allocator.normalCacheSize"64);
DEFAULT_MAX_CACHED_BUFFER_CAPACITY = SystemPropertyUtil.getInt(
                "io.netty.allocator.maxCachedBufferCapacity"32 * 1024);
DEFAULT_CACHE_TRIM_INTERVAL = SystemPropertyUtil.getInt(
                "io.netty.allocator.cacheTrimInterval"8192);
DEFAULT_CACHE_TRIM_INTERVAL_MILLIS = SystemPropertyUtil.getLong(
                        "io.netty.allocator.cacheTrimIntervalMillis"0);
DEFAULT_USE_CACHE_FOR_ALL_THREADS = SystemPropertyUtil.getBoolean(
                "io.netty.allocator.useCacheForAllThreads"true);
DEFAULT_DIRECT_MEMORY_CACHE_ALIGNMENT = SystemPropertyUtil.getInt(
                "io.netty.allocator.directMemoryCacheAlignment"0);
DEFAULT_MAX_CACHED_BYTEBUFFERS_PER_CHUNK = SystemPropertyUtil.getInt(
                "io.netty.allocator.maxCachedByteBuffersPerChunk"1023);

三、構造函數


下面對構造函數的賦值和校驗進行走查。

public PooledByteBufAllocator(boolean preferDirect, int nHeapArena, int nDirectArena, int pageSize, int maxOrder,
                                  int tinyCacheSize, int smallCacheSize, int normalCacheSize,
                                  boolean useCacheForAllThreads, int directMemoryCacheAlignment)
 
{
        // 指定是否使用直接內存
        super(preferDirect);
        // 創建PoolThreadLocalCache,useCacheForAllThreads是否允許使用對象池
        threadCache = new PoolThreadLocalCache(useCacheForAllThreads);
        // tiny類型緩存大小
        this.tinyCacheSize = tinyCacheSize;
        // small類型緩存大小
        this.smallCacheSize = smallCacheSize;
        // normal類型緩存大小
        this.normalCacheSize = normalCacheSize;
        // chunk大小默認16M
        chunkSize = validateAndCalculateChunkSize(pageSize, maxOrder);
    // 需正數
        checkPositiveOrZero(nHeapArena, "nHeapArena");
        checkPositiveOrZero(nDirectArena, "nDirectArena");

        checkPositiveOrZero(directMemoryCacheAlignment, "directMemoryCacheAlignment");
        if (directMemoryCacheAlignment > 0 && !isDirectMemoryCacheAlignmentSupported()) {
            throw new IllegalArgumentException("directMemoryCacheAlignment is not supported");
        }

        if ((directMemoryCacheAlignment & -directMemoryCacheAlignment) != directMemoryCacheAlignment) {
            throw new IllegalArgumentException("directMemoryCacheAlignment: "
                    + directMemoryCacheAlignment + " (expected: power of two)");
        }
        // 頁偏移默認爲13,pageShift = Integer.SIZE - 1 - Integer.numberOfLeadingZeros(8192) = 32 - 1 - 18 = 13
        int pageShifts = validateAndCalculatePageShifts(pageSize);

        if (nHeapArena > 0) {
            // 堆內存,創建PoolArena[]數組
            heapArenas = newArenaArray(nHeapArena);
            List<PoolArenaMetric> metrics = new ArrayList<PoolArenaMetric>(heapArenas.length);
            // 給數組元素賦值HeapArena
            for (int i = 0; i < heapArenas.length; i ++) {
                PoolArena.HeapArena arena = new PoolArena.HeapArena(this,
                        pageSize, maxOrder, pageShifts, chunkSize,
                        directMemoryCacheAlignment);
                heapArenas[i] = arena;
                metrics.add(arena);
            }
            heapArenaMetrics = Collections.unmodifiableList(metrics);
        } else {
            heapArenas = null;
            heapArenaMetrics = Collections.emptyList();
        }

        if (nDirectArena > 0) {
            // 堆外直接內存,創建PoolArena[]數組
            directArenas = newArenaArray(nDirectArena);
            List<PoolArenaMetric> metrics = new ArrayList<PoolArenaMetric>(directArenas.length);
            // 直接內存數組賦值DirectArena
            for (int i = 0; i < directArenas.length; i ++) {
                PoolArena.DirectArena arena = new PoolArena.DirectArena(
                        this, pageSize, maxOrder, pageShifts, chunkSize, directMemoryCacheAlignment);
                directArenas[i] = arena;
                metrics.add(arena);
            }
            directArenaMetrics = Collections.unmodifiableList(metrics);
        } else {
            directArenas = null;
            directArenaMetrics = Collections.emptyList();
        }
        metric = new PooledByteBufAllocatorMetric(this);
    }


四、重要方法走查

比較重要的方法主要是newHeapBuffer&newDirectBuffer,這兩個的流程和代碼結構一致,大體流程見上一篇文末有梳理。

另外,metric這個方法可以觀察到內存相關情況。

public PooledByteBufAllocatorMetric metric() {
        return metric;
}

usedHeapMemory ,查看Netty內存池分配堆空間大小。

final long usedHeapMemory() {
  return usedMemory(heapArenas);
}

usedDirectMemory, 查看Netty內存池分配堆外直接空間大小。

final long usedDirectMemory() {
 return usedMemory(directArenas);
}

通過usedMemory累加PoolArena的內存分配。

 private static long usedMemory(PoolArena<?>[] arenas) {
   if (arenas == null) {
     return -1;
   }
   long used = 0;
   for (PoolArena<?> arena : arenas) {
     used += arena.numActiveBytes();
     if (used < 0) {
       return Long.MAX_VALUE;
     }
   }
   return used;
 }


關注公衆號,回覆「老梁」獲取所有文章

本文分享自微信公衆號 - 瓜農老梁(gh_01130ae30a83)。
如有侵權,請聯繫 [email protected] 刪除。
本文參與“OSC源創計劃”,歡迎正在閱讀的你也加入,一起分享。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章