DirectByteBuffer 里的堆外内存何时释放?

DirectByteBuffer 里的堆外内存何时释放?

简要答案

DirectByteBuffer 在分配堆外内存时:

  1. 先要通过 Bits.reserveMemory 来看看是否还有可用的内存,是否达到了堆外内存的上限?有的话,占坑。
  2. 通过 unsafe.allocateMemory 分配内存。
  3. 创建 Cleaner,这个 Cleaner 就是用来管理堆外内存的。Cleaner 继承了 PhantomReference,当 GC 时发现它除了 PhantomReference 外已不可达(持有它的 DirectByteBuffer 失效了),就会把它放进 Reference 类 pending list 静态变量里。然后另有一条 ReferenceHandler 线程,名字叫”Reference Handler”的,关注着这个 pending list,如果看到有对象类型是 Cleaner,就会执行它的clean()。
    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;
    }

再看看 Bits.reserveMemory 的功能:如果有剩余内存可用,占坑。没有的话,通过 System.gc 来触发 FullGC,GC 完剩余内存还不够就会抛出异常 OutOfMemoryError。

注意,并不是只有 FullGC 才会回收 DirectByteBuffer 进而导致回收堆外内存,YoungGC 如果能回收掉 DirectByteBuffer 也会触发堆外内存回收。只是 DirectByteBuffer 对象本身很小,很容易进入老年代,所以想要回收尽量多的内存,还是要触发 FullGC。

    static void reserveMemory(long size, int cap) {

        if (!memoryLimitSet && VM.isBooted()) {
            maxMemory = VM.maxDirectMemory();
            memoryLimitSet = true;
        }

        // optimist!
        if (tryReserveMemory(size, cap)) {
            return;
        }

        final JavaLangRefAccess jlra = SharedSecrets.getJavaLangRefAccess();

        // retry while helping enqueue pending Reference objects
        // which includes executing pending Cleaner(s) which includes
        // Cleaner(s) that free direct buffer memory
        while (jlra.tryHandlePendingReference()) {
            if (tryReserveMemory(size, cap)) {
                return;
            }
        }

        // trigger VM's Reference processing
        System.gc();

        // a retry loop with exponential back-off delays
        // (this gives VM some time to do it's job)
        boolean interrupted = false;
        try {
            long sleepTime = 1;
            int sleeps = 0;
            while (true) {
                if (tryReserveMemory(size, cap)) {
                    return;
                }
                if (sleeps >= MAX_SLEEPS) {
                    break;
                }
                if (!jlra.tryHandlePendingReference()) {
                    try {
                        Thread.sleep(sleepTime);
                        sleepTime <<= 1;
                        sleeps++;
                    } catch (InterruptedException e) {
                        interrupted = true;
                    }
                }
            }

            // no luck
            throw new OutOfMemoryError("Direct buffer memory");

        } finally {
            if (interrupted) {
                // don't swallow interrupts
                Thread.currentThread().interrupt();
            }
        }
    }

参考资料

  1. JVM源码分析之堆外内存完全解读
  2. JVM源码分析之SystemGC完全解读
  3. Java的引用StrongReference、 SoftReference、 WeakReference 、PhantomReference
  4. Netty之Java堆外内存扫盲贴

你可能感兴趣的:(Java,Java,进阶)