1. 程式人生 > >死磕Netty原始碼之記憶體分配詳解(一)(PooledByteBufAllocator)

死磕Netty原始碼之記憶體分配詳解(一)(PooledByteBufAllocator)

前言

為了避免頻繁的記憶體分配給系統帶來負擔以及GC對系統性能帶來波動,Netty4使用了記憶體池來管理記憶體的分配和回收,Netty記憶體池參考了Slab分配和Buddy分配思想。Slab分配是將記憶體分割成大小不等的記憶體塊,在使用者執行緒請求時根據請求的記憶體大小分配最為貼近Size的記憶體快,減少記憶體碎片同時避免了記憶體浪費。Buddy分配是把一塊記憶體塊等量分割回收時候進行合併,儘可能保證系統中有足夠大的連續記憶體

記憶體資料結構

記憶體分級從上到下主要分為: Arena、ChunkList、Chunk、Page、SubPage,他們關係如下圖

Netty記憶體資料結構

Netty記憶體資料結構

PooledArena是一塊連續的記憶體塊,為了優化併發效能在Netty記憶體池中存在一個由多個Arena組成的陣列,在多個執行緒進行記憶體分配時會按照輪詢策略選擇一個Arena進行記憶體分配。一個PoolArena記憶體塊是由兩個SubPagePools(用來儲存零碎記憶體)和多個ChunkList組成,兩個SubpagePools陣列分別為tinySubpagePools和smallSubpagePools。每個ChunkList裡包含多個Chunk按照雙向連結串列排列,每個Chunk裡包含多個Page(預設2048個),每個Page(預設大小為8k位元組)由多個Subpage組成。Subpage由M個”塊”構成,塊的大小由第一次申請記憶體大小決定。當分配一次記憶體之後此page會被加入到PoolArena的tinySubpagePools或smallSubpagePools中,下次分配時就如果”塊”大小相同則由其直接分配

當利用Arena來進行分配記憶體時,根據申請記憶體的大小有不同的策略。例如:如果申請記憶體的大小小於512時,則首先在cache嘗試分配,如果分配不成功則會在tinySubpagePools嘗試分配,如果分配不成功,則會在PoolChunk重新找一個PoolSubpage來進行記憶體分配,分配之後將此PoolSubpage儲存到tinySubpagePools中

記憶體分配原始碼分析

Netty記憶體分配包括了堆記憶體和非堆記憶體(Direct記憶體),但是記憶體分配的核心演算法是類似,所以從堆記憶體分配程式碼入手

public static void main(String[] args) {
    PooledByteBufAllocator pooledByteBufAllocator = new
PooledByteBufAllocator(false); ByteBuf byteBuf = pooledByteBufAllocator.heapBuffer(252); System.out.println(byteBuf); } 1.堆記憶體(HeapByteBuf)位元組緩衝區:特點是記憶體的分配和回收速度快可以被JVM自動回收,缺點就是如果進行Socket的IO讀寫,需要額外做一次記憶體複製(將堆記憶體對應的緩衝區複製到核心Channel中),效能會有一定程度的下降 2.直接記憶體(DirectByteBuf)位元組緩衝區:非堆記憶體它在堆外進行記憶體分配,相比於堆記憶體它的分配和回收速度會慢一些,但是將它寫入或者從Socket Channel中讀取時,由於少一次記憶體複製速度比堆記憶體快(需要手動維護GC) Netty的最佳實踐是在I/O通訊執行緒的讀寫緩衝區使用DirectByteBuf,後端業務訊息的編解碼模組使用HeapByteBuf

PooledByteBufAllocator

靜態程式碼

以下是PooledByteBufAllocator比較重要的一些靜態屬性,它們在靜態程式碼塊中被初始化

// 預設堆記憶體型別PoolArena個數
private static final int DEFAULT_NUM_HEAP_ARENA;
// 預設直接記憶體型別PoolArena個數
private static final int DEFAULT_NUM_DIRECT_ARENA;

// 預設頁大小 => 8K
private static final int DEFAULT_PAGE_SIZE;
// 每個chunk中的page是用平衡二叉樹對映管理每個PoolSubpage是否被分配
// maxOrder為樹的深度,深度為maxOrder層的節點數量為1 << maxOrder 預設maxOrder => 11
private static final int DEFAULT_MAX_ORDER;

// tiny cache的大小 => 預設512
private static final int DEFAULT_TINY_CACHE_SIZE;
// small cache的大小 => 預設256
private static final int DEFAULT_SMALL_CACHE_SIZE;
// normal cache的大小 => 預設64
private static final int DEFAULT_NORMAL_CACHE_SIZE;

// 最小PAGE_SIZE => 預設4K
private static final int MIN_PAGE_SIZE = 4096;
// 最大Chunk的大小 => 預設等於2的30次方 即1G
private static final int MAX_CHUNK_SIZE = (int) (((long) Integer.MAX_VALUE + 1) / 2);

以上這些常量除了最後兩個都是在如下的static塊中進行初始化

static {
    // 1.對DEFAULT_PAGE_SIZE進行初始化預設是8K
    // 使用者可以通過設定io.netty.allocator.pageSize來設定
    int defaultPageSize = SystemPropertyUtil.getInt("io.netty.allocator.pageSize", 8192);
    Throwable pageSizeFallbackCause = null;
    try {
        // 2.檢查pageSize是否大於MIN_PAGE_SIZE(4K)且是2的冪次方
        validateAndCalculatePageShifts(defaultPageSize);
    } catch (Throwable t) {
        pageSizeFallbackCause = t;
        defaultPageSize = 8192;
    }
    DEFAULT_PAGE_SIZE = defaultPageSize;
    // 3.對樹的深度DEFAULT_MAX_ORDER進行初始化 預設是11
    // 使用者可以通過io.netty.allocator.maxOrder來進行設定
    int defaultMaxOrder = SystemPropertyUtil.getInt("io.netty.allocator.maxOrder", 11);
    Throwable maxOrderFallbackCause = null;
    try {
        // 4.校驗maxOrder 期望值(0-14之間)
        validateAndCalculateChunkSize(DEFAULT_PAGE_SIZE, defaultMaxOrder);
    } catch (Throwable t) {
        maxOrderFallbackCause = t;
        defaultMaxOrder = 11;
    }
    // 5.對樹的深度DEFAULT_MAX_ORDER進行初始化 預設值為11
    // 使用者可以通過io.netty.allocator.maxOrder來進行設定
    DEFAULT_MAX_ORDER = defaultMaxOrder;


    final Runtime runtime = Runtime.getRuntime();
    final int defaultMinNumArena = NettyRuntime.availableProcessors() * 2;
    // 6.初始化預設chunk的大小,為PageSize * (2的maxOrder冪)
    final int defaultChunkSize = DEFAULT_PAGE_SIZE << DEFAULT_MAX_ORDER;
    // 7.計算PoolAreana的個數 PoolArena預設為:cpu核心執行緒數與最大堆記憶體/2/(3*chunkSize)這兩個數中的較小者
    // 這裡的除以2是為了確保系統分配的所有PoolArena佔用的記憶體不超過系統可用記憶體的一半,這裡的除以3是為了保證每個PoolArena至少可以由3個PoolChunk組成
    // 使用者可以通過io.netty.allocator.numHeapArenas/numDirectArenas來進行修改
    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)));

    // 8.對cache sizes進行了設定
    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);

    // 9.對其他常量進行設定
    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_USE_CACHE_FOR_ALL_THREADS = SystemPropertyUtil.getBoolean("io.netty.allocator.useCacheForAllThreads", true);
    DEFAULT_DIRECT_MEMORY_CACHE_ALIGNMENT = SystemPropertyUtil.getInt("io.netty.allocator.directMemoryCacheAlignment", 0);
}

構造方法

public PooledByteBufAllocator(boolean preferDirect) {
    this(preferDirect, DEFAULT_NUM_HEAP_ARENA, DEFAULT_NUM_DIRECT_ARENA, DEFAULT_PAGE_SIZE, DEFAULT_MAX_ORDER);
}

public PooledByteBufAllocator(boolean preferDirect, int nHeapArena, int nDirectArena, int pageSize, int maxOrder) {
    this(preferDirect, nHeapArena, nDirectArena, pageSize, maxOrder, DEFAULT_TINY_CACHE_SIZE, DEFAULT_SMALL_CACHE_SIZE, DEFAULT_NORMAL_CACHE_SIZE);
}

public PooledByteBufAllocator(boolean preferDirect, int nHeapArena, int nDirectArena, int pageSize, int maxOrder, int tinyCacheSize, int smallCacheSize, int normalCacheSize) {
    this(preferDirect, nHeapArena, nDirectArena, pageSize, maxOrder, tinyCacheSize, smallCacheSize, normalCacheSize, DEFAULT_USE_CACHE_FOR_ALL_THREADS, DEFAULT_DIRECT_MEMORY_CACHE_ALIGNMENT);
}

public PooledByteBufAllocator(boolean preferDirect, int nHeapArena, int nDirectArena, int pageSize, int maxOrder, int tinyCacheSize, int smallCacheSize, int normalCacheSize, boolean useCacheForAllThreads, int directMemoryCacheAlignment) {
    super(preferDirect);
    // 1.例項化了threadCache 欄位
    threadCache = new PoolThreadLocalCache(useCacheForAllThreads);
    // 2.使用了預設的值初始化了如下的欄位
    this.tinyCacheSize = tinyCacheSize;
    this.smallCacheSize = smallCacheSize;
    this.normalCacheSize = normalCacheSize;
    // 3.chunkSize初始化 其值為pageSize*2^maxOrder
    chunkSize = validateAndCalculateChunkSize(pageSize, maxOrder);

    if (nHeapArena < 0) {
        throw new IllegalArgumentException("nHeapArena: " + nHeapArena + " (expected: >= 0)");
    }

    if (nDirectArena < 0) {
        throw new IllegalArgumentException("nDirectArea: " + nDirectArena + " (expected: >= 0)");
    }

    if (directMemoryCacheAlignment < 0) {
        throw new IllegalArgumentException("directMemoryCacheAlignment: " + directMemoryCacheAlignment + " (expected: >= 0)");
    }

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

    if ((directMemoryCacheAlignment & -directMemoryCacheAlignment) != directMemoryCacheAlignment) {
        throw new IllegalArgumentException("directMemoryCacheAlignment: " + directMemoryCacheAlignment + " (expected: power of two)");
    }

    // 4.檢查pageSize是否大於4K且為2的冪次方如果不是則拋異常
    // 如果是則返回Integer.SIZE - 1 - Integer.numberOfLeadingZeros(pageSize)的結果
    // 通俗的說pageShifts就是pageSize二進位制表示時尾部0的個數 pageSize是8192時它的二進位制表示是10000000000000,那麼這個pageShifts就是13
    int pageShifts = validateAndCalculatePageShifts(pageSize);

    // 5.例項化heapArenas陣列
    if (nHeapArena > 0) {
        heapArenas = newArenaArray(nHeapArena);
        List<PoolArenaMetric> metrics = new ArrayList<PoolArenaMetric>(heapArenas.length);
        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();
    }

    // 6.例項化directArenas陣列
    if (nDirectArena > 0) {
        directArenas = newArenaArray(nDirectArena);
        List<PoolArenaMetric> metrics = new ArrayList<PoolArenaMetric>(directArenas.length);
        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);
}

總結:PooledByteBufAllocator類中主要完成了一些引數的初始化操作,最重要的為HeapArena陣列和DirectArena陣列。在利用PooledByteBufAllocator分配記憶體時其實就是利用Arena陣列中的元素來完成。在下一篇部落格中將對Arena進行詳細介紹