java之HeapByteBuffer&DirectByteBuffer以及回收DirectByteBuffer

小辣椒 8年前發布 | 33K 次閱讀 Java 日志處理

byte buffer一般在網絡交互過程中java使用得比較多,尤其是以NIO的框架中;

看名字就知道是以字節碼作為緩沖的,先buffer一段,然后flush到終端。

而本文要說的一個重點就是HeapByteBuffer與DirectByteBuffer,以及如何合理使用DirectByteBuffer。

1、 HeapByteBufferDirectByteBuffer ,在原理上,前者可以看出分配的buffer是在heap區域的,其實真正flush到遠程的時候會先拷貝得到直接內存,再做下一步操作(考慮細節還會到OS級別的內核區直接內存),其實發送靜態文件最快速的方法是通過OS級別的 send_file ,只會經過OS一個內核拷貝,而不會來回拷貝;在NIO的框架下,很多框架會采用 DirectByteBuffer 來操作,這樣分配的內存不再是在 java heap 上,而是在 C heap 上,經過性能測試,可以得到非常快速的網絡交互,在大量的網絡交互下,一般速度會比HeapByteBuffer要快速好幾倍。

最基本的情況下

分配HeapByteBuffer的方法是:

ByteBuffer.allocate(int capacity);參數大小為字節的數量

分配DirectByteBuffer的方法是:

ByteBuffer.allocateDirect(int capacity);//可以看到分配內存是通過unsafe.allocateMemory()來實現的,這個unsafe默認情況下java代碼是沒有能力可以調用到的,不過你可以通過反射的手段得到實例進而做操作,當然你需要保證的是程序的穩定性,既然叫unsafe的,就是告訴你這不是安全的,其實并不是不安全,而是交給程序員來操作,它可能會因為程序員的能力而導致不安全,而并非它本身不安全。

由于 HeapByteBufferDirectByteBuffer 類都是default類型的,所以你無法字節訪問到,你只能通過ByteBuffer間接訪問到它,因為JVM不想讓你訪問到它,對了,JVM不想讓你訪問到它肯定就有它不可告人的秘密;后面我們來跟蹤下他的秘密吧。

2、前面說到了,這塊區域不是在java heap上,那么這塊內存的大小是多少呢?默認是一般是64M,可以通過參數: -XX:MaxDirectMemorySize 來控制,你夠牛的話,還可以用代碼控制,呵呵,這里就不多說了。

3、直接內存好,我們為啥不都用直接內存?請注意,這個直接內存的釋放并不是由你控制的,而是由full gc來控制的,直接內存會自己檢測情況而調用system.gc(),但是如果參數中使用了DisableExplicitGC 那么這是個坑了,所以啊,這玩意,設置不設置都是一個坑坑,所以java的優化有沒有絕對的,只有針對實際情況的,針對實際情況需要對系統做一些拆分做不同的優化。

4、那么full gc不觸發,我想自己釋放這部分內存有方法嗎?可以的,在這里沒有什么是不可以的,呵呵!私有屬性我們都任意玩他,還有什么不可以玩的;我們看看它的源碼中 DirectByteBuffer 發現有一個:Cleaner,貌似是用來搞資源回收的,經過查證,的確是,而且又看到這個對象是 sun.misc 開頭的了,此時既驚喜又郁悶,呵呵,只要我能拿到它,我就能有希望消滅掉了;下面第五步我們來做個試驗。

5、因為我們的代碼全是私有的,所以我要訪問它不能直接訪問,我需要通過反射來實現,OK,我知道要調用cleaner()方法來獲取它Cleaner對象,進而通過該對象,執行clean方法;(付: 以下代碼大部分也取自網絡上的一篇copy無數次的代碼,但是那個代碼是有問題的,有問題的部分,我將用紅色標識出來,如果沒有哪條代碼是無法運行的

import java.nio.ByteBuffer;
import sun.nio.ch.DirectBuffer;

public class DirectByteBufferCleaner {

        public static void clean(final ByteBuffer byteBuffer) {
              if (byteBuffer.isDirect()) {
                 ((DirectBuffer)byteBuffer).cleaner().clean();
              }
        }
}

上述類你可以在任何位置建立都可以,這里多謝一樓的回復,以前我的寫法是見到DirectByteBuffer類是Default類型的,因此這個類無法直接引用到,是通過反射去找到cleaner的實例,進而調用內部的clean方法,那樣做麻煩了,其實并不需要那么麻煩,因為DirectByteBuffer implements了DirectBuffer,而DirectBuffer本身是public的,所以通過接口去調用內部的Clear對象來做clean方法。

我們下面來做測試來證明這個程序是有效地回收的:

在任意一個地方寫一段main方法來調用,我這里就直接寫在這個類里面了:

public static void sleep(long i) {
    try {
          Thread.sleep(i);
     }catch(Exception e) {
          /*skip*/
     }
}
public static void main(String []args) throws Exception {
       ByteBuffer buffer = ByteBuffer.allocateDirect(1024 * 1024 * 100);
       System.out.println("start");
       sleep(10000);
       clean(buffer);
       System.out.println("end");
       sleep(10000);
}

這里分配了100M內存,為了將結果看清楚,在執行前,執行后分別看看延遲10s,當然你可以根據你的要求自己改改。請提前將OS的資源管理器打開,看看當前使用的內存是多少,如果你是linux當然是看看free或者用top等命令來看;本地程序我是用windows完成,在運行前機器的內存如下圖所示:

開始運行在輸入start后,但是未輸出end前,內存直接上升將近100m。

在輸入end后發現內存立即降低到2.47m,說明回收是有效的。

此時可以觀察JVM堆的內存,不會有太多的變化,注意:JVM本身啟動后也有一些內存開銷,所以不要將那個開銷和這個綁定在一起;這里之所以一次性申請100m也是為了看清楚過程,其余的可以做實驗玩玩了。

來自: http://www.importnew.com/19191.html

 本文由用戶 小辣椒 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!