Java Reference 学习笔记

最近在知乎上看到的一个写的不错的文章,记录其中关键的知识点,以防忘记。

WeakReference中所有的方法都是继承自Reference,所以要了解WeakReference以及更多其他的SoftReference、PhtomReference,学习好Reference是基础。

弱引用 被GC特殊对待原理

Reference有四个成员变量
这里写图片描述
这四个成员变量不能改变他们的顺序,因为它关系到JVM GC机制,至少Hotspot是这么做的。
Hotspot gc发生时,会对GC roots进行遍历,其中遍历一个GC root能达到的它的引用时,是使用了OopMapBlock,简单说,WeakReference Class的OopMapBlock是这样:

这里写图片描述
但hotspot在Universe的初始化阶段,会调用一个函数,这个函数会调整Class中定义的field在OopMapBlock的属性(具体,可参加GC tracing的引用在上图中,一开始OopMapBlock(2,4), 其中2为Offset从reference开始,4为size大小, 修改之后变为OopMapBlock(3, 1), 即从queue开始,也只遍历这一个field),现实中就会导致这些Field不会被GC时分析可达性的时候被扫描到。在Reference中只有queue一个会被扫描,做可达性分析,所以例如referent是不会被标记可达的。

弱引用 GC过程

弱引用在GC的过程中,会被特殊处理。在GC的过程中,以copy gc为例,所有存货的强引用都会被拷到新的survivor区域中,但是弱引用不会被拷贝。同时会使用WeakReference的discovered域将其串联起来。然后在JVM中,会启动一个线程,叫做Reference Handler(这个就定义在Reference类中,JVM加载Reference class的时候,就会启动这个线程)
若一个对象只有被弱引用的情况下,就会被回收,若不是则对象的新地址会更新到WeakRefernce中的reference中去。
pending是由JVM维护的,可通过Reference中的discovered field找到下一个满足回收条件的Reference。ReferenceHandler负责将JVM维护的pending reference queue 取出来然后将本身所在的Reference对象加入自身的Reference Queue中,这个设计很巧妙,就像上面所提到的,垃圾回收只会去扫描Reference的queue field,当确定好reference的referent字段没有被其他的类引用的时候,就会将自身加入Reference queue中,下次垃圾回收的时候一起回收掉这些引用类。

SoftReference VS WeakReference

SoftReference与WeakReference的处理大致相同,唯一不同的是,它仅仅在是否把Reference添加到链表离,多增加了一些判断,那就是当前引用的存货时间是不是大于_max_interval,如果大于,那它就和WeakReference一样处理,如果不大于的话,那就当成普通的强引用处理。

// Capture state (of-the-VM) information needed to evaluate the policy
void LRUMaxHeapPolicy::setup() {
  size_t max_heap = MaxHeapSize;
  max_heap -= Universe::get_heap_used_at_last_gc();
  max_heap /= M;

  _max_interval = max_heap * SoftRefLRUPolicyMSPerMB;
  assert(_max_interval >= 0,"Sanity check");
}

上述代码记录了_max_interval的计算方式,也就是说,max_heap越小,_max_interval就会越小,SoftReference就会有越大的可能性被回收。SoftRefLRUPolicyMSPerMB可通过调节JVM启动参数调整,这个参数越小,SoftReference就会越倾向于尽快回收。

PhantomReference & Cleaner

PhantomReference 虚引用,它继承于Reference,方法很简单:

/**
     * Returns this reference object's referent.  Because the referent of a
     * phantom reference is always inaccessible, this method always returns
     * <code>null</code>.
     *
     * @return  <code>null</code>
     */
    public T get() {
        return null;
    }

    /**
     * Creates a new phantom reference that refers to the given object and
     * is registered with the given queue.
     *
     * <p> It is possible to create a phantom reference with a <tt>null</tt>
     * queue, but such a reference is completely useless: Its <tt>get</tt>
     * method will always return null and, since it does not have a queue, it
     * will never be enqueued.
     *
     * @param referent the object the new phantom reference will refer to
     * @param q the queue with which the reference is to be registered,
     *          or <tt>null</tt> if registration is not required
     */
    public PhantomReference(T referent, ReferenceQueue<? super T> q) {
        super(referent, q);
    }

Cleaner继承于Phantom Reference,在Reference Handler里面,我们也看到,Reference Handler会判断Reference的类型,如果是Cleaner,那么就直接调用它的clean方法。具体的清理逻辑会写在cleaner中的Runnable接口中,使用者实现它的run方法。

FinalReference 和 finalize 方法

finalize方法定义在Object中:

protected void finalize() throws Throwable {}

在对象中可以重写这个方法,在这个方法中可以释放各种资源。具体做法是,hotspot会把类的构造方法的最后一条指令,也就是Java虚拟机指令的return,重写为一个特殊的指令,具体代码省略,涉及到C++。大致流程就是,如果Class中定义了finalize方法,就会调用register_finalizer这个方法。最终会调用到Finalizer.java中定义的:

/* Invoked by VM */
    static void register(Object finalizee) {
        new Finalizer(finalizee);
    }

生成一个Finalizer对象,而我们自己的对象就是这个方法中所使用的finalizee。

private static ReferenceQueue<Object> queue = new ReferenceQueue<>();

这个queue,类比于Reference Handler线程,那个线程会把所有的Reference从pending链表上取出来,然后加入到一个queue中。对于FinalReference,就都会加入到上述代码中的queue。这个queue中的所有对象,都会被一个名为FinalizerThread所处理,在FinalizerThread中调用它的runFinalizer方法。

private void runFinalizer(JavaLangAccess jla) {
        synchronized (this) {
            if (hasBeenFinalized()) return;
            remove();
        }
        try {
            Object finalizee = this.get();
            if (finalizee != null && !(finalizee instanceof java.lang.Enum)) {
                jla.invokeFinalize(finalizee);

                /* Clear stack slot containing this variable, to decrease
                   the chances of false retention with a conservative GC */
                finalizee = null;
            }
        } catch (Throwable x) { }
        super.clear();
    }

这个方法使用了JavaLangAccess作为参数,其实就是一次虚函数调用,调用到finalizee中的finalize方法。hasBeenFinalized的作用就是保证finalize方法只会被调用一次。
Fianlizer与Cleaner最大的不同是:

public void finalize() {
    Other.ref = this; // 这里Other.ref是任何其他的类的引用
}

这样一个本该回收的对象又在finalize之后复活了。但Cleaner不行,因为Cleaner继承于PhantomReference,它的get方法始终返回null。其实从被创建之后,它就从外面取不到自己的Referent。所以说cleaner比finalizer用起来安全,finalizer如果在finalize方法中又将自身的referent交给其他类的引用,那么将再次复活它,这样又因为finalizer对于每个reference只会执行一回收,那么可想之后就再也不会对其回收了,这样就导致了内存泄露。再有,finalizer的线程优先级也没有Cleaner高。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值