JVM直接内存
Direct Memory
- 常见于 NIO 操作时,用于数据缓冲区
- 分配回收成本较高,但读写性能高
- 不受 JVM 内存回收管理
普通的IO:
读入文件会先将文件放入系统的内存,再将文件放入Java的堆内存,Java才能读取,比较浪费时间、浪费性能。

NIO:
开辟一块系统和Java都能访问到的内存区域,无需将文件再次缓冲到Java的堆内存当中,提高效率。

直接内存也会导致内存溢出,比如运行下面的代码:
static int _100Mb = 1024 * 1024 * 100;
public static void main(String[] args) {
List<ByteBuffer> list = new ArrayList<>();
int i = 0;
try {
while (true) {
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(_100Mb);
list.add(byteBuffer);
i++;
}
} finally {
System.out.println(i);
}
}可以看到下面的结果:

分配和回收原理
直接内存的回收不是通过JVM的垃圾回收来释放的,而是通过 unsafe.freeMemory() 方法来手动释放
import sun.misc.Unsafe;
Unsafe unsafe = getUnsafe(); // Java内部使用的对象,可以通过反射获取对象
// 分配内存 1Gb = 1024 * 1024 * 1024
long base = unsafe.allocateMemory(_1Gb);
unsafe.setMemory(base, 1024 * 1024 * 1024, (byte) 0);
// 释放内存
unsafe.freeMemory(base);而NIO中的ByteBuffer类就是用到了该原理:
- 在DirectByteBuffer类(ByteBuffer的子类)的构造器中,使用了
unsafe.allocateMemory(size)来获取内存空间 - ByteBuffer的实现类内部,使用了Cleaner(虚引用类型)对象来监测ByteBuffer对象是否被回收
- 如果被回收,则会触发Cleaner对象的
clean()方法 clean()方法又会调用创建Cleaner时传入的Deallocator对象(该对象实现了Runnable接口,是一个单独的线程,用来调用unsafe.freeMemory(address)方法)
Demo:
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(1024 * 1024 * 1024);
System.out.println("分配完毕...");
System.out.println("开始释放...");
byteBuffer = null;
System.gc(); // 显式的垃圾回收,把byteBuffer对象回收掉,然后会自动触发Cleaner的clean()方法
System.in.read();禁用显式回收对直接内存的影响
可以使用 -XX:+DisableExplicitGC 命令来显式的禁用代码中的 System.gc() 作用(使用该方法影响性能,不光要回收新生代,还有老年代)。
但是如果禁用掉,上面的Demo中的ByteBuffer对象则会长时间存在,程序占用的1Gb的直接内存也不会释放。
此时,建议使用Unsafe类的 freeMemory() 方法手动释放直接内存。
Last updated on