一、概述
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