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

【大白话系列】深入浅出Cleaner+虚引用完成堆外内存的回收,第1张

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

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

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

文章目录
  • 1.直接内存的创建与销毁
  • 2.通过Cleaner+虚引用完成堆外内存回收
  • 3.总结创建与销毁流程
  • 4.如何一步步顺序解读源码流程
  • 5.使用直接内存的利弊分析

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的时间(交给了 *** 作系统进行管理),另一方面与磁盘交互时使用直接内存减少了复制 *** 作,效率得到提高。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/5572923.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-14
下一篇 2022-12-14

发表评论

登录后才能评论

评论列表(0条)

保存