Netty內存池核心PoolArena源碼解析

PoolArena?是 Netty 內存池化機制的核心組件之一,它負責管理一整塊或多塊內存(PoolChunk),并將這些內存分配給應用程序。每個?PoolArena?實例都與一個特定的線程相關聯(通過?PoolThreadCache),或者在禁用線程緩存時被多個線程共享。Netty 會創建多個?PoolArena?來減少多線程環境下的鎖競爭。

PoolArena?是一個抽象類,它有兩個具體的子類:

  • HeapArena: 用于分配堆內存 (byte[])。
  • DirectArena: 用于分配直接內存 (ByteBuffer)。

主要成員變量和職責

讓我們看一下?PoolArena?類中的一些關鍵字段:

// ...
abstract class PoolArena<T> implements PoolArenaMetric {// ...enum SizeClass {Small,Normal}final PooledByteBufAllocator parent; // 指向創建此 Arena 的 PooledByteBufAllocatorfinal PoolSubpage<T>[] smallSubpagePools; // 用于管理 Small 類型內存分配的 PoolSubpage 池數組// PoolChunkList 用于根據 PoolChunk 的使用率將其組織起來// qInit: 0-25% 使用率 (最初創建的 Chunk)// q000: < 50% 使用率// q025: 25-75% 使用率// q050: 50-100% 使用率// q075: 75-100% 使用率// q100: 100% 使用率 (已滿,但仍可分配 Subpage)private final PoolChunkList<T> q050;private final PoolChunkList<T> q025;private final PoolChunkList<T> q000;private final PoolChunkList<T> qInit;private final PoolChunkList<T> q075;private final PoolChunkList<T> q100;private final List<PoolChunkListMetric> chunkListMetrics; // 用于收集 PoolChunkList 的度量信息// 各種分配和釋放的計數器private long allocationsNormal; // Normal 類型分配次數private final LongAdder allocationsSmall = new LongAdder(); // Small 類型分配次數 (線程安全)private final LongAdder allocationsHuge = new LongAdder(); // Huge 類型分配次數 (線程安全)private final LongAdder activeBytesHuge = new LongAdder(); // Huge 類型活躍字節數 (線程安全)private long deallocationsSmall; // Small 類型釋放次數private long deallocationsNormal; // Normal 類型釋放次數private long pooledChunkAllocations; // 池化 Chunk 的分配次數private long pooledChunkDeallocations; // 池化 Chunk 的釋放次數private final LongAdder deallocationsHuge = new LongAdder(); // Huge 類型釋放次數 (線程安全)// 使用此 Arena 的線程緩存數量final AtomicInteger numThreadCaches = new AtomicInteger();private final ReentrantLock lock = new ReentrantLock(); // 用于保護 Arena 內部狀態的鎖final SizeClasses sizeClass; // 描述了 Arena 的大小規格配置 (pageSize, chunkSize 等)// ...
}

  • SizeClass: 枚舉類型,表示內存分配的類型,分為?Small?(小于等于?pageSize / 2,通常從?PoolSubpage?分配) 和?Normal?(大于?pageSize / 2?但小于?chunkSize,直接從?PoolChunk?分配)。
  • parent: 指向?PooledByteBufAllocator,這是內存分配器的頂層入口。
  • smallSubpagePools: 這是一個?PoolSubpage?數組,數組的每個元素是一個雙向鏈表的頭節點。相同大小的?Small?類型的?PoolSubpage?會被鏈接到同一個鏈表上,便于快速查找和分配。
  • qInit,?q000,?q025,?q050,?q075,?q100: 這些是?PoolChunkList?對象,它們形成了一個雙向鏈表結構。PoolArena?根據?PoolChunk?的內存使用率(usage())將其組織在不同的?PoolChunkList?中。例如,q050?存儲使用率在 50% 到 100% 之間的?PoolChunk。這種組織方式有助于在分配內存時,優先從使用率較高的?PoolChunk?中分配,以期盡快填滿并釋放空閑的?PoolChunk,從而減少內存碎片。
  • Metrics Counters: 大量的計數器用于追蹤不同類型(Small, Normal, Huge)的分配和釋放次數,以及活躍的字節數和 Chunk 數量。這些信息對于監控內存池的性能和狀態非常有用。LongAdder?用于在高并發場景下提供比?AtomicLong?更好的性能。
  • numThreadCaches: 記錄了當前有多少個?PoolThreadCache?正在使用這個?PoolArena
  • lock: 一個可重入鎖,用于在修改?PoolArena?的共享數據結構(如?PoolChunkList)時進行同步,防止并發沖突。
  • sizeClass: (實際上是?this.sizeClass,來自構造函數參數?SizeClasses sizeClass) 這是一個?SizeClasses?對象,它封裝了關于內存規格的配置信息,如?pageSize(頁大小)、pageShiftschunkSize(塊大小)等,并提供了一些計算方法,如根據請求大小計算規格索引 (size2SizeIdx)。

構造函數

PoolArena.java

// ...protected PoolArena(PooledByteBufAllocator parent, SizeClasses sizeClass) {assert null != sizeClass;this.parent = parent;this.sizeClass = sizeClass;smallSubpagePools = newSubpagePoolArray(sizeClass.nSubpages);for (int i = 0; i < smallSubpagePools.length; i ++) {smallSubpagePools[i] = newSubpagePoolHead(i);}q100 = new PoolChunkList<T>(this, null, 100, Integer.MAX_VALUE, sizeClass.chunkSize);q075 = new PoolChunkList<T>(this, q100, 75, 100, sizeClass.chunkSize);q050 = new PoolChunkList<T>(this, q100, 50, 100, sizeClass.chunkSize); // 注意這里 nextList 是 q100q025 = new PoolChunkList<T>(this, q050, 25, 75, sizeClass.chunkSize);q000 = new PoolChunkList<T>(this, q025, 1, 50, sizeClass.chunkSize);qInit = new PoolChunkList<T>(this, q000, Integer.MIN_VALUE, 25, sizeClass.chunkSize);q100.prevList(q075);q075.prevList(q050);q050.prevList(q025);q025.prevList(q000);q000.prevList(null); // q000 的前一個 List 是 null,表示它是鏈表的頭部(在查找時)qInit.prevList(qInit); // qInit 的 prevList 指向自身,它是一個特殊的 ListList<PoolChunkListMetric> metrics = new ArrayList<PoolChunkListMetric>(6);metrics.add(qInit);metrics.add(q000);metrics.add(q025);metrics.add(q050);metrics.add(q075);metrics.add(q100);chunkListMetrics = Collections.unmodifiableList(metrics);}private PoolSubpage<T> newSubpagePoolHead(int index) {PoolSubpage<T> head = new PoolSubpage<T>(index);head.prev = head;head.next = head;return head;}@SuppressWarnings("unchecked")private PoolSubpage<T>[] newSubpagePoolArray(int size) {return new PoolSubpage[size];}
// ...

構造函數主要做了以下幾件事:

  1. 初始化?parent?和?sizeClass
  2. 初始化?smallSubpagePools?數組,其中每個元素都是一個?PoolSubpage?鏈表的頭節點。newSubpagePoolHead?創建一個空的雙向循環鏈表。
  3. 初始化?qInit?到?q100?這些?PoolChunkList。注意它們的?minUsage?和?maxUsage?參數,以及它們之間的?nextList?和?prevList?關系,形成了一個查找鏈。
    • qInit: 用于存放新創建的?PoolChunk,使用率范圍是?Integer.MIN_VALUE?到?25%
    • q000: 使用率?1%?到?50%
    • q025: 使用率?25%?到?75%
    • q050: 使用率?50%?到?100%
    • q075: 使用率?75%?到?100%
    • q100: 使用率?100%?到?Integer.MAX_VALUE?(實際上是100%)。 這些?PoolChunkList?通過?prevList?和?nextList?鏈接起來,方便在分配和釋放時根據?PoolChunk?的使用率變化將其移動到合適的?PoolChunkList?中。

內存分配 (allocate)

內存分配是?PoolArena?的核心功能。

PoolArena.java

// ...PooledByteBuf<T> allocate(PoolThreadCache cache, int reqCapacity, int maxCapacity) {PooledByteBuf<T> buf = newByteBuf(maxCapacity); // 創建一個 PooledByteBuf 對象 (具體類型由子類決定)allocate(cache, buf, reqCapacity); // 調用內部的分配方法return buf;}private void allocate(PoolThreadCache cache, PooledByteBuf<T> buf, final int reqCapacity) {final int sizeIdx = sizeClass.size2SizeIdx(reqCapacity); // 根據請求容量計算規格索引if (sizeIdx <= sizeClass.smallMaxSizeIdx) { // Small 類型分配tcacheAllocateSmall(cache, buf, reqCapacity, sizeIdx);} else if (sizeIdx < sizeClass.nSizes) { // Normal 類型分配tcacheAllocateNormal(cache, buf, reqCapacity, sizeIdx);} else { // Huge 類型分配 (大于 chunkSize)int normCapacity = sizeClass.directMemoryCacheAlignment > 0? sizeClass.normalizeSize(reqCapacity) : reqCapacity;// Huge allocations are never served via the cache so just call allocateHugeallocateHuge(buf, normCapacity);}}
// ...
  1. allocate(PoolThreadCache cache, int reqCapacity, int maxCapacity): 這是外部調用的入口。它首先通過?newByteBuf(maxCapacity)?創建一個?PooledByteBuf?實例(具體是?PooledHeapByteBuf?還是?PooledDirectByteBuf?等由子類實現),然后調用內部的?allocate?方法來實際分配內存。
  2. allocate(PoolThreadCache cache, PooledByteBuf<T> buf, final int reqCapacity):
    • 首先,根據請求容量?reqCapacity?計算出對應的?sizeIdx?(size index)。
    • Small Allocation: 如果?sizeIdx?小于等于?smallMaxSizeIdx?(通常是?pageSize / 2?對應的索引),則認為是小內存分配,調用?tcacheAllocateSmall
    • Normal Allocation: 如果?sizeIdx?大于?smallMaxSizeIdx?但小于?nSizes?(總規格數,對應?chunkSize?的索引),則認為是普通內存分配,調用?tcacheAllocateNormal
    • Huge Allocation: 如果?sizeIdx?超出了?nSizes,表示請求的內存大于?chunkSize,則認為是大內存分配,調用?allocateHuge。大內存分配不會使用線程緩存,并且會創建一個獨立的、非池化的?PoolChunk

tcacheAllocateSmall?(Small 類型分配)

PoolArena.java

// ...private void tcacheAllocateSmall(PoolThreadCache cache, PooledByteBuf<T> buf, final int reqCapacity,final int sizeIdx) {if (cache.allocateSmall(this, buf, reqCapacity, sizeIdx)) { // 嘗試從線程緩存分配// was able to allocate out of the cache so move onreturn;}/** 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.*/final PoolSubpage<T> head = smallSubpagePools[sizeIdx]; // 獲取對應 sizeIdx 的 Subpage 鏈表頭final boolean needsNormalAllocation;head.lock(); // 對 Subpage 鏈表頭加鎖try {final PoolSubpage<T> s = head.next;needsNormalAllocation = s == head; // 如果鏈表為空,則需要進行 Normal Allocation 來創建新的 Subpageif (!needsNormalAllocation) {assert s.doNotDestroy && s.elemSize == sizeClass.sizeIdx2size(sizeIdx) : "doNotDestroy=" +s.doNotDestroy + ", elemSize=" + s.elemSize + ", sizeIdx=" + sizeIdx;long handle = s.allocate(); // 從 Subpage 中分配一個元素assert handle >= 0;s.chunk.initBufWithSubpage(buf, null, handle, reqCapacity, cache); // 初始化 ByteBuf}} finally {head.unlock();}if (needsNormalAllocation) { // 如果沒有可用的 Subpagelock(); // 獲取 Arena 的全局鎖try {allocateNormal(buf, reqCapacity, sizeIdx, cache); // 進行 Normal Allocation (可能會創建新的 Chunk 和 Subpage)} finally {unlock();}}incSmallAllocation(); // 增加 Small 分配計數}
// ...
  1. 首先嘗試從?PoolThreadCache?中分配。如果成功,則直接返回。
  2. 如果線程緩存分配失敗,則從?smallSubpagePools?中查找對應?sizeIdx?的?PoolSubpage?鏈表。
  3. 對該鏈表的頭節點?head?加鎖(head.lock()),這是為了保護?PoolSubpage?鏈表的并發修改。
  4. 如果鏈表中有可用的?PoolSubpage?(s != head),則從該?PoolSubpage?(s) 中調用?s.allocate()?分配一個元素(得到一個?handle),然后用這個?handle?初始化?PooledByteBuf
  5. 如果鏈表為空 (s == head),說明當前沒有合適的?PoolSubpage?可供分配。此時,needsNormalAllocation?為?true
  6. 釋放?head?的鎖。
  7. 如果?needsNormalAllocation?為?true,則需要進行一次“普通分配”(allocateNormal)。這通常意味著需要從某個?PoolChunk?中分配一個新的?PoolSubpage。這個過程需要獲取?PoolArena?的全局鎖 (lock())。
  8. 最后,增加?allocationsSmall?計數。

tcacheAllocateNormal?(Normal 類型分配)

PoolArena.java

// ...private void tcacheAllocateNormal(PoolThreadCache cache, PooledByteBuf<T> buf, final int reqCapacity,final int sizeIdx) {if (cache.allocateNormal(this, buf, reqCapacity, sizeIdx)) { // 嘗試從線程緩存分配// was able to allocate out of the cache so move onreturn;}lock(); // 獲取 Arena 的全局鎖try {allocateNormal(buf, reqCapacity, sizeIdx, cache); // 進行 Normal Allocation++allocationsNormal; // 增加 Normal 分配計數} finally {unlock();}}
// ...
  1. 首先嘗試從?PoolThreadCache?中分配。如果成功,則直接返回。
  2. 如果線程緩存分配失敗,則獲取?PoolArena?的全局鎖 (lock())。
  3. 調用?allocateNormal?方法進行實際的分配。
  4. 增加?allocationsNormal?計數。
  5. 釋放鎖。

allocateNormal?(核心普通分配邏輯)

// ...private void allocateNormal(PooledByteBuf<T> buf, int reqCapacity, int sizeIdx, PoolThreadCache threadCache) {assert lock.isHeldByCurrentThread(); // 確認當前線程已持有 Arena 鎖if (q050.allocate(buf, reqCapacity, sizeIdx, threadCache) || // 嘗試從 q050 分配q025.allocate(buf, reqCapacity, sizeIdx, threadCache) || // 嘗試從 q025 分配q000.allocate(buf, reqCapacity, sizeIdx, threadCache) || // 嘗試從 q000 分配qInit.allocate(buf, reqCapacity, sizeIdx, threadCache) || // 嘗試從 qInit 分配q075.allocate(buf, reqCapacity, sizeIdx, threadCache)) { // 最后嘗試從 q075 分配 (q100 通常是滿的)return;}// Add a new chunk.// 如果所有 PoolChunkList 都分配失敗,則創建一個新的 PoolChunkPoolChunk<T> c = newChunk(sizeClass.pageSize, sizeClass.nPSizes, sizeClass.pageShifts, sizeClass.chunkSize);boolean success = c.allocate(buf, reqCapacity, sizeIdx, threadCache); // 從新 Chunk 中分配assert success; // 新創建的 Chunk 必然能分配成功qInit.add(c); // 將新 Chunk 加入到 qInit 列表++pooledChunkAllocations; // 增加 Chunk 分配計數}
// ...

此方法在持有?PoolArena?全局鎖的情況下執行:

  1. 按順序嘗試從?PoolChunkList?分配
    • q050?(50-100% usage)
    • q025?(25-75% usage)
    • q000?(1-50% usage)
    • qInit?(newly created chunks, <25% usage)
    • q075?(75-100% usage)
    • q100?列表中的?PoolChunk?通常是滿的,但仍可能用于分配?PoolSubpage,這個邏輯在?PoolChunkList.allocate?->?PoolChunk.allocate?中處理)。 這個順序的目的是優先使用那些已經分配了一部分內存的?PoolChunk,以期更快地填滿它們,從而減少內存碎片。
  2. 如果上述所有?PoolChunkList?都無法成功分配(即它們內部的?PoolChunk?都沒有足夠的空間或者無法分配出請求大小的內存塊/子頁),則需要創建一個新的?PoolChunk
  3. 創建新?PoolChunk: 調用?newChunk(...)?方法(由子類?HeapArena?或?DirectArena?實現)創建一個新的?PoolChunk
  4. 從這個新創建的?PoolChunk?中調用?c.allocate(...)?來分配內存給?buf。新創建的?PoolChunk?肯定是空的,所以這次分配一定會成功。
  5. 將新創建的?PoolChunk?添加到?qInit?列表中。
  6. 增加?pooledChunkAllocations?計數。

為什么對于chunkList 是這樣的分配順序

觀察到的分配順序是 q050 , q025 , q000 , qInit , 最后是 q075 。這個順序并非簡單地按利用率從低到高或從高到低,而是基于一種旨在減少內存碎片的“最佳適應”(Best-Fit)策略的變體,這種策略深受 jemalloc 內存分配器的影響。

首先,我們先明確一下這些 PoolChunkList (也就是 q 系列的鏈表)各自管理的 PoolChunk 的內存使用率范圍。根據 PoolArena.java 中的定義:

  • qInit : 使用率低于 25% 的 Chunk (通常是新創建的)。

  • q000 : 使用率在 1% 到 50% 之間的 Chunk 。

  • q025 : 使用率在 25% 到 75% 之間的 Chunk 。

  • q050 : 使用率在 50% 到 100% 之間的 Chunk 。

  • q075 : 使用率在 75% 到 100% 之間的 Chunk 。

  • q100 : 使用率達到 100% 的 Chunk (已滿)。

現在我們來分析這個分配順序:

  1. 主要分配策略 ( q050 -> q025 -> q000 -> qInit ) : 這個順序是從使用率較高的 Chunk 列表開始,逐步到使用率較低的列表。這體現了“最佳適應”的思想。 目標是優先填滿那些已經分配了較多內存的 Chunk 。這樣做的好處是:

    1. 減少內存碎片 :通過集中在少數 Chunk 中進行分配,可以更快地將它們填滿(達到100%使用率),然后將它們移到 q100 鏈表中。一個全滿的 Chunk 不再參與后續的分配,當它內部的所有 ByteBuf 都被釋放后,這個 Chunk 就可以被完全回收,將內存歸還給操作系統。

    2. 提高效率 :如果總是從利用率最低的 Chunk (如 qInit )開始分配,會導致大量 Chunk 都處于“部分使用”的狀態,使得內存碎片化嚴重,難以分配較大的連續內存塊,并且降低了內存歸還給系統的可能性。

  2. q075 為什么排在最后 : q075 列表中的 Chunk 使用率已經非常高(75%-100%),意味著它們的剩余空間很小。 allocateNormal 方法用于分配“正常大小”的內存塊(大于 small ,小于 huge ),這種大小的內存在一個幾乎已滿的 Chunk 中找到合適空間的概率較低。

    1. 性能優化 :將 q075 放在最后檢查是一種性能優化。與其一開始就徒勞地在這些幾乎已滿的 Chunk 中搜索,不如先嘗試其他更有可能成功的 Chunk 列表。只有當其他所有列表都無法滿足分配請求時,才最后嘗試在這些“殘羹剩飯”中尋找機會。這減少了不必要的搜索開銷。

總結來說,Netty 的這個分配順序是一個精心設計的權衡:

  • 主體順序 ( q050 -> qInit ) 是為了 對抗內存碎片 ,傾向于“物盡其用”,盡快填滿并回收 Chunk 。

  • q075 的特殊位置 是為了 提升分配性能 ,避免在成功率低的 Chunk 上浪費時間。

這種設計使得 PooledByteBufAllocator 在高并發和長時間運行的場景下依然能保持高效和穩定的內存管理。

allocateHuge?(Huge 類型分配)

內存池的主要優勢在于復用頻繁申請和釋放的小到中等大小的內存塊,以避免頻繁向操作系統申請內存和垃圾回收帶來的開銷。

對于“巨大”(Huge)的內存分配(通常大于 chunkSize ,默認16MB),這種分配本身就不頻繁。如果將這些巨大的內存塊池化,意味著Netty需要長期持有一個或多個非常大的內存塊,即使它們在大部分時間里是空閑的。這會導致嚴重的內存資源浪費,尤其是在高并發環境下,可能會長時間占用大量內存,降低了整體的內存使用效率。

// ...private void allocateHuge(PooledByteBuf<T> buf, int reqCapacity) {PoolChunk<T> chunk = newUnpooledChunk(reqCapacity); // 創建一個非池化的、大小剛好滿足需求的 ChunkactiveBytesHuge.add(chunk.chunkSize()); // 增加 Huge 類型活躍字節數buf.initUnpooled(chunk, reqCapacity); // 用這個非池化 Chunk 初始化 ByteBufallocationsHuge.increment(); // 增加 Huge 分配計數}
// ...
  1. 調用?newUnpooledChunk(reqCapacity)?創建一個非池化的?PoolChunk。這個?PoolChunk?的大小就是?reqCapacity,它不會被放入任何?PoolChunkList?中,也不會被其他分配請求共享。
  2. 更新?activeBytesHuge?和?allocationsHuge?計數。
  3. 調用?buf.initUnpooled(...)?來初始化?PooledByteBuf

內存釋放 (free)

// ...void free(PoolChunk<T> chunk, ByteBuffer nioBuffer, long handle, int normCapacity, PoolThreadCache cache) {chunk.decrementPinnedMemory(normCapacity); // 減少 Chunk 的 pinned 內存計數if (chunk.unpooled) { // 如果是 Huge Allocation (非池化 Chunk)int size = chunk.chunkSize();destroyChunk(chunk); // 直接銷毀 ChunkactiveBytesHuge.add(-size); // 更新 Huge 類型活躍字節數deallocationsHuge.increment(); // 更新 Huge 類型釋放計數} else { // 池化 ChunkSizeClass sizeClass = sizeClass(handle); // 判斷是 Small 還是 Normal 釋放if (cache != null && cache.add(this, chunk, nioBuffer, handle, normCapacity, sizeClass)) {// 嘗試將內存在線程緩存中緩存起來// cached so not free it.return;}// 如果線程緩存失敗或沒有緩存,則真正釋放回 ArenafreeChunk(chunk, handle, normCapacity, sizeClass, nioBuffer, false);}}private static SizeClass sizeClass(long handle) {return isSubpage(handle) ? SizeClass.Small : SizeClass.Normal;}void freeChunk(PoolChunk<T> chunk, long handle, int normCapacity, SizeClass sizeClass, ByteBuffer nioBuffer,boolean finalizer) {final boolean destroyChunk;lock(); // 獲取 Arena 全局鎖try {// We only call this if freeChunk is not called because of the PoolThreadCache finalizer as otherwise this// may fail due lazy class-loading in for example tomcat.if (!finalizer) { // 如果不是由 finalizer 觸發的釋放switch (sizeClass) {case Normal:++deallocationsNormal;break;case Small:++deallocationsSmall;break;default:throw new Error();}}// 調用 PoolChunkList 的 free 方法,該方法內部會調用 PoolChunk 的 free// 如果 PoolChunk 完全空閑,則 !chunk.parent.free(...) 返回 true,表示需要銷毀 ChunkdestroyChunk = !chunk.parent.free(chunk, handle, normCapacity, nioBuffer);if (destroyChunk) {// all other destroyChunk calls come from the arena itself being finalized, so don't need to be counted++pooledChunkDeallocations; // 更新 Chunk 釋放計數}} finally {unlock();}if (destroyChunk) {// destroyChunk not need to be called while holding the synchronized lock.destroyChunk(chunk); // 在鎖外銷毀 Chunk}}
// ...
  1. free(...):
    • 首先減少?chunk?的?pinnedMemory?計數。
    • 非池化 Chunk (Huge): 如果?chunk.unpooled?為?true,說明是為大內存分配創建的獨立?PoolChunk。直接調用?destroyChunk(chunk)?銷毀它,并更新相應的 Huge 類型計數器。
    • 池化 Chunk:
      • 通過?isSubpage(handle)?判斷是?Small?還是?Normal?類型的釋放。
      • 嘗試將這塊內存添加到?PoolThreadCache?中。如果添加成功,則直接返回,內存被緩存了。
      • 如果緩存失敗或沒有線程緩存,則調用?freeChunk(...)?將內存真正釋放回?PoolArena
  2. freeChunk(...):
    • 獲取?PoolArena?的全局鎖。
    • 更新?deallocationsNormal?或?deallocationsSmall?計數(如果不是由?finalizer?觸發)。
    • 調用?chunk.parent.free(...),這里的?chunk.parent?是指該?PoolChunk?所在的?PoolChunkListPoolChunkList.free(...)?方法會進一步調用?PoolChunk.free(handle)?來釋放?PoolChunk?內部的內存。如果?PoolChunk?在釋放這塊內存后變為空閑(freeBytes == chunkSize),PoolChunkList.free(...)?會將該?PoolChunk?從鏈表中移除,并返回?false。因此,destroyChunk?變量會是?true
    • 如果?destroyChunk?為?true,增加?pooledChunkDeallocations?計數。
    • 釋放鎖。
    • 如果?destroyChunk?為?true,則在鎖外部調用?destroyChunk(chunk)?來實際銷毀?PoolChunk(例如,釋放底層的?ByteBuffer?或?byte[])。

內存重分配 (reallocate)

reallocate 函數主要在 PooledByteBuf 的容量需要改變,并且無法在當前已分配的內存塊( PoolChunk )內完成調整時被調用。具體來說,調用鏈是這樣的:

  1. 當調用 ByteBuf.capacity(int newCapacity) 方法來調整一個池化的 ByteBuf (即 PooledByteBuf )的大小時。

  2. capacity 方法內部,會檢查新的容量 newCapacity 是否可以直接在當前內存區域( maxLength )內容納。

  3. 如果 newCapacity 超出了當前內存塊能支持的范圍(例如,比當前 length 大,且大于 maxLength ),或者不滿足一些特定的收縮條件,就需要進行真正的內存重分配。此時,它會調用 chunk.arena.reallocate(this, newCapacity) ,也就是我們正在討論的 reallocate 方法。

// ...void reallocate(PooledByteBuf<T> buf, int newCapacity) {assert newCapacity >= 0 && newCapacity <= buf.maxCapacity();final int oldCapacity;final PoolChunk<T> oldChunk;final ByteBuffer oldNioBuffer;final long oldHandle;final T oldMemory;final int oldOffset;final int oldMaxLength;final PoolThreadCache oldCache;// We synchronize on the ByteBuf itself to ensure there is no "concurrent" reallocations for the same buffer.// ...synchronized (buf) { // 對 ByteBuf 對象本身加鎖,防止對同一個 buf 的并發重分配oldCapacity = buf.length;if (oldCapacity == newCapacity) {return;}// 保存舊 buf 的信息oldChunk = buf.chunk;oldNioBuffer = buf.tmpNioBuf;oldHandle = buf.handle;oldMemory = buf.memory;oldOffset = buf.offset;oldMaxLength = buf.maxLength;oldCache = buf.cache;// This does not touch buf's reader/writer indices// 為 buf 分配新的內存空間allocate(parent.threadCache(), buf, newCapacity);}int bytesToCopy;if (newCapacity > oldCapacity) {bytesToCopy = oldCapacity;} else {buf.trimIndicesToCapacity(newCapacity); // 如果新容量更小,調整讀寫指針bytesToCopy = newCapacity;}memoryCopy(oldMemory, oldOffset, buf, bytesToCopy); // 將舊內存數據拷貝到新內存free(oldChunk, oldNioBuffer, oldHandle, oldMaxLength, oldCache); // 釋放舊的內存塊}
// ...

reallocate 函數的核心作用是“搬家”:為 ByteBuf 申請一塊新的、符合 newCapacity 大小的內存,將舊內存中的有效數據復制過去,然后釋放舊的內存。這個過程確保了 ByteBuf 擴容或縮容時數據的完整性。

其主要步驟如下:

  1. 線程安全保障 : synchronized (buf) 會鎖定當前的 ByteBuf 實例,防止多個線程同時對同一個 ByteBuf 進行重分配,避免狀態錯亂。

  2. 保存舊內存信息 :記錄下當前 ByteBuf 的舊容量、所屬的 PoolChunk 、句柄 handle 、內存對象 memory 等所有與舊內存位置相關的信息。

  3. 分配新內存 :調用 allocate(parent.threadCache(), buf, newCapacity) 方法,從內存池中申請一塊新的內存。這個調用會更新 buf 對象內部的字段(如 chunk , handle , memory 等),使其指向新的內存位置。

  4. 數據拷貝 :調用 memoryCopy(...) 方法,將舊內存中的數據拷貝到新分配的內存中。拷貝的字節數是新舊容量中較小的那一個,以防止越界。

  5. 釋放舊內存 :調用 free(...) 方法,將之前保存的舊內存塊信息傳入,把舊內存歸還給內存池,以便后續復用。

HeapArena?(堆內存)

  • lastDestroyedChunk:?HeapArena?會嘗試緩存最后一個被銷毀的?PoolChunk<byte[]>。當需要創建新的?PoolChunk?時,如果參數匹配,會復用這個緩存的?PoolChunk,避免重新分配?byte[]?數組的開銷。
  • newByteArray(int size): 使用?PlatformDependent.allocateUninitializedArray(size)?來分配字節數組,這可能比?new byte[size]?更高效,因為它可能不會對數組內容進行零初始化(取決于 JVM 實現和?-XX:+AlwaysZeroTLAB?等標志)。
  • newChunk(...): 嘗試從?lastDestroyedChunk?復用,否則創建新的?PoolChunk,其內存是新分配的?byte[]
  • newUnpooledChunk(...): 創建新的?PoolChunk,其內存是新分配的?byte[]
  • destroyChunk(...): 如果是池化的?PoolChunk?并且?lastDestroyedChunk?為空,則將其緩存起來。否則依賴 GC 回收?byte[]
  • newByteBuf(...): 根據?PlatformDependent.hasUnsafe()?的結果,創建?PooledUnsafeHeapByteBuf?或?PooledHeapByteBuf
  • memoryCopy(...): 使用?System.arraycopy?進行內存拷貝。

HeapArena 是 Netty 高性能內存池在堆內存管理上的具體體現。它通過繼承 PoolArena 的通用分級管理框架(PoolChunkListPoolSubpage),并結合線程緩存(PoolThreadCache)機制,實現了高效的內存分配與回收。

其針對堆內存的特有實現,如復用 PoolChunk 對象和使用 System.arraycopy,進一步優化了性能,有效降低了 GC 壓力和內存碎片,是 Netty 實現高性能網絡通信的重要基石之一。

HeapArena 繼承自 PoolArena,因此其核心結構與 PoolArena 保持一致,主要包括:

  • ??smallSubpagePools??:PoolSubpage<byte[]>[] 數組,用于管理小規格內存(小于 pageSize)的分配。每個 PoolSubpage 內部通過位圖(bitmap)來跟蹤更小內存塊(element)的分配狀態,實現了對小內存的高效利用。
  • ??PoolChunkList 鏈表??(qInitq000q025q050q075q100):這些鏈表用于管理不同內存使用率區間的 PoolChunkPoolChunk 是內存池的基本分配單元(默認為 16MB)。通過將 PoolChunk 按使用率分組,可以快速找到合適的 Chunk 進行內存分配,實現了分級管理。
  • ??SizeClasses??:一個用于規格化請求容量的工具類,它將任意大小的內存請求映射到預定義的規格(size index),并提供相關的計算方法。
  • ??鎖機制??:使用 ReentrantLock 來保證在多線程環境下對 Arena 內部數據結構訪問的線程安全。

內存分配 (allocate)

HeapArena 作為 PoolArena 的子類,重寫了幾個關鍵的抽象方法,以適配堆內存的特性:

  • ??isDirect()??:返回 false,表明它管理的是非直接內存(堆內存)。
  • ??newChunk(...)??:創建一個新的 PoolChunk<byte[]>。值得注意的是,這里有一個優化:它會嘗試復用一個最近被銷毀的 PoolChunk(通過 lastDestroyedChunk 字段)。如果復用失敗,則通過 newByteArray(chunkSize) 創建一個新的 byte[] 數組作為 PoolChunk 的底層內存。
        private final AtomicReference<PoolChunk<byte[]>> lastDestroyedChunk;protected PoolChunk<byte[]> newChunk(int pageSize, int maxPageIdx, int pageShifts, int chunkSize) {PoolChunk<byte[]> chunk = lastDestroyedChunk.getAndSet(null);if (chunk != null) {assert chunk.chunkSize == chunkSize &&chunk.pageSize == pageSize &&chunk.maxPageIdx == maxPageIdx &&chunk.pageShifts == pageShifts;return chunk; // The parameters are always the same, so it's fine to reuse a previously allocated chunk.}return new PoolChunk<byte[]>(this, null, null, newByteArray(chunkSize), pageSize, pageShifts, chunkSize, maxPageIdx);}
  • ??newUnpooledChunk(...)??:創建一個用于大內存分配的非池化 PoolChunk,底層同樣是 byte[]
  • ??newByteArray(...)??:內部調用 PlatformDependent.allocateUninitializedArray(size) 來分配 byte[] 數組。使用 allocateUninitializedArray 可以避免數組初始化時的額外開銷。
  • ??destroyChunk(...)??:銷毀一個 PoolChunk。對于堆內存,銷毀操作實際上依賴于 GC。但為了性能,HeapArena 會嘗試緩存最后一個被銷毀的 ChunklastDestroyedChunk.set(chunk)),以便在下次創建新 Chunk 時可以復用。
  • ??newByteBuf(...)??:根據 PlatformDependent.hasUnsafe() 的結果,創建 PooledUnsafeHeapByteBufPooledHeapByteBuf 實例。
  • ??memoryCopy(...)??:使用 System.arraycopy 來實現內存復制,這是針對 byte[] 數組最高效的方式。

DirectArena

??DirectArena?? 是 PoolArena 的一個靜態內部類,專門用于管理堆外內存(Direct Memory),其管理的內存類型是 java.nio.ByteBuffer。它繼承了 PoolArena<ByteBuffer>,復用了 PoolArena 中通用的內存池管理算法(如伙伴算法的變體、多層級的 PoolChunkList 管理等),但對直接內存的分配、釋放和操作等關鍵部分提供了專門的實現。


類的定義和構造函數

static final class DirectArena extends PoolArena<ByteBuffer> {DirectArena(PooledByteBufAllocator parent, SizeClasses sizeClass) {super(parent, sizeClass);}
}
  • ??extends PoolArena<ByteBuffer>??
    泛型參數 <ByteBuffer> 表明這個 Arena 管理的底層內存是 ByteBuffer 對象,與管理 byte[]HeapArena 形成對比。
  • ??static final??
    static 意味著 DirectArena 的實例不依賴于外部 PoolArena 的實例;final 表示它不能被繼承。


內存塊的創建(newChunknewUnpooledChunk

@Override
protected PoolChunk<ByteBuffer> newChunk(int pageSize, int maxPageIdx, int pageShifts, int chunkSize) {if (sizeClass.directMemoryCacheAlignment == 0) {CleanableDirectBuffer cleanableDirectBuffer = allocateDirect(chunkSize);return new PoolChunk<ByteBuffer>(this, cleanableDirectBuffer, memory, memory, pageSize, pageShifts, chunkSize, maxPageIdx);}CleanableDirectBuffer cleanableDirectBuffer = allocateDirect(chunkSize + sizeClass.directMemoryCacheAlignment);final ByteBuffer memory = PlatformDependent.alignDirectBuffer(base, sizeClass.directMemoryCacheAlignment);return new PoolChunk<ByteBuffer>(this, cleanableDirectBuffer, base, memory, pageSize, pageShifts, chunkSize, maxPageIdx);
}
  • ??核心功能??
    HeapArena 使用 new byte[chunkSize] 不同,DirectArena 通過 PlatformDependent.allocateDirect(capacity) 分配堆外內存(通常調用 ByteBuffer.allocateDirect())。
  • ??內存對齊(directMemoryCacheAlignment)??
    directMemoryCacheAlignment > 0,會分配稍大的內存并通過 PlatformDependent.alignDirectBuffer() 對齊地址,優化 CPU 緩存行利用,防止偽共享(False Sharing)。
  • ??CleanableDirectBuffer??
    包裝分配的內存,利用 Java 9+ 的 Cleaner(或舊版類似機制)確保 PoolChunkByteBuffer 被垃圾回收時,底層堆外內存可靠釋放,防止泄漏。

內存塊的銷毀和創建

@Override
protected void destroyChunk(PoolChunk<ByteBuffer> chunk) {chunk.cleanable.clean();
}
  • ??與 HeapArena 的區別??
    HeapArena 會緩存最后銷毀的 PoolChunk 以供復用(lastDestroyedChunk),而 DirectArena 直接調用 chunk.cleanable.clean() 立即釋放內存。

理解為什么可以“銷毀”,我們需要知道 destroyChunk 是在什么條件下被觸發的。

  • 當一個 PoolChunk 內的所有內存都被釋放后,它的使用率會降為 0% ( usage() == 0 )。此時, PoolChunkList 的 free 方法會嘗試將這個完全空閑的 chunk 移動到前一個 PoolChunkList (即使用率更低的 List )。
  • 然而,對于管理著 0%-25% 使用率的 qInit 這個 PoolChunkList 來說,它的 prevList 是 null 。當它試圖移動一個使用率為 0 的 chunk 時, move0 方法會因為 prevList == null 而返回 false 。這個 false 返回值會一路傳遞,最終導致 PoolArena 調用 destroyChunk 。

因此 當destroyChunk 被調用,意味著這個 PoolChunk 已經完全變空了 。它不再服務于任何內存分配。


ByteBuf 的創建(newByteBuf

@Override
protected PooledByteBuf<ByteBuffer> newByteBuf(int maxCapacity) {if (HAS_UNSAFE) {return PooledUnsafeDirectByteBuf.newInstance(maxCapacity);} else {return PooledDirectByteBuf.newInstance(maxCapacity);}
}
  • ??創建策略??
    根據 PlatformDependent.hasUnsafe() 結果選擇實現:
    • ??PooledUnsafeDirectByteBuf??(支持 sun.misc.Unsafe):使用 Unsafe API 直接操作內存地址,性能最高。
    • ??PooledDirectByteBuf??(不支持 Unsafe):回退到標準 ByteBuffer API(get/put),兼容性更好。

內存復制(memoryCopy

@Override
protected void memoryCopy(ByteBuffer src, int srcOffset, PooledByteBuf<ByteBuffer> dstBuf, int length) {if (HAS_UNSAFE) {PlatformDependent.copyMemory(PlatformDependent.directBufferAddress(src) + srcOffset,PlatformDependent.directBufferAddress(dstBuf.memory) + dstBuf.offset, length);} else {src.position(srcOffset).limit(srcOffset + length);dst.position(dstBuf.offset);dst.put(src);}
}
  • ??高性能復制??
    • 支持 Unsafe 時調用 PlatformDependent.copyMemory(底層為 Unsafe.copyMemory),實現高效內存塊復制。
    • 否則回退到 ByteBuffer.put(ByteBuffer) 的標準方法。

度量信息 (Metrics)

PoolArena?實現了?PoolArenaMetric?接口,提供了大量關于內存池狀態的度量信息,例如:

  • numThreadCaches(): 使用此 Arena 的線程緩存數量。
  • numSmallSubpages(),?numChunkLists(): Subpage 和 ChunkList 的數量。
  • smallSubpages(),?chunkLists(): 返回 Subpage 和 ChunkList 的度量信息列表。
  • numAllocations(),?numDeallocations(): 總的分配和釋放次數。
  • numSmallAllocations(),?numNormalAllocations(),?numHugeAllocations(): 不同類型的分配次數。
  • numChunkAllocations(),?numChunkDeallocations(): Chunk 的分配和釋放次數。
  • numActiveAllocations(),?numActiveSmallAllocations(), etc.: 當前活躍的分配數量。
  • numActiveBytes(): 當前活躍的總字節數。
  • numPinnedBytes(): 當前被固定的字節數(用于直接 I/O 等)。

這些方法大多通過讀取內部的計數器或遍歷?chunkListMetrics?來獲取數據。訪問某些計數器(如?allocationsNormal)時會加鎖。

總結

PoolArena?是 Netty jemalloc 風格內存池的核心,它通過管理?PoolChunk?和?PoolSubpage?來高效地分配和釋放內存。

  • 分級管理: 將內存請求分為 Small, Normal, Huge 三種類型,采用不同的分配策略。
  • Chunk 列表: 使用多個?PoolChunkList?(qInit, q000, q025, q050, q075, q100) 根據?PoolChunk?的使用率對其進行組織,優化分配查找。
  • Subpage 池: 對 Small 類型的分配,使用?PoolSubpage?進一步細化內存管理,減少碎片。
  • 線程緩存: 與?PoolThreadCache?配合,為每個線程提供本地緩存,極大減少了對?PoolArena?全局鎖的競爭。
  • 同步: 使用?ReentrantLock?保護 Arena 級別的共享數據,使用?PoolSubpage?自身的鎖保護其內部鏈表,使用?synchronized(buf)?保護?reallocate?操作。
  • 堆外/堆內支持: 通過?HeapArena?和?DirectArena?子類分別支持堆內存和直接內存的池化。
  • 度量: 提供豐富的度量信息,方便監控和調優。

PoolArena?的設計體現了 Netty 在高性能網絡編程中對內存管理的極致追求,通過精細化的管理和多層次的緩存來提高內存分配效率并減少GC壓力。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/88269.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/88269.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/88269.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

echarts-for-react 日歷熱力圖渲染導致白屏 踩坑記錄

先說結果&#xff0c;補上了一行tooltip.trigger后能正常渲染了。 報錯情況&#xff1a; 在頁面中添加了一個日歷熱力圖后&#xff0c;一渲染它就白屏&#xff0c;控制臺報錯如下&#xff1a; echarts-for-react版本是當前最新的3.0.2&#xff0c;嘗試debug但沒看懂源碼這里是…

SpringBoot項目啟動時自動加載數據到Redis的完整實現方案,用于存儲字典,定時任務,登錄用戶等

一、基礎配置 ?在pom.xml中添加必要依賴&#xff1a; <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency><groupId>com.baomi…

python:使用 OpenAI CLIP 模型進行圖像與文本的語義匹配,并用彩虹色帶可視化 CLIP 模型的相似度矩陣

作者&#xff1a;CSDN _養樂多_ 本文將介紹如何使用 OpenAI 的 CLIP 模型來實現圖像與文本之間的語義匹配。代碼使用 Python 語言&#xff0c;加載多個圖像與類別文本&#xff0c;并通過計算余弦相似度判斷每張圖片最匹配的文本標簽。 結果如下圖所示&#xff0c; 文章目錄 …

微服務鏈路追蹤在生產環境問題定位中的實戰經驗

微服務鏈路追蹤在生產環境問題定位中的實戰經驗 在當今復雜的系統架構中&#xff0c;微服務之間相互調用形成的鏈路往往變得極其復雜。一旦出現問題&#xff0c;僅憑日志和監控信息常常難以迅速定位根因。鏈路追蹤技術因此成為生產環境中不可或缺的工具&#xff0c;能夠幫助我…

正點原子——直流無刷電機-霍爾傳感基本實現流程

直流無刷電機-霍爾傳感實現流程 初始化TIM以及IO 霍爾狀態讀取函數 uint32_t hallsersor(void) {uint32_t state 0;if(HAL_GPIO_ReadPin(HALL1_TIM_CH1_GPIO,HALL_TIM_CH1_PIN)!RESET){state |0x01;}if(HAL_GPIO_ReadPin(HALL1_TIM_CH2_GPIO,HALL_TIM_CH2_PIN)!RESET){stat…

小白的進階之路系列之十七----人工智能從初步到精通pytorch綜合運用的講解第十部分

NLP 從零開始:使用字符級 RNN 生成姓名 這是我們“NLP 從零開始”系列三部分教程中的第二部分。在第一個教程中,我們使用了 RNN 將姓名分類到其語言來源。這次我們將反過來,從語言生成姓名。 > python sample.py Russian RUS Rovakov Uantov Shavakov> python sampl…

思辨場域丨AR技術如何重塑未來學術會議體驗?

毫無疑問&#xff0c;增強現實&#xff08;AR&#xff09;已成為科技浪潮中最澎湃的浪花之一。當Pokemon Go點燃全球熱情&#xff0c;我們首次大規模體驗到數字精靈與現實街景的奇妙交融。這不僅是游戲革命&#xff0c;更是一個強烈的信號&#xff1a;虛實共生的交互時代已轟然…

醫學數據分析實戰:冠心病發病因素可視化

一、數據加載與基本信息檢查 #例9.5 import numpy as np import pandas as pd import matplotlib.pyplot as plt import seaborn as sns import warnings#引入第三方庫plt.rcParams[font.sans-serif]=[SimHei] #用來正常顯示中文標簽 warnings.filterwarnings(ignore) #防止…

Proteus8.0 打開前期版本的操作方法

1.打開Proteus8.0 2. 打開菜單File->Import legacy Project 3.在Import Legacy Project 點擊Browse按鈕 4.選擇要打開的例子目錄 5.打開*.dsn文件 6.點擊Import 7.導入成功 點擊運行即可

【innovus基礎】- 對某根線單獨route

在某些特殊需求場景&#xff0c;我們可能需要對某些net進行單獨的route&#xff0c;方法如下&#xff1a; 1、打開design browser&#xff0c;選擇對應net&#xff1a;&#xff08;或者使用selectNet命令&#xff09; 2、Route → nano Route → 勾選selectNet only 可以看到…

【Linux】network網絡配置

目錄 1、介紹2、網絡配置【1】查看【2】說明 3、工作流程【1】啟動以太網接口【2】關閉接口時反向執行 4、現代替換方案 1、介紹 linux中network網絡服務的核心配置位于/etc/sysconfig/network-scripts/目錄下。它們共同構成了網絡接口的管理框架&#xff0c;負責處理網絡接口…

深入解析 Taro 項目結構:從入門到精通

在現代前端開發中&#xff0c;跨平臺開發框架變得越來越重要。Taro 作為一款由京東凹凸實驗室推出的多端統一開發框架&#xff0c;支持編譯到微信小程序、支付寶小程序、百度小程序、H5、React Native 等多個平臺&#xff0c;極大地提高了開發效率。然而&#xff0c;要充分發揮…

零基礎開始的網工之路第二十一天------系統安全基線和系統加固

目錄 一、系統安全基線 1、賬戶與認證安全 2、文件與目錄權限 3、SSH服務安全 4、網絡與服務配置 5、日志與審計 6、內核參數加固 7、更新與補丁 8、安全模塊配置 9、SUID/SGID文件檢查 10、默認權限控制&#xff08;umask&#xff09; 二、系統安全加固 1、賬戶與…

Log4j 和 Log4j2的比較

以下是 Log4j&#xff08;通常指 Log4j 1.x&#xff09;與 Log4j2 的核心對比分析&#xff0c;結合架構、性能、功能及適用場景&#xff0c;幫助開發者做出合理選擇&#xff1a; &#x1f4ca; 一、架構與設計 特性Log4j 1.xLog4j2分析架構模型單模塊設計&#xff0c;耦合度高…

說說 Springboot 的啟動流程?

Spring Boot 的啟動流程是一個相對復雜但有序的過程&#xff0c;它涉及多個組件和步驟的協同工作。以下是 Spring Boot 啟動流程的詳細解析&#xff1a; 一、初始化階段 啟動入口 Spring Boot 應用的啟動入口通常是一個包含 main 方法的類&#xff0c;該類上標注了 SpringBoot…

從服務器收到預料之外的響應。此文件可能已被成功上傳。請檢查媒體庫或刷新本頁

如果php.ini已經加入了如下的內容還是報錯 &#xff1a; upload_max_filesize 1024M post_max_size 1024M 那就是因為阿帕奇導致&#xff1a;

10、java語法糖

編譯期處理&#xff1a;語法糖&#xff08;即java編譯器把。java的源碼編譯成。class字節碼的過程中&#xff0c;自動生成和轉換的一些代碼&#xff0c;主要是為了減輕程序員的負擔&#xff0c;算是java編譯器給我們的一個額外福利-給糖吃&#xff09; 默認構造器&#xff1a;…

在Vscode中安裝Sass并配置

在Vscode中安裝Sass并配置 sass簡介安裝Sass插件配置sass插件編寫sass使用Sass sass簡介 Sass&#xff08;Syntactically Awesome Style Sheets,英文官方文檔 &#xff09;是一種CSS預處理器&#xff0c;擴展了CSS的功能并提供了更高效的樣式表編寫方式。它兼容所有CSS版本&am…

深入解析 MySQL 并發控制:讀寫鎖、鎖粒度與高級優化

深入解析 MySQL 并發控制:讀寫鎖、鎖粒度與 InnoDB 實現細節 在高并發數據庫應用中,確保數據一致性的同時最大化性能是永恒的挑戰。MySQL 通過精巧的 鎖機制(Locking) 和 多版本并發控制(MVCC) 來解決這個問題。本文聚焦于鎖機制的核心:讀寫鎖(共享/排他鎖) 和 鎖粒度…

【深度學習加速探秘】Winograd 卷積算法:讓計算效率 “飛” 起來

一、為什么需要 Winograd 卷積算法&#xff1f;從 “卷積計算瓶頸” 說起 在深度學習領域&#xff0c;卷積神經網絡&#xff08;CNN&#xff09;被廣泛應用于圖像識別、目標檢測、語義分割等任務。然而&#xff0c;卷積操作作為 CNN 的核心計算單元&#xff0c;其計算量巨大&a…