JVM系列(九)直接内存(Direct Memory)

2,343 阅读2分钟
  • 直接内存不是虚拟机运行时数据区的一部分,也不是《Java虚拟机规范》中定义的内存区域
  • 直接内存是在Java堆外的、直接向系统申请的内存空间
  • 来源于NIO,通过存在堆中的DirectByteBuffer操作Native内存
  • 通常,访问直接内存的速度会优于Java堆,即读写性能高。因此处于性能考虑,读写频繁的场合可能会考虑使用直接内存。Java的NIO库允许Java程序使用直接内存,用于数据缓冲区

读写文件,需要与磁盘交互,需要由用户态切换到内核态。在内核态时,需要内存如下图的操作。使用IO,这里需要两份内存存储重复数据,效率低 image.png

使用NIO时,如图所示,操作系统划出的直接缓存区可以被java代码直接访问,只有一份。NIO适合对大文件的读写操作 image.png

代码测试 IO & NIO 复制大文件3次耗时对比

```java
package com.nasuf.jvm;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

public class BufferTest {

    private static final String TO = "/Users/nasuf/Downloads/pycharm-community-2021.1.1.dmg";
    private static final int _100MB = 1024 * 1024 * 100;

    public static void main(String[] args) {
        long sum = 0;
        String src = "/Users/nasuf/Downloads/pycharm-community-2021.1.1.dmg";   // 文件大小:472.1 MB
        for (int i = 0; i < 3; i++) {
            String dest = "/Users/nasuf/Downloads/pycharm-community-2021.1.1." + i + "dmg";
//            sum += io(src, dest);   // 总花费时间为:3019
            sum += directBuffer(src, dest); // 总花费时间为:1647
        }
        System.out.println("总花费时间为:" + sum);
    }

    private static long directBuffer(String src, String dest) {
        long start = System.currentTimeMillis();

        FileChannel inChannel = null;
        FileChannel outChannel = null;

        try {
            inChannel = new FileInputStream(src).getChannel();
            outChannel = new FileOutputStream(dest).getChannel();

            ByteBuffer byteBuffer = ByteBuffer.allocateDirect(_100MB);
            while (inChannel.read(byteBuffer) != -1) {
                byteBuffer.flip();  // 修改为读数据模式
                outChannel.write(byteBuffer);
                byteBuffer.clear(); // 清空
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (inChannel != null) {
                try {
                    inChannel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (outChannel != null) {
                try {
                    outChannel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

        long end = System.currentTimeMillis();
        return end - start;
    }

    private static long io(String src, String dest) {
        long start =  System.currentTimeMillis();

        FileInputStream fis = null;
        FileOutputStream fos = null;
        try {
            fis = new FileInputStream(src);
            fos = new FileOutputStream(dest);
            byte[] buffer = new byte[_100MB];
            while (true) {
                int len = fis.read(buffer);
                if (len == -1) {
                    break;
                }
                fos.write(buffer, 0, len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (fis != null) {
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (fos != null) {
                try {
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        long end = System.currentTimeMillis();
        return end - start;
    }
}
```
  • 直接内存也可能导致OutOfMemoryError异常

    代码测试 直接内存OOM异常

    package com.nasuf.jvm;
    
    import java.nio.ByteBuffer;
    import java.util.ArrayList;
    
    public class BufferTest2 {
        private static final int BUFFER = 1024 * 1024 * 20; // 20MB
    
        public static void main(String[] args) {
            ArrayList<ByteBuffer> list = new ArrayList<>();
    
            int count = 0;
            try {
                while (true) {
                    ByteBuffer byteBuffer = ByteBuffer.allocateDirect(BUFFER);
                    list.add(byteBuffer);
                    count++;
                }
            } finally {
                System.out.println(count);
            }
        }
    }
    
    输出如下:
    182
    Exception in thread "main" java.lang.OutOfMemoryError: Direct buffer memory
            at java.nio.Bits.reserveMemory(Bits.java:693)
            at java.nio.DirectByteBuffer.<init>(DirectByteBuffer.java:123)
            at java.nio.ByteBuffer.allocateDirect(ByteBuffer.java:311)
            at com.nasuf.jvm.BufferTest2.main(BufferTest2.java:15)
    
  • 由于直接内存在Java堆外,因此它的大小不会直接受限于-Xmx指定的最大堆大小,但是系统内存是有限的,Java堆和直接内存的总和依然受限于操作系统能给出的最大内存
  • 缺点
    • 分配回收成本较高
    • 不受JVM内存回收管理
  • 直接内存大小可以通过MaxDirectMemorySize设置;如果不指定,默认与堆的最大值-Xmx参数值一致
  • 简单理解:java process memory = java heap + native memory