zoukankan      html  css  js  c++  java
  • 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

    本博客所有文章仅用于学习、研究和交流目的,版权归作者所有,欢迎非商业性质转载。

    如果本篇博客给您带来帮助,请作者喝杯咖啡吧!点击下面打赏,您的支持是我最大的动力!

  • 相关阅读:
    Ajax
    事件委托
    事件对象
    js的基础类型与引用类型
    Javascript和HTML dom
    用jQuery写的最简单的表单验证
    初学HTML5的一点理解
    CMake 入门
    centos7上rpm安装wkhtmltopdf
    centos7 下安装思源黑体字体
  • 原文地址:https://www.cnblogs.com/originator/p/15435505.html
Copyright © 2011-2022 走看看