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() 方法,来释放内存。

 

 

 

参考:

【1】https://www.cnblogs.com/duanxz/p/6089485.html

【2】https://zhuanlan.zhihu.com/p/161939673

posted @ 2022-04-29 21:28  寻找风口的猪  阅读(258)  评论(0编辑  收藏  举报