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

posted @ 2021-10-21 22:20  Craftsman-L  阅读(497)  评论(0编辑  收藏  举报