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); 8System.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
本部落格所有文章僅用於學習、研究和交流目的,版權歸作者所有,歡迎非商業性質轉載。
如果本篇部落格給您帶來幫助,請作者喝杯咖啡吧!點選下面打賞,您的支援是我最大的動力!