netty原始碼解解析(4.0)-23 ByteBuf記憶體管理:分配和釋放
ByteBuf記憶體分配和釋放由具體實現負責,抽象型別只定義的記憶體分配和釋放的時機。
記憶體分配分兩個階段: 第一階段,初始化時分配記憶體。第二階段: 記憶體不夠用時分配新的記憶體。ByteBuf抽象層沒有定義第一階段的行為,但定義了第二階段的方法:
public abstract ByteBuf capacity(int newCapacity)
這個方法負責分配一個長度為newCapacity的新記憶體。
記憶體釋放的抽象實現在AbstractReferenceCountedByteBuf中實現,這個類實現引用計數,當呼叫release方法把引用計數變成0時,會呼叫
protected abstract void deallocate()
執行真正的記憶體釋放操作。
記憶體相關的屬性
ByteBuf定義了兩個記憶體相關的屬性:
capacity: 當前的當前的容量,也就是當前使用的記憶體大小。使用capacity()方法獲得。
maxCapacity: 最大容量,也就是可以使用的最大記憶體大小。使用newCapacity()方法獲得。
記憶體分配時機
AbstractByteBuf定義了記憶體分配的時機。當writeXX方法被呼叫的時候,如果如果發現可寫空間不足,就呼叫capacity分配新的記憶體。下面以writeInt為例詳細分析這個過程。
1 @Override 2 public ByteBuf writeInt(int value) { 3 ensureWritable0(4); 4 _setInt(writerIndex, value); 5 writerIndex += 4; 6 return this; 7 } 8 9 10 final void ensureWritable0(int minWritableBytes) { 11 ensureAccessible(); 12 if (minWritableBytes <= writableBytes()) { 13 return; 14 } 15 16 if (minWritableBytes > maxCapacity - writerIndex) { 17 throw new IndexOutOfBoundsException(String.format( 18 "writerIndex(%d) + minWritableBytes(%d) exceeds maxCapacity(%d): %s", 19 writerIndex, minWritableBytes, maxCapacity, this)); 20 } 21 22 // Normalize the current capacity to the power of 2. 23 int newCapacity = calculateNewCapacity(writerIndex + minWritableBytes); 24 25 // Adjust to the new capacity. 26 capacity(newCapacity); 27 } 28
3行: ensureWritable確保當前能寫入4Byte的資料。
11行: 確保當前ByteBuf是可以訪問的。防止多執行緒環境下,ByteBuf記憶體被釋放後讀寫資料。
12,13行: 如果記憶體夠用,就此作罷。
16行: 如果需要記憶體大於maxCapacity丟擲異常。
23, 26行行: 計算新記憶體的大小, 呼叫capacity(int)分配新記憶體。
重新分配記憶體之前的一個重要步驟的計算新記憶體的大小。這個工作由calculateNewCapacity方法完成,它的程式碼如下:
1 private int calculateNewCapacity(int minNewCapacity) { 2 final int maxCapacity = this.maxCapacity; 3 final int threshold = 1048576 * 4; // 4 MiB page 4 5 if (minNewCapacity == threshold) { 6 return threshold; 7 } 8 9 // If over threshold, do not double but just increase by threshold. 10 if (minNewCapacity > threshold) { 11 int newCapacity = minNewCapacity / threshold * threshold; 12 if (newCapacity > maxCapacity - threshold) { 13 newCapacity = maxCapacity; 14 } else { 15 newCapacity += threshold; 16 } 17 return newCapacity; 18 } 19 20 // Not over threshold. Double up to 4 MiB, starting from 64. 21 int newCapacity = 64; 22 while (newCapacity < minNewCapacity) { 23 newCapacity <<= 1; 24 } 25 26 return Math.min(newCapacity, maxCapacity); 27 }
1行:接受一個最小的新記憶體引數minNewCapacity。
3行: 定義一個4MB的閾值常量threshold。
5,6行: 如果minNewCapacity==threshold,那麼新記憶體大小就是threshold。
10-17行: 如果minNewCapacity>threshold, 新記憶體大小是min(maxCapacity, threshold * n)且threshold * n >= minNewCapacity。
21-26行: 如果minNewCapacity<threshold, 新記憶體大小是min(maxCapacity, 64 * 2n)且64 * 2n >= minNewCapacity。
記憶體分配和釋放的具體實現
本章涉及到的記憶體分配和釋放的具體實現只涉及到unpooled型別的ByteBuf,即:
UnpooledHeapByteBuf
UnpooledDirectByteBuf
UnpooledUnsafeHeapByteBuf
UnpooledUnsafeDirectByteBuf
這幾個具體實現中涉及到的記憶體分配和釋放程式碼比較簡潔,更容易明白ByteBuf記憶體管理的原理。相比之下,pooled型別的ByteBuf記憶體分配和釋放的程式碼要複雜很多,會在後面的章節獨立分析。
UnpooledHeapByteBuf和UnpooledUnsafeHeapByteBuf實現
UnpooledHeapByteBuf中,記憶體分配的實現程式碼主要集中在capacity(int)和allocateArray()方法中。capacity分配新記憶體的步驟是
- 呼叫allocateArray分配一塊新記憶體。
- 把舊記憶體中的實際複製到新記憶體中。
- 使用新記憶體替換舊記憶體(24行)。
- 釋放掉舊記憶體(25行)。
程式碼如下:
1 @Override 2 public ByteBuf capacity(int newCapacity) { 3 checkNewCapacity(newCapacity); 4 5 int oldCapacity = array.length; 6 byte[] oldArray = array; 7 if (newCapacity > oldCapacity) { 8 byte[] newArray = allocateArray(newCapacity); 9 System.arraycopy(oldArray, 0, newArray, 0, oldArray.length); 10 setArray(newArray); 11 freeArray(oldArray); 12 } else if (newCapacity < oldCapacity) { 13 byte[] newArray = allocateArray(newCapacity); 14 int readerIndex = readerIndex(); 15 if (readerIndex < newCapacity) { 16 int writerIndex = writerIndex(); 17 if (writerIndex > newCapacity) { 18 writerIndex(writerIndex = newCapacity); 19 } 20 System.arraycopy(oldArray, readerIndex, newArray, readerIndex, writerIndex - readerIndex); 21 } else { 22 setIndex(newCapacity, newCapacity); 23 } 24 setArray(newArray); 25 freeArray(oldArray); 26 } 27 return this; 28 } 29
capacity中複製舊記憶體資料到新記憶體中的時候分兩種情況(newCapacity,oldCapacity分別是新舊記憶體的大小):
- newCapacity>oldCapacity,這種情況比較簡單,直接複製就好了(第8行)。不影響readerIndex和writerIndex。
- newCapacity<oldCapacity, 這種情況比較複雜。capacity儘量把可讀的資料複製新記憶體中。
- 如果readerIndex<newCapacity且writerIndex<newCapacity。可讀資料會全部轉移到新記憶體中。readerIndex和writerIndex保持不變。
- 如果readerIndex<newCapacity且writeIndex>newCapacity。可端資料會部分轉移的新記憶體中,會丟失部分可讀資料。readerIndex不變,writerIndex變成newCapacity。
- 如果readerIndex>newCapacity,資料全部丟失,readerIndex和writerIndex都會變成newCapacity。
allocateArray方法負責分配一塊新記憶體,它的實現是new byte[]。freeArray方法負責釋放記憶體,這個方法是個空方法。
UnpooledUnsafeHeapByteBuf是UnloopedHeadpByteBuf的直接子類,在記憶體管理上的差別是allocateArray的實現,UnpooledUnsafeHeapByteBuf的實現是:
1 @Override 2 byte[] allocateArray(int initialCapacity) { 3 return PlatformDependent.allocateUninitializedArray(initialCapacity); 4 }
UnpooledDirectByteBuf和UnpooledUnsafeDirectByteBuf實現
UnpooledDirectByteBuf類使用DirectByteBuffer作為記憶體,使用了DirectByteBuffer的能力來實現ByteBuf介面。allocateDirect和freeDirect方法負責分配和釋放DirectByteBuffer。capacity(int)方法和UnloopedHeapByteBuf類似,使用allocateDirect建立一個新的DirectByteBuffer, 把舊記憶體資料複製到新記憶體中,然後使用新記憶體替換舊記憶體,最後呼叫freeDirect方法釋放掉舊的DirectByteBuffer。
1 protected ByteBuffer allocateDirect(int initialCapacity) { 2 return ByteBuffer.allocateDirect(initialCapacity); 3 } 4 5 protected void freeDirect(ByteBuffer buffer) { 6 PlatformDependent.freeDirectBuffer(buffer); 7 } 8 9 @Override 10 public ByteBuf capacity(int newCapacity) { 11 checkNewCapacity(newCapacity); 12 13 int readerIndex = readerIndex(); 14 int writerIndex = writerIndex(); 15 16 int oldCapacity = capacity; 17 if (newCapacity > oldCapacity) { 18 ByteBuffer oldBuffer = buffer; 19 ByteBuffer newBuffer = allocateDirect(newCapacity); 20 oldBuffer.position(0).limit(oldBuffer.capacity()); 21 newBuffer.position(0).limit(oldBuffer.capacity()); 22 newBuffer.put(oldBuffer); 23 newBuffer.clear(); 24 setByteBuffer(newBuffer); 25 } else if (newCapacity < oldCapacity) { 26 ByteBuffer oldBuffer = buffer; 27 ByteBuffer newBuffer = allocateDirect(newCapacity); 28 if (readerIndex < newCapacity) { 29 if (writerIndex > newCapacity) { 30 writerIndex(writerIndex = newCapacity); 31 } 32 oldBuffer.position(readerIndex).limit(writerIndex); 33 newBuffer.position(readerIndex).limit(writerIndex); 34 newBuffer.put(oldBuffer); 35 newBuffer.clear(); 36 } else { 37 setIndex(newCapacity, newCapacity); 38 } 39 setByteBuffer(newBuffer); 40 } 41 return this; 42 }
對比UnloopedHeapByteBuf的capacity(int)方法,發現這兩個實現非常類似,也分兩種情況處理:
- 18-24行,newCapacity > oldCapacity的情況。
- 26-39行, newCapacity < oldCapacity的情況。
兩種情況對readerIndex和writerIndex的影響也一樣,不同的是資料複製時的具體實現。
UnpooledUnsafeDirectByteBuf和UnpooledDirectByteBuf同屬於AbstractReferenceCountedByteBuf的派生類,它們之間沒有繼承關係。但記憶體分配和釋放實現是一樣的,不同的地方是記憶體I/O。UnpooledUnsafeDirectByteBuf使用UnsafeByteBufUtil類之間讀寫DirectByteBuffer的記憶體,沒有使用DirectByteBuffer的I/O能力。