JVM详解(七)——直接内存
一、概述
1、介绍
直接内存,不是虚拟机运行时数据区的一部分,也不是《Java虚拟机规范》中定义的内存区域。是Java堆直接向系统申请的内存区间。
来源于NIO,通过存在堆中的DirectByteBuffer操作Native内存。通常,访问直接内存的速度会优于Java堆,即读写性能高。因此处于性能考虑,读写频繁的场合可能会考虑使用直接内存。Java的NIO库允许Java程序使用直接内存,用于数据缓冲区。
IO
|
NIO
|
byte[] / char[]
|
Buffer
|
面向流(Stream)
|
面向缓冲区(Channel)
|
阻塞
|
非阻塞
|
代码示例:IO与NIO、查看直接内存的占用与释放
1 public class BufferTest { 2 // 1GB 3 private static final int BUFFER = 1024 * 1024 * 1024; 4 5 public static void main(String[] args) { 6 // 直接分配本地内存空间 7 ByteBuffer byteBuffer = ByteBuffer.allocateDirect(BUFFER); 8 System.out.println("直接内存分配完毕,请求指示!"); 9 10 Scanner scanner = new Scanner(System.in); 11 scanner.next(); 12 13 System.out.println("直接内存开始释放!"); 14 byteBuffer = null; 15 16 System.gc(); 17 scanner.next(); 18 } 19 }
通过任务管理器,以及进程id,都可以看到java.exe占用了1G的内存。
2、使用本地内存读写
通常,访问直接内存的速度会优于Java堆,即读写性能高。
读写文件,需要与磁盘交互,需要由用户态切换到内核态,在内核态时,需要内存如图的操作。使用IO,如图,这里需要两份内存存储重复数据,效率低。非直接缓冲区:
使用NIO,如图,操作系统划出的直接缓存区可以被Java代码直接访问,只有一份,NIO适合对大文件的读写操作。直接缓冲区:
代码示例:使用本地内存读写数据的测试,验证直接缓冲区的读写性能比IO高。
1 public class BufferTest1 { 2 3 private static final int _100Mb = 1024 * 1024 * 100; 4 5 public static void main(String[] args) { 6 long sum = 0; 7 8 String src = "F:\\后会无期.mkv"; 9 10 for (int i = 0; i < 3; i++) { 11 String dest = "F:\\后会无期_" + i + ".mkv"; 12 13 // 非直接缓冲区 IO 14 // sum += io(src,dest); // 54540 15 // 直接缓冲区 NIO 16 sum += directBuffer(src, dest); // 49619 17 } 18 19 System.out.println("总花费的时间为:" + sum); 20 } 21 22 private static long directBuffer(String src, String dest) { 23 long start = System.currentTimeMillis(); 24 25 FileChannel inChannel = null; 26 FileChannel outChannel = null; 27 try { 28 inChannel = new FileInputStream(src).getChannel(); 29 outChannel = new FileOutputStream(dest).getChannel(); 30 31 ByteBuffer byteBuffer = ByteBuffer.allocateDirect(_100Mb); 32 while (inChannel.read(byteBuffer) != -1) { 33 byteBuffer.flip(); // 修改为读数据模式 34 outChannel.write(byteBuffer); 35 byteBuffer.clear(); // 清空 36 } 37 } catch (IOException e) { 38 e.printStackTrace(); 39 } finally { 40 if (inChannel != null) { 41 try { 42 inChannel.close(); 43 } catch (IOException e) { 44 e.printStackTrace(); 45 } 46 } 47 if (outChannel != null) { 48 try { 49 outChannel.close(); 50 } catch (IOException e) { 51 e.printStackTrace(); 52 } 53 } 54 } 55 56 long end = System.currentTimeMillis(); 57 return end - start; 58 } 59 60 private static long io(String src, String dest) { 61 long start = System.currentTimeMillis(); 62 63 FileInputStream fis = null; 64 FileOutputStream fos = null; 65 try { 66 fis = new FileInputStream(src); 67 fos = new FileOutputStream(dest); 68 byte[] buffer = new byte[_100Mb]; 69 while (true) { 70 int len = fis.read(buffer); 71 if (len == -1) { 72 break; 73 } 74 fos.write(buffer, 0, len); 75 } 76 } catch (IOException e) { 77 e.printStackTrace(); 78 } finally { 79 if (fis != null) { 80 try { 81 fis.close(); 82 } catch (IOException e) { 83 e.printStackTrace(); 84 } 85 } 86 if (fos != null) { 87 try { 88 fos.close(); 89 } catch (IOException e) { 90 e.printStackTrace(); 91 } 92 } 93 } 94 long end = System.currentTimeMillis(); 95 return end - start; 96 } 97 }
3、直接内存OOM与大小的设置
直接内存也可能导致OOM异常。由于直接内存在Java堆外,因此它的大小不会直接受限于-Xmx指定的最大堆大小。但是系统内存是有限的,Java堆和直接内存的总和应小于操作系统给出的最大内存。
缺点:分配回收成本较高,不受JVM内存回收管理。
直接内存大小可以通过MaxDirectMemorySize设置。默认情况与堆的最大值参数一致。
代码示例:本地内存OOM,OutOfMemoryError:Direct buffer memory
1 public class BufferTest2 { 2 // 20MB 3 private static final int BUFFER = 1024 * 1024 * 20; 4 5 public static void main(String[] args) { 6 ArrayList<ByteBuffer> list = new ArrayList<>(); 7 8 int count = 0; 9 try { 10 while (true) { 11 ByteBuffer byteBuffer = ByteBuffer.allocateDirect(BUFFER); 12 13 list.add(byteBuffer); 14 count++; 15 try { 16 Thread.sleep(100); 17 } catch (InterruptedException e) { 18 e.printStackTrace(); 19 } 20 } 21 } finally { 22 System.out.println(count); 23 } 24 } 25 } 26 27 // 181 28 // Exception in thread "main" java.lang.OutOfMemoryError: Direct buffer memory
代码示例:直接内存大小设置
1 // -Xmx20m -XX:MaxDirectMemorySize=10m 2 public class MaxDirectMemorySizeTest { 3 private static final long _1MB = 1024 * 1024; 4 5 public static void main(String[] args) throws IllegalAccessException { 6 Field unsafeField = Unsafe.class.getDeclaredFields()[0]; 7 unsafeField.setAccessible(true); 8 9 Unsafe unsafe = (Unsafe) unsafeField.get(null); 10 while (true) { 11 unsafe.allocateMemory(_1MB); 12 } 13 } 14 }
简单理解:java process memory = java heap + native memory
作者:Craftsman-L
本博客所有文章仅用于学习、研究和交流目的,版权归作者所有,欢迎非商业性质转载。
如果本篇博客给您带来帮助,请作者喝杯咖啡吧!点击下面打赏,您的支持是我最大的动力!