byte buffer通常在網絡交互過程當中java使用得比較多,尤爲是以NIO的框架中;java
看名字就知道是以字節碼做爲緩衝的,先buffer一段,而後flush到終端。linux
而本文要說的一個重點就是HeapByteBuffer與DirectByteBuffer,以及如何合理使用DirectByteBuffer。程序員
一、HeapByteBuffer與DirectByteBuffer,在原理上,前者能夠看出分配的buffer是在heap區域的,其實真正flush到遠程的時候會先拷貝獲得直接內存,再作下一步操做(考慮細節還會到OS級別的內核區直接內存),其實發送靜態文件最快速的方法是經過OS級別的send_file,只會通過OS一個內核拷貝,而不會來回拷貝;在NIO的框架下,不少框架會採用DirectByteBuffer來操做,這樣分配的內存再也不是在java heap上,而是在C heap上,通過性能測試,能夠獲得很是快速的網絡交互,在大量的網絡交互下,通常速度會比HeapByteBuffer要快速好幾倍。windows
最基本的狀況下安全
分配HeapByteBuffer的方法是:網絡
[java] view plaincopy框架
ByteBuffer.allocate(int capacity);參數大小爲字節的數量 性能
分配DirectByteBuffer的方法是:測試
[java] view plaincopy優化
ByteBuffer.allocateDirect(int capacity);//能夠看到分配內存是經過unsafe.allocateMemory()來實現的,這個unsafe默認狀況下java代碼是沒有能力能夠調用到的,不過你能夠經過反射的手段獲得實例進而作操做,固然你須要保證的是程序的穩定性,既然叫unsafe的,就是告訴你這不是安全的,其實並非不安全,而是交給程序員來操做,它可能會由於程序員的能力而致使不安全,而並不是它自己不安全。
因爲HeapByteBuffer和DirectByteBuffer類都是default類型的,因此你沒法字節訪問到,你只能經過ByteBuffer間接訪問到它,由於JVM不想讓你訪問到它,對了,JVM不想讓你訪問到它確定就有它不可告人的祕密;後面咱們來跟蹤下他的祕密吧。
二、前面說到了,這塊區域不是在java heap上,那麼這塊內存的大小是多少呢?默認是通常是64M,能夠經過參數:-XX:MaxDirectMemorySize來控制,你夠牛的話,還能夠用代碼控制,呵呵,這裏就很少說了。
三、直接內存好,咱們爲啥不都用直接內存?請注意,這個直接內存的釋放並非由你控制的,而是由full gc來控制的,直接內存會本身檢測狀況而調用system.gc(),可是若是參數中使用了DisableExplicitGC 那麼這是個坑了,因此啊,這玩意,設置不設置都是一個坑坑,因此java的優化有沒有絕對的,只有針對實際狀況的,針對實際狀況須要對系統作一些拆分作不一樣的優化。
四、那麼full gc不觸發,我想本身釋放這部份內存有方法嗎?能夠的,在這裏沒有什麼是不能夠的,呵呵!私有屬性咱們都任意玩他,還有什麼不能夠玩的;咱們看看它的源碼中DirectByteBuffer發現有一個:Cleaner,貌似是用來搞資源回收的,通過查證,的確是,並且又看到這個對象是sun.misc開頭的了,此時既驚喜又鬱悶,呵呵,只要我能拿到它,我就能有但願消滅掉了;下面第五步咱們來作個試驗。
五、由於咱們的代碼全是私有的,因此我要訪問它不能直接訪問,我須要經過反射來實現,OK,我知道要調用cleaner()方法來獲取它Cleaner對象,進而經過該對象,執行clean方法;(付:如下代碼大部分也取自網絡上的一篇copy無數次的代碼,可是那個代碼是有問題的,有問題的部分,我將用紅色標識出來,若是沒有哪條代碼是沒法運行的)
[java] view plaincopy
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方法來調用,我這裏就直接寫在這個類裏面了:
[java] view plaincopy
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也是爲了看清楚過程,其他的能夠作實驗玩玩了。