天天看點

高并發程式設計系列之Netty記憶體配置設定源碼分析

1.1 初識ByteBuf

ByteBuf是Netty整個結構裡面最為底層的子產品,主要負責把資料從底層I/O讀到ByteBuf,然後傳遞給應用程式,應用程式處理完成之後再把資料封裝成ByteBuf寫回I/O。是以,ByteBuf是直接與底層打交道的一層抽象。相對于Netty其他子產品來說,這部分内容是非常複雜的。筆者會把這部分内容拆解,從不同角度來分析ByteBuf的配置設定和回收。本章主要從記憶體與記憶體管理器的抽象、不同規格大小和不同類别的記憶體的配置設定政策及記憶體的回收過程等内容來展開分析。

1.1.1 ByteBuf的基本結構

我們來看Netty官方對ByteBuf的描述,具體如下。

高并發程式設計系列之Netty記憶體配置設定源碼分析

從上面ByteBuf的結構來看,我們發現ByteBuf有三個非常重要的指針,分别是readerIndex(記錄讀指針的開始位置)、writerIndex(記錄寫指針的開始位置)和capacity(緩沖區的總長度),三者的關系是readerIndex<=writerIndex<=capacity。從0到readerIndex為discardable bytes,表示是無效的;從readerIndex到writerIndex為readable bytes,表示可讀資料區;從writerIndex到capacity為writable bytes,表示這段區間空閑,可以往裡面寫資料。除了這三個指針,ByteBuf裡面其實還有一個指針maxCapacity,它相當于ByteBuf擴容的最大門檻值,相應的代碼如下。

高并發程式設計系列之Netty記憶體配置設定源碼分析

這個maxCapacity指針可以看作是指向capactiy之後的這段區間,當Netty發現writable bytes寫資料超出空間大小時,ByteBuf會提前自動擴容,擴容之後,就有了足夠的空間來寫資料,同時capactiy也會同步更新,maxCapacity就是擴容後capactiy的最大值。

1.1.2 ByteBuf的重要API

我們來看ByteBuf的基本API,主要包括read()、write()、set()、mark()及reset()等方法。我們對ByteBuf最重要的API做一個詳細說明,如下表所示。

高并發程式設計系列之Netty記憶體配置設定源碼分析

在Netty中,ByteBuf的大部分功能是在AbstractByteBuf中實作的。

高并發程式設計系列之Netty記憶體配置設定源碼分析

最重要的幾個屬性readerIndex、writerIndex、markedReaderIndex、markedWriterIndex、maxCapacity被定義在AbstractByteBuf抽象類中,下面來看基本讀寫的骨架代碼實作。例如,幾個基本的判斷讀寫區間的API,具體實作代碼如下。

高并發程式設計系列之Netty記憶體配置設定源碼分析
高并發程式設計系列之Netty記憶體配置設定源碼分析

上面代碼已經介紹了這些API的功能。再來看幾個讀寫操作的API,具體代碼如下。

高并發程式設計系列之Netty記憶體配置設定源碼分析
高并發程式設計系列之Netty記憶體配置設定源碼分析

可以看到,上面代碼中readByte()方法和getByte()方法都調用了一個抽象的getByte()方法,這個方法在AbstractByteBuf的子類中實作。在writeByte()方法中調用了一個抽象的setByte()方法,這個方法同樣也在子類中實作。

1.1.3 ByteBuf的基本分類

AbstractByteBuf有衆多子類,大緻可以從三個次元來進行分類,分别如下。

● Pooled:池化記憶體,就是從預先配置設定好的記憶體空間中提取一段連續記憶體封裝成一個ByteBuf,分給應用程式使用。

● Unsafe:是JDK底層的一個負責I/O操作的對象,可以直接獲得對象的記憶體位址,基于記憶體位址進行讀寫操作。

● Direct:堆外記憶體,直接調用JDK的底層API進行實體記憶體配置設定,不在JVM的堆記憶體中,需要手動釋放。

綜上所述,其實ByteBuf共會有六種組合:Pooled(池化記憶體)和Unpooled(非池化記憶體);Unsafe和非Unsafe;Heap(堆内記憶體)和Direct(堆外記憶體)。下圖是ByteBuf最重要的繼承關系類結構圖,通過命名就能一目了然。

高并發程式設計系列之Netty記憶體配置設定源碼分析

ByteBuf最基本的讀寫API操作在AbstractByteBuf中已經實作了,其衆多子類采用不同的政策來配置設定記憶體空間,下表是對重要的幾個子類的總結。

高并發程式設計系列之Netty記憶體配置設定源碼分析

1.2 ByteBufAllocator記憶體管理器

Netty中記憶體配置設定有一個頂層的抽象就是ByteBufAllocator,負責配置設定所有ByteBuf類型的記憶體。功能其實不是很多,主要有幾個重要的API,如下表所示。

高并發程式設計系列之Netty記憶體配置設定源碼分析

可能小夥伴會有疑問,以上API中為什麼沒有前面提到的8種類型的記憶體配置設定API?下面我們來看ByteBufAllocator的基本實作類AbstractByteBufAllocator,重點分析主要API的基本實作,比如buffer()方法的代碼如下。

高并發程式設計系列之Netty記憶體配置設定源碼分析

我們發現buffer()方法中對是否預設支援directBuffer做了判斷,如果支援則配置設定directBuffer,否則配置設定heapBuffer。

下面分别來看directBuffer()方法和heapBuffer()方法的實作,先來看directBuffer()方法的代碼。

高并發程式設計系列之Netty記憶體配置設定源碼分析

directBuffer()方法有多個重載方法,最終會調用newDirectBuffer()方法,繼續跟進newDirectBuffer()方法的代碼。

高并發程式設計系列之Netty記憶體配置設定源碼分析

我們發現newDirectBuffer()方法其實是一個抽象方法,最終,交給AbstractByteBufAllocator的子類來實作。同理,我們再來看heapBuffer()方法的代碼。

高并發程式設計系列之Netty記憶體配置設定源碼分析

我們發現heapBuffer()方法最終是調用newHeapBuffer()方法,而newHeapBuffer()方法也是抽象方法,具體交給AbstractByteBufAllocator的子類實作。AbstractByteBufAllocator的子類主要有兩個:PooledByteBufAllocator和UnpooledByteBufAllocator。AbstractByteBufAllocator子類實作的類結構圖如下圖所示。

高并發程式設計系列之Netty記憶體配置設定源碼分析

分析到這裡,其實我們還隻知道directBuffer、heapBuffer和Pooled、Unpooled的配置設定規則,那麼Unsafe和非Unsafe是如何判别的呢?其實是Netty自動判别的。如果作業系統底層支援Unsafe那就采用Unsafe讀寫,否則采用非Unsafe讀寫。我們可以從UnpooledByteBufAllocator的源碼中驗證,代碼如下。

高并發程式設計系列之Netty記憶體配置設定源碼分析

我們發現在newHeapBuffer()方法和newDirectBuffer()方法中,配置設定記憶體判斷PlatformDependent是否支援Unsafe,如果支援則建立Unsafe類型的Buffer,否則建立非Unsafe類型的Buffer,由Netty自動判斷。

1.3 非池化記憶體配置設定

1.3.1 堆内記憶體的配置設定

現在來看UnpooledByteBufAllocator的記憶體配置設定原理。首先是heapBuffer的配置設定邏輯,newHeapBuffer()方法的代碼如下。

高并發程式設計系列之Netty記憶體配置設定源碼分析

通過調用PlatformDependent.hasUnsafe()方法來判斷作業系統是否支援Unsafe,如果支援Unsafe則建立UnpooledUnsafeHeapByteBuf類,否則建立UnpooledHeapByteBuf類。我們先進入UnpooledUnsafeHeapByteBuf的構造器看看會進行哪些操作?代碼如下。

高并發程式設計系列之Netty記憶體配置設定源碼分析

我們發現UnpooledUnsafeHeapByteBuf繼承了UnpooledHeapByte,并且在UnpooledUnsafeHeapByteBuf的構造器中直接調用了super()方法,也就是其父類UnpooledHeapByte的構造方法。我們看UnpooledHeapByte的構造器代碼。

高并發程式設計系列之Netty記憶體配置設定源碼分析
高并發程式設計系列之Netty記憶體配置設定源碼分析

其中調用了一個關鍵方法就是setArray()方法。這個方法的功能非常簡單,就是把預設配置設定的數組new byte[initialCapacity]指派給全局變量initialArray數組。

高并發程式設計系列之Netty記憶體配置設定源碼分析

緊接着就是調用setIndex()方法。

高并發程式設計系列之Netty記憶體配置設定源碼分析

最終在setIndex0()方法中初始化readerIndex屬性和writerIndex屬性。

既然UnpooledUnsafeHeapByteBuf和UnpooledHeapByteBuf調用的都是UnpooledHeapByteBuf的構造方法,那麼它們之間到底有什麼差別呢?其實根本差別在于I/O的讀寫,我們分别來看它們的getByte()方法,了解二者的差別。先來看UnpooledHeapByteBuf的getByte()方法的實作代碼。

高并發程式設計系列之Netty記憶體配置設定源碼分析

可以看到最終調用的是HeapByteBufUtil的getByte()方法。

高并發程式設計系列之Netty記憶體配置設定源碼分析

getByte()這個方法中的處理邏輯也非常簡單,就是根據index索引直接從數組中取值。接着來看UnpooledUnsafeHeapByteBuf的getByte()方法中的代碼實作。

高并發程式設計系列之Netty記憶體配置設定源碼分析

可以看到,最終調用的是UnsafeByteBufUtil的getByte()方法。

高并發程式設計系列之Netty記憶體配置設定源碼分析
高并發程式設計系列之Netty記憶體配置設定源碼分析

通過這樣對比代碼,我們已經基本了解UnpooledUnsafeHeapByteBuf和UnpooledHeapByteBuf的差別了。

1.3.2 堆外記憶體的配置設定

再回到UnpooledByteBufAllocator的newDirectBuffer()方法,代碼如下。

高并發程式設計系列之Netty記憶體配置設定源碼分析

從上面代碼可以看出,如果支援Unsafe則調用UnsafeByteBufUtil.newUnsafeDirectByteBuf()方法,否則建立UnpooledDirectByteBuf類。來看UnpooledDirectByteBuf構造器。

高并發程式設計系列之Netty記憶體配置設定源碼分析
高并發程式設計系列之Netty記憶體配置設定源碼分析

首先調用ByteBuffer.allocateDirect.allocateDirect()通過JDK底層配置設定一個直接緩沖區,然後傳給setByteBuffer()方法,繼續跟進,代碼如下。

高并發程式設計系列之Netty記憶體配置設定源碼分析

由上述代碼可以看到,setByteBuffer()方法主要做了一次指派。

繼續看UnsafeByteBufUtil.newUnsafeDirectByteBuf()方法的邏輯。

高并發程式設計系列之Netty記憶體配置設定源碼分析

從上面代碼中,我們看到這個方法中傳回了一個UnpooledUnsafeDirectByteBuf對象,關于UnpooledUnsafeNoCleanerDirectByteBuf,我們在後續章節再進行詳細分析。下面我們繼續來看UnpooledUnsafeDirectByteBuf構造器中的代碼。

高并發程式設計系列之Netty記憶體配置設定源碼分析
高并發程式設計系列之Netty記憶體配置設定源碼分析

UnpooledUnsafeDirectByteBuf構造器的邏輯和UnpooledDirectByteBuf構造器的邏輯是相似的,其setByteBuffer()方法的實作代碼如下。

高并發程式設計系列之Netty記憶體配置設定源碼分析

同樣還是先儲存在JDK底層建立的Buffer,接下來有個很重要的操作就是調用PlatformDependent.directBufferAddress()方法擷取Buffer真實的記憶體位址,并儲存到memoryAddress變量中。PlatformDependent.directBufferAddress()方法的實作代碼如下。

高并發程式設計系列之Netty記憶體配置設定源碼分析

PlatformDependent0的directBufferAddress()方法的實作代碼如下。

高并發程式設計系列之Netty記憶體配置設定源碼分析

getLong()方法的實作代碼如下。

高并發程式設計系列之Netty記憶體配置設定源碼分析

可以看到,上述代碼調用了Unsafe的getLong()方法,這是一個native方法。它直接通過Buffer的記憶體位址加上一個偏移量去取資料。到這裡,我們已經基本清楚UnpooledUnsafeDirectByteBuf和UnpooledDirectByteBuf的差別,非Unsafe通過數組的下标取資料,Unsafe直接操作記憶體位址,相對于非Unsafe來說效率當然要更高。

繼續閱讀