虚引用解析

虚引用解析,第1张

引用解析

结论:堆外内存只有在fullgc的时候才能够回收掉。

先看普通对象回收,这里设置jvm参数,便于触发和观察gc

-verbose:gc -XX:+PrintGCDetails -server -Xms20m -Xmx20m

实例代码

public class Test3 {
    public static final ReferenceQueue QUEUE = new ReferenceQueue<>();
    public static final List list = new ArrayList<>();

    public static void main(String[] args) throws Exception {
        for (int i = 0; i < 12; i++) {
            // 里面包含一个1M的字节数组
            Entti ti = new Entti();
            PhantomReference phantomReference = new PhantomReference(ti, QUEUE);
            ti = null;
            System.out.println(1);
        }
        System.gc();
        // 保证gc触发
        Thread.sleep(1000);
        int i = 0;
        Reference t;
        while ((t = QUEUE.poll()) != null) {
            i++;
        }
        System.out.println("size" + i);

    }
}

结果

分析:虚引用不是再对象回收后会加入队列嘛?gc日志显示对象确实被回收了,为何queue里面没有数据?在对象回收的时候,如果发现对象还有虚引用,就把虚引用加入其队列,但是这里的虚引用已经不存活了,所以队列为空。

我们把虚引用保存起来,保证其存活:

public class Test3 {
    public static final ReferenceQueue QUEUE = new ReferenceQueue<>();
    public static final List list = new ArrayList<>();

    public static void main(String[] args) throws Exception {
        for (int i = 0; i < 12; i++) {
            // 里面包含一个1M的字节数组
            Entti ti = new Entti();
            PhantomReference phantomReference = new PhantomReference(ti, QUEUE);
            ti = null;
            list.add(phantomReference);
            System.out.println(1);
        }
        System.gc();
        // 保证gc触发
        Thread.sleep(1000);
        int i = 0;
        Reference t;
        while ((t = QUEUE.poll()) != null) {
            i++;
        }
        System.out.println("size" + i);

    }
}

结果

 分析:队列的数量确实对了,但是发现堆的占用空间基本没有释放,虚引用不是不影响对象回收吗?虚引用只是不影响对象回收的判断,现在jvm确实认定这是可以回收的对象,但是因为list->phantomReference->对象空间。jvm不能把这块对象释放,但是jvm把虚引用放入了队列里,通知用户这块空间要回收。

我们把列表的虚引用去掉,再gc

public class Test3 {
    public static final ReferenceQueue QUEUE = new ReferenceQueue<>();
    public static final List list = new ArrayList<>();

    public static void main(String[] args) throws Exception {
        for (int i = 0; i < 12; i++) {
            // 里面包含一个1M的字节数组
            Entti ti = new Entti();
            PhantomReference phantomReference = new PhantomReference(ti, QUEUE);
            ti = null;
            list.add(phantomReference);
            System.out.println(1);
        }
        System.gc();
        // 保证gc触发
        Thread.sleep(1000);
        int i = 0;
        Reference t;
        while ((t = QUEUE.poll()) != null) {
            i++;
        }
        System.out.println("size" + i);
        list.clear();
        System.gc();
    }

结果

分析:果然空间释放了,如果对象在回收的时候有虚引用,yanggc无法回收这块空间,如果虚引用一直不释放,jvm无法释放这块空间,只会把虚引用放入对应队列中。

如果只清理list,不清理QUEUE

1

结果

分析:必须把所有的引用都清理掉,不然无法回收空间 

堆外内存释放的原理就是虚引用的实现,只不过这个虚引用队列jvm会管理。

堆外内存构造函数:堆外内存在构造的时候包含一个cleaner(包含一个runable),cleaner.create会把这个clear对象加入到一个单向的列表中,保证clear对象不会再yanggc的时候回收掉。

    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;

    }

fullgc的时候jvm会进入这段逻辑:如果这个引用是cleaner类型,就调用cleaner的clean方法(最终调用注册的runable释放堆外内存)。

  static boolean tryHandlePending(boolean waitForNotify) {
        Reference r;
        Cleaner c;
        try {
            synchronized (lock) {
                if (pending != null) {
                    r = pending;
                    // 'instanceof' might throw OutOfMemoryError sometimes
                    // so do this before un-linking 'r' from the 'pending' chain...
                    c = r instanceof Cleaner ? (Cleaner) r : null;
                    // unlink 'r' from 'pending' chain
                    pending = r.discovered;
                    r.discovered = null;
                } else {
                    // The waiting on the lock may cause an OutOfMemoryError
                    // because it may try to allocate exception objects.
                    if (waitForNotify) {
                        lock.wait();
                    }
                    // retry if waited
                    return waitForNotify;
                }
            }
        } catch (OutOfMemoryError x) {
            // Give other threads CPU time so they hopefully drop some live references
            // and GC reclaims some space.
            // Also prevent CPU intensive spinning in case 'r instanceof Cleaner' above
            // persistently throws OOME for some time...
            Thread.yield();
            // retry
            return true;
        } catch (InterruptedException x) {
            // retry
            return true;
        }

        // Fast path for cleaners
        if (c != null) {
            c.clean();
            return true;
        }

        ReferenceQueue q = r.queue;
        if (q != ReferenceQueue.NULL) q.enqueue(r);
        return true;
    } 

补充 如果对象重写了析构方法(和cleaner类似):

  • JVM创建Finalizable对象
  • JVM创建 java.lang.ref.Finalizer实例,指向刚创建的对象。
  • java.lang.ref.Finalizer类持有新创建的java.lang.ref.Finalizer的实例。这使得下一次新生代GC无法回收这些对象。
  • 新生代GC无法清空Eden区,因此会将这些对象移到老年代。
  • 垃圾回收器发现这些对象实现了finalize()方法。因为会把它们添加到java.lang.ref.Finalizer.ReferenceQueue队列中。
  • Finalizer线程会处理这个队列,将里面的对象逐个d出,并调用它们的finalize()方法。
  • finalize()方法调用完后,Finalizer线程会将引用从Finalizer类中去掉,因此在下一轮GC中,这些对象就可以被回收了。
  • Finalizer线程会和我们的主线程进行竞争,不过由于它的优先级较低,获取到的CPU时间较少,因此它永远也赶不上主线程的步伐。
  • 程序消耗了所有的可用资源,最后抛出OutOfMemoryError异常。

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

原文地址: https://outofmemory.cn/zaji/5482160.html

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

发表评论

登录后才能评论

评论列表(0条)