nio DirectByteBuffer如何回收堆外内存

Posted windliu

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了nio DirectByteBuffer如何回收堆外内存相关的知识,希望对你有一定的参考价值。

概述

使用了nio框架的应用,比如服务框架,利用nio建立长连接通信,他们会使用DirectByteBuffer来分配堆外内存,也就是本地直接内存,这个内存的回收不由gc直接维护,我们通常所说的gc,只回收jvm的堆、栈、方法区。本地内存如果没有用jvm启动参数手动指定,它会根据主机的剩余可用内存进行分配,如果说一个机器的8G内存的,其中,我们手动指定的jvm堆、方法区内存为2048 + 256,那么,除了其他进程占用的内存,剩余的可用内存可能是较大的。如果你的主机有内存使用量监控(不是jvm级的内存监控),在使用类似Netty这种通信框架时,有可能会触发主机内存使用率报警。

队外内存回收方法

  • 首先强调,gc不会直接回收堆外内存,堆外内存如果不通过启动参数指定,会根据主机的剩余可用内存来作为容量,这有可能是一块很大的内存,gc回收代价可能较大
  • DirectByteBuffer对象在生成时,会和一个RefereceQueue建立联系,并且这个引用是虚引用,对象A虚引用了对象B,不会影响对象B的生命周期,垃圾回收在计算对象可达性时,会忽略A对B的虚引用
  • jdk实现GC时,对几种引用类型有定制化开发,在对象B被回收后,会通知到一个ReferenceHandler线程,获取到虚引用对象A,判断A是否是Cleaner,如果是就会调用Cleaner.clean()方法,获取DirectByteBuffer被分配的堆外内存地址,释放B在堆外内存开辟的空间,释放内存

    class DirectByteBuffer{

      ...
     private static class Deallocator
        implements Runnable
    {
    
        private static Unsafe unsafe = Unsafe.getUnsafe();
    
        private long address;
        private long size;
        private int capacity;
    
        private Deallocator(long address, long size, int capacity) {
            assert (address != 0);
            this.address = address;
            this.size = size;
            this.capacity = capacity;
        }
    
        public void run() {
            if (address == 0) {
                // Paranoia
                return;
            }
            unsafe.freeMemory(address);
            address = 0;
            Bits.unreserveMemory(size, capacity);
        }
    
    }
    
    ...

    }

Clean类,runnable这里对应Deallocator类型

package sun.misc;

import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;
import java.security.AccessController;
import java.security.PrivilegedAction;

public class Cleaner extends PhantomReference<Object> {
    private static final ReferenceQueue<Object> dummyQueue = new ReferenceQueue();
    private static Cleaner first = null;
    private Cleaner next = null;
    private Cleaner prev = null;
    private final Runnable thunk;

    private static synchronized Cleaner add(Cleaner var0) {
        if(first != null) {
            var0.next = first;
            first.prev = var0;
        }

        first = var0;
        return var0;
    }

    private static synchronized boolean remove(Cleaner var0) {
        if(var0.next == var0) {
            return false;
        } else {
            if(first == var0) {
                if(var0.next != null) {
                    first = var0.next;
                } else {
                    first = var0.prev;
                }
            }

            if(var0.next != null) {
                var0.next.prev = var0.prev;
            }

            if(var0.prev != null) {
                var0.prev.next = var0.next;
            }

            var0.next = var0;
            var0.prev = var0;
            return true;
        }
    }

    private Cleaner(Object var1, Runnable var2) {
        super(var1, dummyQueue);
        this.thunk = var2;
    }

    public static Cleaner create(Object var0, Runnable var1) {
        return var1 == null?null:add(new Cleaner(var0, var1));
    }

    public void clean() {
        if(remove(this)) {
            try {
                this.thunk.run();
            } catch (final Throwable var2) {
                AccessController.doPrivileged(new PrivilegedAction() {
                    public Void run() {
                        if(System.err != null) {
                            (new Error("Cleaner terminated abnormally", var2)).printStackTrace();
                        }

                        System.exit(1);
                        return null;
                    }
                });
            }

        }
    }
}

以上是关于nio DirectByteBuffer如何回收堆外内存的主要内容,如果未能解决你的问题,请参考以下文章

java 堆外内存使用

HeapByteBuffer和DirectByteBuffer以及回收DirectByteBuffer

NIO中如何使用虚引用管理堆外内存原理

初识 Java NIO

Java 堆外内存回收原理

JVM详解——直接内存