記憶體結構篇:直接記憶體
阿新 • • 發佈:2020-11-15
一、定義
直接記憶體(Direct Memory)並不是虛擬機器執行時資料區的一部分,也不是《Java虛擬機器規範》中定義的記憶體區域。但是這部分記憶體也被頻繁地使用,而且也可能導致 OutOfMemoryError 異常出現。(即不屬於JVM虛擬機器記憶體區域,屬於作業系統的記憶體)
在 JDK1.4 中新引入了NIO類,它可以使用 Native 函式庫直接分配堆外記憶體,然後通過 Java 堆裡的 DirectByteBuffer 物件作為這塊記憶體的引用進行操作。這樣就能在一些場景中顯著提高效能,因為避免了在 Java 堆(堆記憶體)和 Native 堆(堆外記憶體)中來回複製資料。
- 常見於 NIO 操作時,用於資料緩衝區
- 分配回收成本較高,但讀寫效能高
- 不受 JVM 記憶體回收管理
ByteBuffer.allocateDirect(); //分配直接記憶體空間
static final String FROM = "E:\\test.mp4"; static final String TO = "E:\\a.mp4"; static final int _1Mb = 1024 * 1024; public static void main(String[] args) { io(); // io 用時:1535.586957 1766.963399 1359.240226 directBuffer(); // directBuffer 用時:479.295165 702.291454 562.56592 } private static void directBuffer() { long start = System.nanoTime(); try (FileChannel from = new FileInputStream(FROM).getChannel(); FileChannel to = new FileOutputStream(TO).getChannel(); ) { ByteBuffer bb = ByteBuffer.allocateDirect(_1Mb); //分配一塊直接記憶體,系統和java程式都能訪問 while (true) { int len = from.read(bb); if (len == -1) { break; } bb.flip(); to.write(bb); bb.clear(); } } catch (IOException e) { e.printStackTrace(); } long end = System.nanoTime(); System.out.println("directBuffer 用時:" + (end - start) / 1000_000.0); } private static void io() { long start = System.nanoTime(); try (FileInputStream from = new FileInputStream(FROM); FileOutputStream to = new FileOutputStream(TO); ) { byte[] buf = new byte[_1Mb]; while (true) { int len = from.read(buf); if (len == -1) { break; } to.write(buf, 0, len); } } catch (IOException e) { e.printStackTrace(); } long end = System.nanoTime(); System.out.println("io 用時:" + (end - start) / 1000_000.0); }
不使用直接記憶體進行讀寫:
使用直接記憶體進行讀寫:在作業系統劃出一個緩衝區作為直接記憶體,系統和java程式都能訪問,減少一個緩衝區的複製操作,加快檔案的讀寫
二、分配和回收原理
- 使用了 Unsafe 物件完成直接記憶體的分配回收,並且回收需要主動呼叫 freeMemory 方法
/** * 直接記憶體分配的底層原理:Unsafe */ public class Demo { 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() { try { Field f = Unsafe.class.getDeclaredField("theUnsafe"); f.setAccessible(true); Unsafe unsafe = (Unsafe) f.get(null); return unsafe; } catch (NoSuchFieldException | IllegalAccessException e) { throw new RuntimeException(e); } } }
- ByteBuffer 的實現類內部,使用了 Cleaner (虛引用)來監測 ByteBuffer 物件,一旦
ByteBuffer 物件被垃圾回收,那麼就會由 ReferenceHandler 執行緒通過 Cleaner 的 clean 方法調
用 freeMemory 來釋放直接記憶體
/**
* 禁用顯式回收對直接記憶體的影響
*/
public class Demo1_26 {
static int _1Gb = 1024 * 1024 * 1024;
/*
* -XX:+DisableExplicitGC 禁止顯式的垃圾回收
*/
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(); // 顯式的垃圾回收,Full GC
System.in.read();
}
}