1. 程式人生 > >netty原始碼解解析(4.0)-23 ByteBuf記憶體管理:分配和釋放

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能力。