JAVA FinalReference

JAVA FinalReference

引入

使用MAT分析dump出的内存时,常会看到java.lang.ref.Finalizer占用内存也不小,比较纳闷我们在编程中并没有用到这个东西,为什么他会出现并且占用分量不算小的一部分内存呢?

final class Finalizer extends FinalReference {
    private static ReferenceQueue queue = new ReferenceQueue();
    //... ...
}

结合它的数据结构基本可以看出来,Finalizer中持有一个一个引用队列。猜测是这个队列吃掉了那些内存。

引用类型

Java开发不必关心内存的释放、申请和垃圾回收,这些事情都有JVM代劳,但是JVM依然提供了一些方式,让我们能够在应用的层次利用内存或者GC特性,从而更好的使用内存。Reference(引用)就是其中一种。

  • StrongReference(强引用)
    我们平时开发中new一个对象出来,这种引用便是强引用。 JVM 系统采用 Finalizer 来管理每个强引用对象 , 并将其被标记要清理时加入 ReferenceQueue, 并逐一调用该对象的 finalize() 方法。
  • SoftReference(软引用)
    当内存足够的时候,软引用所指向的对象没有其他强引用指向的话,GC的时候并不会被回收,当且只当内存不够时才会被GC回收(调用finalize方法)。强度仅次于强引用。
  • WeakReference(弱引用)
    弱引用指向的对象没有任何强引用指向的话,GC的时候会进行回收。
  • PhantomReference(虚引用)
    如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。

TODO引用详细单独在另一篇作介绍

java.lang.ref包简介

包结构

ref包下对应了Java中对应几种引用类型,改包下的类的可见性均为包内可见。FinalReference可以看作是强引用的一个对应。

FinalReference

FinalReference由JVM来实例化,VM会对那些实现了Object中finalize()方法的类实例化一个对应的FinalReference。
注意:实现的finalize方法体必须非空。

Finalizer

Finalizer是FinalReference的子类,该类被final修饰,不可再被继承,JVM实际操作的是Finalizer。当一个类满足实例化FinalReference的条件时,JVM会调用Finalizer.register()进行注册。(PS:后续讲的Finalizer其实也是在说FinalReference。)

何时注册(实例化FinalReference)

JVM在类加载的时候会遍历当前类的所有方法,包括父类的方法,只要有一个参数为空且返回void的非空finalize方法就认为这个类在创建对象的时候需要进行注册。

对象的创建其实是被拆分成多个步骤,注册的时机可以在为对象分配好内存空间后,也可以在构造函数返回之前,这个点由-XX:-RegisterFinalizersAtInit控制,这个参数默认为true,即:在构造函数返回之前调用。注册入口是Finalizer的register()方法。

final class Finalizer extends FinalReference {
     private static ReferenceQueue queue = new ReferenceQueue();
    private static Finalizer unfinalized = null;
    private static final Object lock = new Object();

    private Finalizer
        next = null,
        prev = null;

    //构造一个对象链表,如图
    /**             
        *        +------+  prev  +-----+        +-----+
    *unfinalized |  f3  | <----> | f2  | <----> | f1  | 
        *        +------+  next  +-----+        +-----+
    **/
    private void add() {
        synchronized (lock) {
            if (unfinalized != null) {
                this.next = unfinalized;
                unfinalized.prev = this;
            }
            unfinalized = this;
        }
    }

    private Finalizer(Object finalizee) {
        super(finalizee, queue);
        add();
    }

    /* Invoked by VM 入口在这里 */
    static void register(Object finalizee) {
        new Finalizer(finalizee);
    }

    //...
 }
何时进入ReferenceQueue

GC工作时,如果发现对象只被Finalizer类引用,说明他可以被回收了,那么就把该对象从对象链中取出,放入ReferenceQueue,并通知FinalizerThread去消费。也就是说,本次GC并不能回收掉这个对象占用的内存。

ReferenceQueue是个典型的生产消费队列,此处不在赘述,可看其源码,实现很简单。

FinalizerThread线程

在Finalizer类的clinit方法(静态块)里,会创建一个FinalizerThread守护线程,这个线程的优先级不是最高的,这就意味着在CPU很紧张的情况下其被调度的优先级可能会受到影响。

FinalizerThread业务很简单,从ReferenceQueue拿出Finalizer,执行finalize方法,并且忽略其抛出的所有异常。执行完毕后,该对象称为真正的垃圾对象,再次发生GC,他的一生也就结束了。

private static class FinalizerThread extends Thread {
        private volatile boolean running;
        FinalizerThread(ThreadGroup g) {
            super(g, "Finalizer");
        }
        public void run() {
            if (running)
                return;
            //...
            running = true;
            for (;;) {
                try {
                    Finalizer f = (Finalizer)queue.remove();
                    f.runFinalizer(jla);
                } catch (InterruptedException x) {
                    // ignore and continue
                }
            }
        }
    }

    static {
        ThreadGroup tg = Thread.currentThread().getThreadGroup();
        for (ThreadGroup tgn = tg;
             tgn != null;
             tg = tgn, tgn = tg.getParent());
        Thread finalizer = new FinalizerThread(tg);
        finalizer.setPriority(Thread.MAX_PRIORITY - 2);
        finalizer.setDaemon(true);
        finalizer.start();
    }
GC回收问题
  • 对象因为Finalizer的引用而变成了一个临时的强引用,即使没有其他的强引用,还是无法立即被回收;

  • 对象至少经历两次GC才能被回收,因为只有在FinalizerThread执行完了f对象的finalize方法的情况下才有可能被下次GC回收,而有可能期间已经经历过多次GC了,但是一直还没执行对象的finalize方法;

  • CPU资源比较稀缺的情况下FinalizerThread线程有可能因为优先级比较低而延迟执行对象的finalize方法;

  • 因为对象的finalize方法迟迟没有执行,有可能会导致大部分f对象进入到old分代,此时容易引发old分代的GC,甚至Full GC,GC暂停时间明显变长,甚至导致OOM;

  • 对象的finalize方法被调用后,这个对象其实还并没有被回收,虽然可能在不久的将来会被回收。

举个例子
/**
 * -Xms4m -Xmx4m -XX:+PrintGCDetails -Xloggc:/Users/childe/logs/gc-f.log
 * -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/Users/childe/logs/oom-f.hprof
 * Created by childe on 2017/3/31.
 */
public class Finalizable {
    static AtomicInteger aliveCount = new AtomicInteger(0);

    Finalizable() {
        //如果注释掉改行,在GC日志中仅能看到简单的新生代GC,程序不会因为内存问题停止
        //如果未注释,程序跑上几分钟就挂掉了,因为生产和消费的能力不对等。GC日志中大部分是Full GC。
        aliveCount.incrementAndGet();
    }

    @Override
    protected void finalize() throws Throwable {
        Finalizable.aliveCount.decrementAndGet();
    }

    public static void main(String args[]) {
        for (int i = 0;; i++) {
            Finalizable f = new Finalizable();
            if ((i % 100_000) == 0) {
                System.out.format("After creating %d objects, %d are still alive.%n", new Object[] {i, Finalizable.aliveCount.get() });
            }
        }
    }
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值