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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM