第九章-直接記憶體
阿新 • • 發佈:2021-09-05
title: 第十一章 - 直接記憶體
date: 2020-11-30 17:38:11
tags: [jvm, jvm, java]
category: jvm
目錄
1.直接記憶體概述
-
不是虛擬機器執行時資料區的一部分,也不是《Java虛擬機器規範》中定義的記憶體區域。
-
直接記憶體是在Java堆外的、直接向系統申請的記憶體區間。
-
來源於NIO,通過存在堆中的DirectByteBuffer操作Native記憶體
-
通常,訪問直接記憶體的速度會優於Java堆。即讀寫效能高。
- 因此出於效能考慮,讀寫頻繁的場合可能會考慮使用直接記憶體。
- Java的NIO庫允許Java程式使用直接記憶體,用於資料緩衝區
程式碼示例
/** * IO NIO (New IO / Non-Blocking IO) * byte[] / char[] Buffer * Stream Channel * * 檢視直接記憶體的佔用與釋放 */ public class BufferTest { private static final int BUFFER = 1024 * 1024 * 1024; //1GB public static void main(String[] args){ //直接分配本地記憶體空間 ByteBuffer byteBuffer = ByteBuffer.allocateDirect(BUFFER); System.out.println("直接記憶體分配完畢,請求指示!"); Scanner scanner = new Scanner(System.in); scanner.next(); System.out.println("直接記憶體開始釋放!"); byteBuffer = null; System.gc(); scanner.next(); } }
- 吶,直接佔用了 1G 的本地記憶體
- 釋放後,Java程式的記憶體佔用明顯減少
2.BIO 與 NIO
非直接緩衝區(BIO)
原來採用BIO的架構,在讀寫本地檔案時,我們需要從使用者態切換成核心態
直接緩衝區(NIO)
使用NIO時,如下圖。作業系統劃出的直接快取區可以通過mmap間接被Java程式碼直接訪問,只有一份。
NIO適合對大檔案的讀寫操作
程式碼測試
public class BufferTest1 { private static final String TO = "F:\\test\\異界BD中字.mp4"; private static final int _100Mb = 1024 * 1024 * 100; public static void main(String[] args) { long sum = 0; String src = "F:\\test\\異界BD中字.mp4"; for (int i = 0; i < 3; i++) { String dest = "F:\\test\\異界BD中字_" + i + ".mp4"; // sum += io(src,dest); //54606 sum += directBuffer(src, dest); //50244 } System.out.println("總花費的時間為:" + sum); } private static long directBuffer(String src, String dest) { long start = System.currentTimeMillis(); FileChannel inChannel = null; FileChannel outChannel = null; try { inChannel = new FileInputStream(src).getChannel(); outChannel = new FileOutputStream(dest).getChannel(); ByteBuffer byteBuffer = ByteBuffer.allocateDirect(_100Mb); while (inChannel.read(byteBuffer) != -1) { byteBuffer.flip(); //修改為讀資料模式 outChannel.write(byteBuffer); byteBuffer.clear(); //清空 } } catch (IOException e) { e.printStackTrace(); } finally { if (inChannel != null) { try { inChannel.close(); } catch (IOException e) { e.printStackTrace(); } } if (outChannel != null) { try { outChannel.close(); } catch (IOException e) { e.printStackTrace(); } } } long end = System.currentTimeMillis(); return end - start; } private static long io(String src, String dest) { long start = System.currentTimeMillis(); FileInputStream fis = null; FileOutputStream fos = null; try { fis = new FileInputStream(src); fos = new FileOutputStream(dest); byte[] buffer = new byte[_100Mb]; while (true) { int len = fis.read(buffer); if (len == -1) { break; } fos.write(buffer, 0, len); } } catch (IOException e) { e.printStackTrace(); } finally { if (fis != null) { try { fis.close(); } catch (IOException e) { e.printStackTrace(); } } if (fos != null) { try { fos.close(); } catch (IOException e) { e.printStackTrace(); } } } long end = System.currentTimeMillis(); return end - start; } }
深入 ByteBuffer 原始碼
-
ByteBuffer.allocateDirect( ) 方法
public static ByteBuffer allocateDirect(int capacity) { return new DirectByteBuffer(capacity); }
-
DirectByteBuffer 類的構造器用到了 Unsafe 類分配本地記憶體
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; }
3.直接記憶體與OOM
-
直接記憶體也可能導致OutofMemoryError異常
-
由於直接記憶體在Java堆外,因此它的大小不會直接受限於-Xmx指定的最大堆大小,但是系統記憶體是有限的,Java堆和直接記憶體的總和依然受限於作業系統能給出的最大記憶體。
-
直接記憶體的缺點為:
- 分配回收成本較高
- 不受JVM記憶體回收管理
-
直接記憶體大小可以通過MaxDirectMemorySize設定
-
如果不指定,預設與堆的最大值-Xmx引數值一致
程式碼示例
/**
* 本地記憶體的OOM: OutOfMemoryError: Direct buffer memory
*/
public class BufferTest2 {
private static final int BUFFER = 1024 * 1024 * 20; //20MB
public static void main(String[] args) {
ArrayList<ByteBuffer> list = new ArrayList<>();
int count = 0;
try {
while(true){
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(BUFFER);
list.add(byteBuffer);
count++;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} finally {
System.out.println(count);
}
}
}
- 本地記憶體持續增長,直至程式丟擲異常:java.lang.OutOfMemoryError: Direct buffer memory
程式碼示例2
-
直接通過 Unsafe 類申請本地記憶體
/** * -Xmx20m -XX:MaxDirectMemorySize=10m */ public class MaxDirectMemorySizeTest { private static final long _1MB = 1024 * 1024; public static void main(String[] args) throws IllegalAccessException { Field unsafeField = Unsafe.class.getDeclaredFields()[0]; unsafeField.setAccessible(true); Unsafe unsafe = (Unsafe)unsafeField.get(null); while(true){ unsafe.allocateMemory(_1MB); } } }
-
設定JVM 引數
-Xmx20m -XX:MaxDirectMemorySize=10m
-
丟擲 OOM 異常
JDK8 中元空間直接使用本地記憶體
- java程式程序所佔的記憶體空間 = 本地記憶體 + 堆空間