DirectByterBuffer直接内存回收机制
在处理文件复制,传输使用NIO可以申请Direct Memory,好处又很多,比如可以减少JVM的GC,提高文件传输的性能。坏处就是如果出现了直接内存OOM,比较难排查问题。理论上NIO可以申请的直接内存是除JVM内存空间之前的所有内存。我们也可以使用 -XX:MaxDirectMemorySize=512m 来指定能够占用的直接内存的大小。那么问题来了。既然直接内存不归JVM管理,那么就是JVM无法去回收直接内存。那直接内存是怎样被回收的呢?
一、看了几篇比较优秀的博客,再结合源码。自己理解了一下直接内存的回收机制。下面先看看 DirectByteBuffer.java 这个类的构造函数
DirectByteBuffer(int cap) { // package-private super(-1, 0, cap, cap); boolean pa = VM.isDirectMemoryPageAligned(); int ps = Bits.pageSize(); long size = Math.max(1L, (long)cap + (pa ? ps : 0)); Bits.reserveMemory(size, cap); //① long base = 0; try { base = unsafe.allocateMemory(size); } catch (OutOfMemoryError x) { Bits.unreserveMemory(size, cap); throw x; } unsafe.setMemory(base, size, (byte) 0); if (pa && (base % ps != 0)) { // Round up to page boundary address = base + ps - (base & (ps - 1)); } else { address = base; } cleaner = Cleaner.create(this, new Deallocator(base, size, cap)); //② att = null; }
看到①这个位置 代码点进去,很醒目的看到了有一个非常熟悉的方法: System.gc()。这个方法就是建议JVM进行一次Full GC。所以我们知道了。当我们每次申请一个DirectByteBuffer的时候,首先都是会执行这个System.gc()建议JVM进行一次Full GC 的。这是直接内存被回收的方式之一
二、接下来我们再看一下② 这个Cleaner.clear()方法。在这里创建了一个 Deallocator 对象。
Deallocator实现了Runnable,所以我们要看一下Deallocator这个类的run()方法,run方法中非常重要的一个native方法 unsafe.freeMemory() ,通过方法名字就知道这个方法是用来释放内存的
public void run() { if (address == 0) { // Paranoia return; } unsafe.freeMemory(address); address = 0; Bits.unreserveMemory(size, capacity); }
public static Cleaner create(Object var0, Runnable var1) { return var1 == null ? null : add(new Cleaner(var0, var1)); }
并把这个对象加到了Cleaner队列里面
private static synchronized Cleaner add(Cleaner var0) { if (first != null) { var0.next = first; first.prev = var0; } first = var0; return var0; }
Cleaner这个类继承了 PhantomReference
public class Cleaner extends PhantomReference<Object>
这是一个弱引用。在GC的时候就会触发。在Cleaner类中有一个clean()方法。触发了这个clean()方法,就会执行Deallocator的run()方法。回收内存。
public void clean() { if (remove(this)) { try { this.thunk.run(); } catch (final Throwable var2) { AccessController.doPrivileged(new PrivilegedAction<Void>() { public Void run() { if (System.err != null) { (new Error("Cleaner terminated abnormally", var2)).printStackTrace(); } System.exit(1); return null; } }); } } }
简单总结一下怎么NIO的直接内存是怎么内存回收的。在创建 DirectoryByteBuffer 类的时候就会触发一次 System.gc() 方法。对象创建的时候还会创建一个弱引用Cleaner对象,这个对象里面的clean()方法在GC的时候就会被触发,触发后就会执行Deallocator类的run方法。通过 unsafe.freeMemory() 方法,来释放内存。