• 【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的架构,在读写本地文件时,我们需要从用户态切换成内核态

        

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

        

    • 测试代码:分别使用 BIO 和 NIO 复制大文件,看看用时差别
        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

    • 异常信息

      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 中元空间直接使用本地内存

      

  • 相关阅读:
    第四章 使用jQuery操作DOM
    第三章 jQuery中的事件与动画
    第二章 jQuery选择器
    第一章 jQuery基础
    第五章 JavaScript对象及初识面向对象
    第四章 JavaScript操作DOM对象
    第三章 JavaScript操作BOM对象
    第二章 JavaScript核心语法
    第一章 Javascript基础
    第九章 MySQL中LIMIT和NOT IN案例
  • 原文地址:https://www.cnblogs.com/h--d/p/14213335.html
Copyright © 2020-2023  润新知