寫NIO程序經常使用ByteBuffer來讀取或者寫入數據,那麼使用ByteBuffer.allocate(capability)還是使用ByteBuffer.allocteDirect(capability)來分配緩存了?第一種方式是分配JVM堆內存,屬於GC管轄範圍,由於需要拷貝所以速度相對較慢;第二種方式是分配OS本地內存,不屬於GC管轄範圍,由於不需要內存拷貝所以速度相對較快。
我們肯定想選擇比較快的,但問題是直接內存不屬於GC管轄範圍,需要弄清楚這部分內存如何管理,否則造成內存泄露就麻煩了。本地內存在JAVA中有一個對應的包裝類DirectByteBuffer,該類屬於Java類,適當的時候會被GC回收,當它被回收前會調用本地方法把直接內存給釋放了,所以本地內存可以隨DirectByteBuffer對象被回收而自動回收,貌似沒有問題;但如果不斷分配本地內存,堆內存很少使用,那麼JVM就不需要執行GC,DirectByteBuffer對象們就不會被回收,這時候堆內存充足,但本地內存可能已經使用光了,再次嘗試分配本地內存就會出現OutOfMemoryError,那程序就直接崩潰了。
有沒有解決方案?自動釋放不靠譜,我們是否可以手動釋放本地內存,把握主動權?果然DirectByteBuffer持有一個Cleaner對象,該對象有一個clean()方法可用於釋放本地內存,所以需要的時候我們可以調用這個方法手動釋放本地內存。
以下代碼與測試場景幫助理解與證實以上描述。
代碼1:
package com.stevex.app.nio; import java.nio.ByteBuffer; import java.util.concurrent.TimeUnit; public class DirectByteBufferTest { public static void main(String[] args) throws InterruptedException{ //分配128MB直接內存 ByteBuffer bb = ByteBuffer.allocateDirect(1024*1024*128); TimeUnit.SECONDS.sleep(10); System.out.println("ok"); } }
測試用例1:設置JVM參數-Xmx100m,運行異常,因爲如果沒設置-XX:MaxDirectMemorySize,則默認與-Xmx參數值相同,分配128M直接內存超出限制範圍。
Exception in thread "main" java.lang.OutOfMemoryError: Direct buffer memory at java.nio.Bits.reserveMemory(Bits.java:658) at java.nio.DirectByteBuffer.<init>(DirectByteBuffer.java:123) at java.nio.ByteBuffer.allocateDirect(ByteBuffer.java:306) at com.stevex.app.nio.DirectByteBufferTest.main(DirectByteBufferTest.java:8)
測試用例2:設置JVM參數-Xmx256m,運行正常,因爲128M小於256M,屬於範圍內分配。
ok
測試用例3:設置JVM參數-Xmx256m -XX:MaxDirectMemorySize=100M,運行異常,分配的直接內存128M超過限定的100M。
Exception in thread "main" java.lang.OutOfMemoryError: Direct buffer memory at java.nio.Bits.reserveMemory(Bits.java:658) at java.nio.DirectByteBuffer.<init>(DirectByteBuffer.java:123) at java.nio.ByteBuffer.allocateDirect(ByteBuffer.java:306) at com.stevex.app.nio.DirectByteBufferTest.main(DirectByteBufferTest.java:8)
代碼2:
package com.stevex.app.nio; import java.nio.ByteBuffer; import java.util.concurrent.TimeUnit; import sun.nio.ch.DirectBuffer; public class DirectByteBufferTest { public static void main(String[] args) throws InterruptedException{ //分配512MB直接緩存 ByteBuffer bb = ByteBuffer.allocateDirect(1024*1024*512); TimeUnit.SECONDS.sleep(10); //清除直接緩存 ((DirectBuffer)bb).cleaner().clean(); TimeUnit.SECONDS.sleep(10); System.out.println("ok"); } }
測試用例4:設置JVM參數-Xmx768m,運行程序觀察內存使用變化,會發現clean()後內存馬上下降,說明使用clean()方法能有效及時回收直接緩存。