第 11 章 直接記憶體
1、直接記憶體概述
直接記憶體
- 不是虛拟機運作時資料區的一部分,也不是《Java虛拟機規範》中定義的記憶體區域。
- 直接記憶體是在Java堆外的、直接向系統申請的記憶體區間。
- 來源于NIO,通過存在堆中的DirectByteBuffer操作Native記憶體
- 通常,通路直接記憶體的速度會優于Java堆。即讀寫性能高。
- 是以出于性能考慮,讀寫頻繁的場合可能會考慮使用直接記憶體。
- Java的NIO庫允許Java程式使用直接記憶體,用于資料緩沖區
代碼示例
- 代碼
/**
* IO NIO (New IO / Non-Blocking IO)
* byte[] / char[] Buffer
* Stream Channel
*
* 檢視直接記憶體的占用與釋放
* @author shkstart [email protected]
* @create 2020 0:22
*/
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 直接操作實體磁盤,省去了中間商賺差價
代碼測試
- 測試代碼:分别使用 BIO 和 NIO 複制大檔案,看看用時差别
/**
* @author shkstart [email protected]
* @create 2020 0:04
*/
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參數值一緻
代碼示例 1
- 代碼
/**
* 本地記憶體的OOM: OutOfMemoryError: Direct buffer memory
*
* @author shkstart [email protected]
* @create 2020 0:09
*/
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
- 異常資訊
"C:\Program Files\Java\jdk1.8.0_144\bin\java" "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2017.3.1\lib\idea_rt.jar=1296:C:\Program Files\JetBrains\IntelliJ IDEA 2017.3.1\bin" -Dfile.encoding=UTF-8 -classpath "C:\Program Files\Java\jdk1.8.0_144\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\rt.jar;C:\Users\Heygo\Desktop\JVMDemo\out\production\chapter11" com.atguigu.java.BufferTest2
180
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 com.atguigu.java.BufferTest2.main(BufferTest2.java:21)
Process finished with exit code 1
複制
代碼示例 2
- 代碼:直接通過 Unsafe 類申請本地記憶體
/**
* -Xmx20m -XX:MaxDirectMemorySize=10m
* @author shkstart [email protected]
* @create 2020 0:36
*/
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 異常
"C:\Program Files\Java\jdk1.8.0_144\bin\java" "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2017.3.1\lib\idea_rt.jar=1362:C:\Program Files\JetBrains\IntelliJ IDEA 2017.3.1\bin" -Dfile.encoding=UTF-8 -classpath "C:\Program Files\Java\jdk1.8.0_144\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\rt.jar;C:\Users\Heygo\Desktop\JVMDemo\out\production\chapter11" com.atguigu.java.MaxDirectMemorySizeTestException in thread "main" java.lang.OutOfMemoryError at sun.misc.Unsafe.allocateMemory(Native Method) at com.atguigu.java.MaxDirectMemorySizeTest.main(MaxDirectMemorySizeTest.java:20)Process finished with exit code 1
複制
JDK8 中元空間直接使用本地記憶體