大白话系列深入浅出Cleaner+虚引用完成堆外内存的回收

Posted Roninaxious

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了大白话系列深入浅出Cleaner+虚引用完成堆外内存的回收相关的知识,希望对你有一定的参考价值。

在NIO技术中,使用allocateDirect()方法可以创建直接内存;如何释放该内存呢?

(1)通过手动释放内存(Cleaner+虚引用)
(2)交给JVM进行处理(Full GC)

1.直接内存的创建与销毁

base = UNSAFE.allocateMemory(size);

在DirectByteBuffer类构造方法中,主要通过allocateMemory方法完成堆外内存的创建,这个方法是Unsafe类中,这个类是干什么的呢?通过名字我们可以也可以看出这个类是不安全的,也就是它是能够直接操作堆外内存,超出了JVM的管辖范围!

UNSAFE.freeMemory(address);

需要注意的是通过Unsafe开辟的直接内存,需要通过调用freeMemory手动回收(当然帮你写了)

2.通过Cleaner+虚引用完成堆外内存回收

Java对象有四种引用方式:强软弱虚
虚引用PhantomReference一般来说极少使用,而且它不能单独使用,它需要和引用队列 ReferenceQueue一块使用

首先分析allocateDirect()方法底层(DirectByteBuffer类)

通过它的构造方法可以看出创建了Cleaner对象和Deallocator对象,首先分析Cleaner对象,通过看它的源码可以得出,它的底层维护了一个双向链表,当Cleaner对象初始化时,就会加入到这个Cleaner双向链表中(而且是安全的,使用了Synchronized,Cleaner类继承了虚引用)

   public static Cleaner create(Object ob, Runnable thunk) 
        if (thunk == null)
            return null;
        return add(new Cleaner(ob, thunk));
    

当DirectByteBuffer对象不存在时,Cleaner对象不再处于引用链中,等到下一次GC时,Cleaner会被加入到ReferenceQueue队列中,并执行clean方法;将将自身从Cleaner双向链表中移除(remove),然利用多态调用了Deallocator类中的run方法释放内存

public void clean() 
        if (!remove(this))
            return;
        try 
            thunk.run();
         catch (final Throwable x) 
            AccessController.doPrivileged(new PrivilegedAction<>() 
                    public Void run() 
                        if (System.err != null)
                            new Error("Cleaner terminated abnormally", x)
                                .printStackTrace();
                        System.exit(1);
                        return null;
                    );
        
    
public void run() 
            if (address == 0) 
                // Paranoia
                return;
            
            UNSAFE.freeMemory(address);
            address = 0;
            Bits.unreserveMemory(size, capacity);
        

3.总结创建与销毁流程

分析下图总结流程(来源百度图片)

初始时,创建了一个DirectByteBuffer对象,在DirectByteBuffer的构造函数中创建了一个Cleaner对象和Deallocator对象;Cleaner对象初始化时将自身加入到了链表中;一旦DirectByteBuffer对象被回收,那么下次GC时Cleaner(继承了虚引用)对象会被放到ReferenceQueue队列中并执行clean方法(多态调用了Deallocator中的run方法释放内存)



4.如何一步步顺序解读源码流程

(1)ByteBuffer抽象类的allocateDirect方法

(2)DirectByteBfufer的构造方法
(3)Cleaner类的create方法
(4)Cleaner类的add方法
(5)回到第二步

cleaner = Cleaner.create(this, new Deallocator(base, size, cap));

(6)Deallocator类的构造方法

将Cleaner类中分配的直接内存的地址、大小等进行复制,因为这个类的run方法是进行内存回收的根方法。

5.使用直接内存的利弊分析

使用直接内存肯定也有坏处,否者还有堆内存什么事;使用直接内存不受JVM的管辖,稍有不慎容易造成内存泄漏!当然使用直接内存减少了GC的时间(交给了操作系统进行管理),另一方面与磁盘交互时使用直接内存减少了复制操作,效率得到提高。

以上是关于大白话系列深入浅出Cleaner+虚引用完成堆外内存的回收的主要内容,如果未能解决你的问题,请参考以下文章

强软弱虚(关键词: ThreadLocal,堆外内存)

使用sun.misc.Cleaner或者PhantomReference实现堆外内存的自动释放

使用sun.misc.Cleaner或者PhantomReference实现堆外内存的自动释放

[ Java ] 超级大白话解释 —— 强引用弱引用软引用虚引用(59.99秒懂)

java jvm之直接内存释放过程

java jvm之直接内存释放过程