【JVM】直接内存(十二)

一、直接内存概述

  直接内存  

  1. 不是虚拟机运行时数据区的一部分,也不是《Java虚拟机规范》中定义的内存区域。

  2. 直接内存是在Java堆外的、直接向系统申请的内存区间。

  3. 来源于NIO,通过存在堆中的DirectByteBuffer操作Native内存

  4. 通常,访问直接内存的速度会优于Java堆。即读写性能高。

  5. 因此出于性能考虑,读写频繁的场合可能会考虑使用直接内存。

  6. Java的NIO库允许Java程序使用直接内存,用于数据缓冲区

  • 示例代码

     1 public class BufferTest {
     2     private static final int BUFFER = 1024 * 1024 * 1024;//1GB
     3 
     4     public static void main(String[] args){
     5         //直接分配本地内存空间
     6         ByteBuffer byteBuffer = ByteBuffer.allocateDirect(BUFFER);
     7         System.out.println("直接内存分配完毕,请求指示!");
     8 
     9         Scanner scanner = new Scanner(System.in);
    10         scanner.next();
    11 
    12         System.out.println("直接内存开始释放!");
    13         byteBuffer = null;
    14         System.gc();
    15         scanner.next();
    16     }
    17 }
  • 直接占用了 1G 的本地内存

  • 释放后,Java程序的内存占用明显减少,可以通过任务管理器查看内存变化

二、直接缓冲区(NIO)

  关于NIO可以参考:【Java】Java NIO 概览(一)

  • 原来采用BIO的架构,在读写本地文件时,我们需要从用户态切换成内核态

    【JVM】直接内存(十二)

  • NIO 直接操作物理磁盘,省去了中间商赚差价

    【JVM】直接内存(十二)

  • 测试代码:分别使用 BIO 和 NIO 复制大文件,看看用时差别 【JVM】直接内存(十二)
      1 public class BufferTest1 {
      2 
      3     private static final String TO = "F:\\test\\异界BD中字.mp4";
      4     private static final int _100Mb = 1024 * 1024 * 100;
      5 
      6     public static void main(String[] args) {
      7         long sum = 0;
      8         String src = "F:\\test\\异界BD中字.mp4";
      9         for (int i = 0; i < 3; i++) {
     10             String dest = "F:\\test\\异界BD中字_" + i + ".mp4";
     11             // sum += io(src,dest);//54606
     12             sum += directBuffer(src, dest);//50244
     13         }
     14 
     15         System.out.println("总花费的时间为:" + sum);
     16     }
     17 
     18     private static long directBuffer(String src, String dest) {
     19         long start = System.currentTimeMillis();
     20 
     21         FileChannel inChannel = null;
     22         FileChannel outChannel = null;
     23         try {
     24             inChannel = new FileInputStream(src).getChannel();
     25             outChannel = new FileOutputStream(dest).getChannel();
     26 
     27             ByteBuffer byteBuffer = ByteBuffer.allocateDirect(_100Mb);
     28             while (inChannel.read(byteBuffer) != -1) {
     29                 byteBuffer.flip();//修改为读数据模式
     30                 outChannel.write(byteBuffer);
     31                 byteBuffer.clear();//清空
     32             }
     33         } catch (IOException e) {
     34             e.printStackTrace();
     35         } finally {
     36             if (inChannel != null) {
     37                 try {
     38                     inChannel.close();
     39                 } catch (IOException e) {
     40                     e.printStackTrace();
     41                 }
     42 
     43             }
     44             if (outChannel != null) {
     45                 try {
     46                     outChannel.close();
     47                 } catch (IOException e) {
     48                     e.printStackTrace();
     49                 }
     50 
     51             }
     52         }
     53 
     54         long end = System.currentTimeMillis();
     55         return end - start;
     56 
     57     }
     58 
     59     private static long io(String src, String dest) {
     60         long start = System.currentTimeMillis();
     61 
     62         FileInputStream fis = null;
     63         FileOutputStream fos = null;
     64         try {
     65             fis = new FileInputStream(src);
     66             fos = new FileOutputStream(dest);
     67             byte[] buffer = new byte[_100Mb];
     68             while (true) {
     69                 int len = fis.read(buffer);
     70                 if (len == -1) {
     71                     break;
     72                 }
     73                 fos.write(buffer, 0, len);
     74             }
     75         } catch (IOException e) {
     76             e.printStackTrace();
     77         } finally {
     78             if (fis != null) {
     79                 try {
     80                     fis.close();
     81                 } catch (IOException e) {
     82                     e.printStackTrace();
     83                 }
     84 
     85             }
     86             if (fos != null) {
     87                 try {
     88                     fos.close();
     89                 } catch (IOException e) {
     90                     e.printStackTrace();
     91                 }
     92 
     93             }
     94         }
     95 
     96 
     97         long end = System.currentTimeMillis();
     98 
     99         return end - start;
    100     }
    101 }
    View Code
  •  

    深入 ByteBuffer 源码

    • ByteBuffer.allocateDirect() 方法

      1 public static ByteBuffer allocateDirect(int capacity) {
      2     return new DirectByteBuffer(capacity);
      3 }
    • DirectByteBuffer 类的构造器用到了 Unsafe 类分配本地内存

       1 DirectByteBuffer(int cap) {                   // package-private
       2 
       3     super(-1, 0, cap, cap);
       4     boolean pa = VM.isDirectMemoryPageAligned();
       5     int ps = Bits.pageSize();
       6     long size = Math.max(1L, (long)cap + (pa ? ps : 0));
       7     Bits.reserveMemory(size, cap);
       8 
       9     long base = 0;
      10     try {
      11         base = unsafe.allocateMemory(size);
      12     } catch (OutOfMemoryError x) {
      13         Bits.unreserveMemory(size, cap);
      14         throw x;
      15     }
      16     unsafe.setMemory(base, size, (byte) 0);
      17     if (pa && (base % ps != 0)) {
      18         // Round up to page boundary
      19         address = base + ps - (base & (ps - 1));
      20     } else {
      21         address = base;
      22     }
      23     cleaner = Cleaner.create(this, new Deallocator(base, size, cap));
      24     att = null;
      25 
      26 
      27 
      28 }

       

三、直接内存与 OOM

  1. 直接内存也可能导致OutofMemoryError异常

  2. 由于直接内存在Java堆外,因此它的大小不会直接受限于-Xmx指定的最大堆大小,但是系统内存是有限的,Java堆和直接内存的总和依然受限于操作系统能给出的最大内存。

  3. 直接内存的缺点为:

    • 分配回收成本较高
    • 不受JVM内存回收管理
  4. 直接内存大小可以通过MaxDirectMemorySize设置

  5. 如果不指定,默认与堆的最大值-Xmx参数值一致

  代码示例 1

 1 public class BufferTest2 {
 2     private static final int BUFFER = 1024 * 1024 * 20;//20MB
 3 
 4     public static void main(String[] args) {
 5         ArrayList<ByteBuffer> list = new ArrayList<>();
 6 
 7         int count = 0;
 8         try {
 9             while(true){
10                 ByteBuffer byteBuffer = ByteBuffer.allocateDirect(BUFFER);
11                 list.add(byteBuffer);
12                 count++;
13                 try {
14                     Thread.sleep(100);
15                 } catch (InterruptedException e) {
16                     e.printStackTrace();
17                 }
18             }
19         } finally {
20             System.out.println(count);
21         }
22     }
23 }
  • 本地内存持续增长,直至程序抛出异常:java.lang.OutOfMemoryError: Direct buffer memory
    【JVM】直接内存(十二)

  • 异常信息

    Exception in thread "main" java.lang.OutOfMemoryError: Direct buffer memory
        at java.nio.Bits.reserveMemory(Bits.java:694)
        at java.nio.DirectByteBuffer.<init>(DirectByteBuffer.java:123)
        at java.nio.ByteBuffer.allocateDirect(ByteBuffer.java:311)
        at com.atguigu.java.BufferTest2.main(BufferTest2.java:21)
    
    Process finished with exit code 1

     

  代码示例 2

  • 代码:直接通过 Unsafe 类申请本地内存
     1 /**
     2  * -Xmx20m -XX:MaxDirectMemorySize=10m
     3  */
     4 public class MaxDirectMemorySizeTest {
     5     private static final long _1MB = 1024 * 1024;
     6 
     7     public static void main(String[] args) throws IllegalAccessException {
     8         Field unsafeField = Unsafe.class.getDeclaredFields()[0];
     9         unsafeField.setAccessible(true);
    10         Unsafe unsafe = (Unsafe)unsafeField.get(null);
    11         while(true){
    12             unsafe.allocateMemory(_1MB);
    13         }
    14 
    15     }
    16 }
  • JVM 参数:-Xmx20m -XX:MaxDirectMemorySize=10m

  • 抛出 OOM 异常
    Exception in thread "main" java.lang.OutOfMemoryError
        at sun.misc.Unsafe.allocateMemory(Native Method)
        at com.atguigu.java.MaxDirectMemorySizeTest.main(MaxDirectMemorySizeTest.java:20)
    
    Process finished with exit code 1

  JDK8 中元空间直接使用本地内存

  【JVM】直接内存(十二)

上一篇:Java网络编程--NIO


下一篇:异步文件通道Java NIO你需要了解多少,来看看这篇文章