1. 程式人生 > 其它 >JVM詳解(七)——直接記憶體

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

出處:https://www.cnblogs.com/originator

本部落格所有文章僅用於學習、研究和交流目的,版權歸作者所有,歡迎非商業性質轉載。

如果本篇部落格給您帶來幫助,請作者喝杯咖啡吧!點選下面打賞,您的支援是我最大的動力!