1、定義
(1)常見於NIO操作時,用於數據緩沖區
(2)分配回收成本較高(屬於操作系統內存),但讀寫性能高
(3)不受JVM內存回收管理(依舊存在內存溢出的問題)
2、直接內存基本使用(IO操作舉例)
(1)分為兩步操作:
(2)使用直接內存后,可以減少步驟:
3、直接內存導致的內存溢出問題
書寫程序:每次都分配直接內存,直到內存溢出
public class Test1 { static int _100Mb=1024*1024*100; public static void main(String[] args) { List<ByteBuffer> list=new ArrayList<>(); int i=0; try { while (true){ ByteBuffer byteBuffer=ByteBuffer.allocateDirect(_100Mb); list.add(byteBuffer); i++; } }finally { System.out.println(i); } } }
測試結果:
17 Exception in thread "main" java.lang.OutOfMemoryError: Direct buffer memory at java.nio.Bits.reserveMemory(Bits.java:694) at java.nio.DirectByteBuffer.<init>(DirectByteBuffer.java:123) at java.nio.ByteBuffer.allocateDirect(ByteBuffer.java:311) at pers.zhb.test.Test1.main(Test1.java:15)
4、直接內存的分配與回收(底層通過Unsafe對象管理)
(1)直接內存的分配與回收
運行程序前:
直接內存的分配與釋放程序:
public class Test1 { static int _1Gb=1024*1024*1024; public static void main(String[] args) throws IOException { ByteBuffer byteBuffer=ByteBuffer.allocateDirect(_1Gb); System.out.println("分配完畢"); System.in.read(); System.out.println("開始釋放"); byteBuffer=null; System.gc(); } }
分配直接內存后:
在IDEA的控制台點擊回車對內存進行釋放:
控制台打印出分配與回收的提示:
分配完畢
開始釋放
Process finished with exit code 0
其中System.gc() 回收掉byteBuffer對象
(2)Unsafe實現對直接內存的分配與回收:
public class Test1 { static int _1Gb=1024*1024*1024; public static void main(String[] args) throws IOException { Unsafe unsafe=getUnsafe(); //分配內存 long base=unsafe.allocateMemory(_1Gb); unsafe.setMemory(base,_1Gb,(byte)0); System.in.read(); //釋放內存 unsafe.freeMemory(base); System.in.read(); } public static Unsafe getUnsafe(){ Field field= null; try { field = Unsafe.class.getDeclaredField("theUnsafe"); } catch (NoSuchFieldException e) { e.printStackTrace(); } field.setAccessible(true); Unsafe unsafe= null; try { unsafe = (Unsafe)field.get(null); } catch (IllegalAccessException e) { e.printStackTrace(); } return unsafe; } }
jvm的內存分配與回收是自動的,不需要手動調用任何的方法,但是直接內存需要我們手動調用方法
5、ByteBuffer源碼
(1)ByteBuffer :
ByteBuffer byteBuffer= ByteBuffer.allocateDirect(_1Gb);
(2)allocateDirect:
public static ByteBuffer allocateDirect(int capacity) { return new DirectByteBuffer(capacity); }
(3)DirectByteBuffer
DirectByteBuffer(int cap) { // package-private super(-1, 0, cap, cap); boolean pa = VM.isDirectMemoryPageAligned(); int ps = Bits.pageSize(); long size = Math.max(1L, (long)cap + (pa ? ps : 0)); Bits.reserveMemory(size, cap); long base = 0; try { base = unsafe.allocateMemory(size); } catch (OutOfMemoryError x) { Bits.unreserveMemory(size, cap); throw x; } unsafe.setMemory(base, size, (byte) 0); if (pa && (base % ps != 0)) { // Round up to page boundary address = base + ps - (base & (ps - 1)); } else { address = base; } cleaner = Cleaner.create(this, new Deallocator(base, size, cap)); att = null; }
底層用到的依舊是unsafe對象
6、優缺點
(1)優點
- 減少了垃圾回收
堆外內存是直接受操作系統管理(不是JVM)。這樣做能保持一個較小的堆內內存,以減少垃圾收集對應用的影響。
- 提升IO速度
堆內內存由JVM管理,屬於“用戶態”;而堆外內存由OS管理,屬於“內核態”。如果從堆內向磁盤寫數據時,數據會被先復制到堆外內存,即內核緩沖區,然后再由OS寫入磁盤,使用堆外內存避免了這個操作。
(2)缺點
缺點就是沒有JVM協助管理內存,需要我們自己來管理堆外內存,防止內存溢出。為了避免一直沒有FULL GC,最終導致物理內存被耗完。我們會指定直接內存的最大值,通過-XX:MaxDirectMemerySize來指定,當達到閾值的時候,調用system.gc來進行一次full gc,把那些沒有被使用的直接內存回收掉。