天天看點

Buffer源碼深入分析BufferMappedByteBufferHaep****BufferDirectByteBuffer

本機環境:

<code>Linux 4.4.0-21-generic #37-Ubuntu SMP Mon Apr 18 18:33:37 UTC 2016 x86_64 x86_64 x86_64 GNU/Linux</code>

Buffer的類圖如下:

除了Boolean,其他基本資料類型都有對應的Buffer,但是隻有ByteBuffer才能和Channel互動。隻有ByteBuffer才能産生Direct的buffer,其他資料類型的Buffer隻能産生Heap類型的Buffer。ByteBuffer可以産生其他資料類型的視圖Buffer,如果ByteBuffer本身是Direct的,則産生的各視圖Buffer也是Direct的。

首選說說JVM是怎麼進行IO操作的。

JVM在需要通過作業系統調用完成IO操作,比如可以通過read系統調用完成檔案的讀取。read的原型是:<code>ssize_t read(int fd,void *buf,size_t nbytes)</code>,和其他的IO系統調用類似,一般需要緩沖區作為其中一個參數,該緩沖區要求是連續的。

Buffer分為Direct和Heap兩類,下面分别說明這兩類buffer。

Heap類型的Buffer存在于JVM的堆上,這部分記憶體的回收與整理和普通的對象一樣。Heap類型的Buffer對象都包含一個對應基本資料類型的數組屬性(比如:final **[] hb),數組才是Heap類型Buffer的底層緩沖區。

但是Heap類型的Buffer不能作為緩沖區參數直接進行系統調用,主要因為下面兩個原因。

JVM在GC時可能會移動緩沖區(複制-整理),緩沖區的位址不固定。

系統調用時,緩沖區需要是連續的,但是數組可能不是連續的(JVM的實作沒要求連續)。

是以使用Heap類型的Buffer進行IO時,JVM需要産生一個臨時Direct類型的Buffer,然後進行資料複制,再使用臨時Direct的Buffer作為參數進行作業系統調用。這造成很低的效率,主要是因為兩個原因:

需要把資料從Heap類型的Buffer裡面複制到臨時建立的Direct的Buffer裡面。

可能産生大量的Buffer對象,進而提高GC的頻率。是以在IO操作時,可以通過重複利用Buffer進行優化。

Direct類型的buffer,不存在于堆上,而是JVM通過malloc直接配置設定的一段連續的記憶體,這部分記憶體成為直接記憶體,JVM進行IO系統調用時使用的是直接記憶體作為緩沖區。

<code>-XX:MaxDirectMemorySize</code>,通過這個配置可以設定允許配置設定的最大直接記憶體的大小(MappedByteBuffer配置設定的記憶體不受此配置影響)。

直接記憶體的回收和堆記憶體的回收不同,如果直接記憶體使用不當,很容易造成OutOfMemoryError。JAVA沒有提供顯示的方法去主動釋放直接記憶體,sun.misc.Unsafe類可以進行直接的底層記憶體操作,通過該類可以主動釋放和管理直接記憶體。同理,也應該重複利用直接記憶體以提高效率。

This is a little bit backwards: By rights MappedByteBuffer should be a subclass of DirectByteBuffer, but to keep the spec clear and simple, and for optimization purposes, it's easier to do it the other way around.This works because DirectByteBuffer is a package-private class.(本段話摘自MappedByteBuffer的源碼)

實際上,MappedByteBuffer屬于映射buffer(自己看看虛拟記憶體),但是DirectByteBuffer隻是說明該部分記憶體是JVM在直接記憶體區配置設定的連續緩沖區,并不一是映射的。也就是說MappedByteBuffer應該是DirectByteBuffer的子類,但是為了友善和優化,把MappedByteBuffer作為了DirectByteBuffer的父類。另外,雖然MappedByteBuffer在邏輯上應該是DirectByteBuffer的子類,而且MappedByteBuffer的記憶體的GC和直接記憶體的GC類似(和堆GC不同),但是配置設定的MappedByteBuffer的大小不受-XX:MaxDirectMemorySize參數影響。

MappedByteBuffer封裝的是記憶體映射檔案操作,也就是隻能進行檔案IO操作。MappedByteBuffer是根據mmap産生的映射緩沖區,這部分緩沖區被映射到對應的檔案頁上,屬于直接記憶體在使用者态,通過MappedByteBuffer可以直接操作映射緩沖區,而這部分緩沖區又被映射到檔案頁上,作業系統通過對應記憶體頁的調入和調出完成檔案的寫入和寫出。

通過<code>FileChannel.map(MapMode mode,long position, long size)</code>得到MappedByteBuffer,下面結合源碼說明MappedByteBuffer的産生過程。

<code>FileChannel.map</code>的源碼:

<code>map0</code>的源碼實作:

雖然<code>FileChannel.map()</code>的zise參數是long,但是size的大小最大為Integer.MAX_VALUE,也就是最大隻能映射最大2G大小的空間。實際上作業系統提供的MMAP可以配置設定更大的空間,但是JAVA限制在2G,ByteBuffer等Buffer也最大隻能配置設定2G大小的緩沖區。

MappedByteBuffer是通過mmap産生得到的緩沖區,這部分緩沖區是由作業系統直接建立和管理的,最後JVM通過unmmap讓作業系統直接釋放這部分記憶體。

下面以ByteBuffer為例,說明Heap類型Buffer的細節。

該類型的Buffer可以通過下面方式産生:

<code>ByteBuffer.allocate(int capacity)</code>

<code>ByteBuffer.wrap(byte[] array)</code>

使用傳入的數組作為底層緩沖區,變更數組會影響緩沖區,變更緩沖區也會影響數組。

<code>ByteBuffer.wrap(byte[] array,int offset, int length)</code>

使用傳入的數組的一部分作為底層緩沖區,變更數組的對應部分會影響緩沖區,變更緩沖區也會影響數組。

DirectByteBuffer隻能通過<code>ByteBuffer.allocateDirect(int capacity)</code> 産生。

<code>ByteBuffer.allocateDirect()</code>源碼如下:

<code>DirectByteBuffer()</code>源碼如下:

<code>unsafe.allocateMemory()</code>的源碼在openjdk/src/openjdk/hotspot/src/share/vm/prims/unsafe.cpp中。具體的源碼如下:

JVM通過malloc配置設定得到連續的緩沖區,這部分緩沖區可以直接作為緩沖區參數進行作業系統調用。

本文轉自  zddnd  51CTO部落格,原文連結:http://blog.51cto.com/13013666/1943055