一、直接内存概述
直接内存
-
不是虚拟机运行时数据区的一部分,也不是《Java虚拟机规范》中定义的内存区域。
-
直接内存是在Java堆外的、直接向系统申请的内存区间。
-
来源于NIO,通过存在堆中的DirectByteBuffer操作Native内存
-
通常,访问直接内存的速度会优于Java堆。即读写性能高。
-
因此出于性能考虑,读写频繁的场合可能会考虑使用直接内存。
-
Java的NIO库允许Java程序使用直接内存,用于数据缓冲区
-
示例代码
1 public class BufferTest { 2 private static final int BUFFER = 1024 * 1024 * 1024;//1GB 3 4 public static void main(String[] args){ 5 //直接分配本地内存空间 6 ByteBuffer byteBuffer = ByteBuffer.allocateDirect(BUFFER); 7 System.out.println("直接内存分配完毕,请求指示!"); 8 9 Scanner scanner = new Scanner(System.in); 10 scanner.next(); 11 12 System.out.println("直接内存开始释放!"); 13 byteBuffer = null; 14 System.gc(); 15 scanner.next(); 16 } 17 }
-
直接占用了 1G 的本地内存
- 释放后,Java程序的内存占用明显减少,可以通过任务管理器查看内存变化
二、直接缓冲区(NIO)
关于NIO可以参考:【Java】Java NIO 概览(一)
- 原来采用BIO的架构,在读写本地文件时,我们需要从用户态切换成内核态
- NIO 直接操作物理磁盘,省去了中间商赚差价
- 测试代码:分别使用 BIO 和 NIO 复制大文件,看看用时差别
1 public class BufferTest1 { 2 3 private static final String TO = "F:\test\异界BD中字.mp4"; 4 private static final int _100Mb = 1024 * 1024 * 100; 5 6 public static void main(String[] args) { 7 long sum = 0; 8 String src = "F:\test\异界BD中字.mp4"; 9 for (int i = 0; i < 3; i++) { 10 String dest = "F:\test\异界BD中字_" + i + ".mp4"; 11 // sum += io(src,dest);//54606 12 sum += directBuffer(src, dest);//50244 13 } 14 15 System.out.println("总花费的时间为:" + sum); 16 } 17 18 private static long directBuffer(String src, String dest) { 19 long start = System.currentTimeMillis(); 20 21 FileChannel inChannel = null; 22 FileChannel outChannel = null; 23 try { 24 inChannel = new FileInputStream(src).getChannel(); 25 outChannel = new FileOutputStream(dest).getChannel(); 26 27 ByteBuffer byteBuffer = ByteBuffer.allocateDirect(_100Mb); 28 while (inChannel.read(byteBuffer) != -1) { 29 byteBuffer.flip();//修改为读数据模式 30 outChannel.write(byteBuffer); 31 byteBuffer.clear();//清空 32 } 33 } catch (IOException e) { 34 e.printStackTrace(); 35 } finally { 36 if (inChannel != null) { 37 try { 38 inChannel.close(); 39 } catch (IOException e) { 40 e.printStackTrace(); 41 } 42 43 } 44 if (outChannel != null) { 45 try { 46 outChannel.close(); 47 } catch (IOException e) { 48 e.printStackTrace(); 49 } 50 51 } 52 } 53 54 long end = System.currentTimeMillis(); 55 return end - start; 56 57 } 58 59 private static long io(String src, String dest) { 60 long start = System.currentTimeMillis(); 61 62 FileInputStream fis = null; 63 FileOutputStream fos = null; 64 try { 65 fis = new FileInputStream(src); 66 fos = new FileOutputStream(dest); 67 byte[] buffer = new byte[_100Mb]; 68 while (true) { 69 int len = fis.read(buffer); 70 if (len == -1) { 71 break; 72 } 73 fos.write(buffer, 0, len); 74 } 75 } catch (IOException e) { 76 e.printStackTrace(); 77 } finally { 78 if (fis != null) { 79 try { 80 fis.close(); 81 } catch (IOException e) { 82 e.printStackTrace(); 83 } 84 85 } 86 if (fos != null) { 87 try { 88 fos.close(); 89 } catch (IOException e) { 90 e.printStackTrace(); 91 } 92 93 } 94 } 95 96 97 long end = System.currentTimeMillis(); 98 99 return end - start; 100 } 101 }
-
深入 ByteBuffer 源码
-
ByteBuffer.allocateDirect() 方法
1 public static ByteBuffer allocateDirect(int capacity) { 2 return new DirectByteBuffer(capacity); 3 }
-
DirectByteBuffer 类的构造器用到了 Unsafe 类分配本地内存
1 DirectByteBuffer(int cap) { // package-private 2 3 super(-1, 0, cap, cap); 4 boolean pa = VM.isDirectMemoryPageAligned(); 5 int ps = Bits.pageSize(); 6 long size = Math.max(1L, (long)cap + (pa ? ps : 0)); 7 Bits.reserveMemory(size, cap); 8 9 long base = 0; 10 try { 11 base = unsafe.allocateMemory(size); 12 } catch (OutOfMemoryError x) { 13 Bits.unreserveMemory(size, cap); 14 throw x; 15 } 16 unsafe.setMemory(base, size, (byte) 0); 17 if (pa && (base % ps != 0)) { 18 // Round up to page boundary 19 address = base + ps - (base & (ps - 1)); 20 } else { 21 address = base; 22 } 23 cleaner = Cleaner.create(this, new Deallocator(base, size, cap)); 24 att = null; 25 26 27 28 }
-
三、直接内存与 OOM
-
直接内存也可能导致OutofMemoryError异常
-
由于直接内存在Java堆外,因此它的大小不会直接受限于-Xmx指定的最大堆大小,但是系统内存是有限的,Java堆和直接内存的总和依然受限于操作系统能给出的最大内存。
-
直接内存的缺点为:
- 分配回收成本较高
- 不受JVM内存回收管理
-
直接内存大小可以通过MaxDirectMemorySize设置
-
如果不指定,默认与堆的最大值-Xmx参数值一致
代码示例 1
1 public class BufferTest2 { 2 private static final int BUFFER = 1024 * 1024 * 20;//20MB 3 4 public static void main(String[] args) { 5 ArrayList<ByteBuffer> list = new ArrayList<>(); 6 7 int count = 0; 8 try { 9 while(true){ 10 ByteBuffer byteBuffer = ByteBuffer.allocateDirect(BUFFER); 11 list.add(byteBuffer); 12 count++; 13 try { 14 Thread.sleep(100); 15 } catch (InterruptedException e) { 16 e.printStackTrace(); 17 } 18 } 19 } finally { 20 System.out.println(count); 21 } 22 } 23 }
-
本地内存持续增长,直至程序抛出异常:java.lang.OutOfMemoryError: Direct buffer memory
-
异常信息
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 类申请本地内存
1 /** 2 * -Xmx20m -XX:MaxDirectMemorySize=10m 3 */ 4 public class MaxDirectMemorySizeTest { 5 private static final long _1MB = 1024 * 1024; 6 7 public static void main(String[] args) throws IllegalAccessException { 8 Field unsafeField = Unsafe.class.getDeclaredFields()[0]; 9 unsafeField.setAccessible(true); 10 Unsafe unsafe = (Unsafe)unsafeField.get(null); 11 while(true){ 12 unsafe.allocateMemory(_1MB); 13 } 14 15 } 16 }
-
JVM 参数:-Xmx20m -XX:MaxDirectMemorySize=10m
- 抛出 OOM 异常
Exception 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 中元空间直接使用本地内存