OpenJDK16 ZGC 源码分析(六)GC阶段之非强引用处理

1. 简介

在并发标记结束后,ZGC进入非强引用的并发标记和处理阶段。主要负责处理软引用、弱引用、虚引用、FinalReference(实现了Finalize方法的类的实例)。

2. 引用

2.1 引用的基本概念

引用的类型
memory/referenceType.hpp

enum ReferenceType {
  REF_NONE,      // 正常的类
  REF_OTHER,     // java/lang/ref/Reference的子类,但不在下面里面
  REF_SOFT,      // 软引用,java/lang/ref/SoftReference的子类
  REF_WEAK,      // 弱引用,java/lang/ref/WeakReference的子类
  REF_FINAL,     // java/lang/ref/FinalReference的子类
  REF_PHANTOM    // 虚引用,java/lang/ref/PhantomReference的子类
};

在这里插入图片描述
引用主要有4种状态:

  • active:活跃
  • pending:对象进入pending列表
  • enqueued:对象进入引用队列
  • Inactive:不活跃,可以回收

pending转变为enqueued由Java线程ReferenceHandler负责,其他由GC线程负责。

2.2 java.lang.ref.Reference

相关引用类别均继承了抽象类java.lang.ref.Reference,Reference主要包含如下成员变量:

// jvm在gc时会将要处理的对象放到这个静态字段上面。
private static Reference<Object> pending = null;
// 表示要处理的对象的下一个对象。即可以理解要处理的对象也是一个链表,通过discovered进行排队
// processPendingReferences只需要不停地拿到pending,然后再通过discovered不断地拿到下一个对象赋值给pending即可,直到取到了最有一个。
// 因为这个pending对象,两个线程都可能访问,因此需要加锁处理。
transient private Reference<T> discovered;  /* used by VM */
// 引用中的对象,由GC决定何时、是否回收
private T referent; /* Treated specially by GC */
// 即描述当前引用节点所存储的下一个即将被处理的节点。但next仅在放到queue中才会有意义
Reference next;
// 引用的队列,tryHandlePending最后一步就是把Reference对象入队。new的时候如果是null就不入队了。
volatile ReferenceQueue<? super T> queue;

在这里插入图片描述

2.3 ReferenceHandler线程

Reference的static代码块中会启动一个线程,该线程会死循环调用processPendingReferences方法,方法

    private static class ReferenceHandler extends Thread {

        private static void ensureClassInitialized(Class<?> clazz) {
            try {
                Class.forName(clazz.getName(), true, clazz.getClassLoader());
            } catch (ClassNotFoundException e) {
                throw (Error) new NoClassDefFoundError(e.getMessage()).initCause(e);
            }
        }

        static {
            ensureClassInitialized(Cleaner.class);
        }

        ReferenceHandler(ThreadGroup g, String name) {
            super(g, null, name, 0, false);
        }

        public void run() {
            while (true) {
                // 死循环调用processPendingReferences
                processPendingReferences();
            }
        }
    }

    static {
        ThreadGroup tg = Thread.currentThread().getThreadGroup();
        for (ThreadGroup tgn = tg;
             tgn != null;
             tg = tgn, tgn = tg.getParent());
        Thread handler = new ReferenceHandler(tg, "Reference Handler");
        // 最高优先级线程
        handler.setPriority(Thread.MAX_PRIORITY);
        // 守护线程
        handler.setDaemon(true);
        handler.start();

        SharedSecrets.setJavaLangRefAccess(new JavaLangRefAccess() {
            @Override
            public boolean waitForReferenceProcessing()
                throws InterruptedException
            {
                return Reference.waitForReferenceProcessing();
            }

            @Override
            public void runFinalization() {
                Finalizer.runFinalization();
            }
        });
    }

2.4 processPendingReferences

processPendingReferences主要逻辑如下,也比较简单:

    private static final Object processPendingLock = new Object();
    private static boolean processPendingActive = false;

    private static void processPendingReferences() {
        // wait直到被notify或JVM通知有pending的引用
        waitForReferencePendingList();
        Reference<Object> pendingList;
        synchronized (processPendingLock) {
            pendingList = getAndClearReferencePendingList();
            processPendingActive = true;
        }
        while (pendingList != null) {
            // 链表前进一位,并清除链表头的discovered
            Reference<Object> ref = pendingList;
            pendingList = ref.discovered;
            ref.discovered = null;

			// 判断是否是jdk.internal.ref.Cleaner
            if (ref instanceof Cleaner) {
                // 如是,则通常是回收资源的场景,如回收堆外内存
                ((Cleaner)ref).clean();
                synchronized (processPendingLock) {
                    processPendingLock.notifyAll();
                }
            } else {
                // 否则,就是软、弱、虚引用的处理场景,对象入队
                ReferenceQueue<? super Object> q = ref.queue;
                if (q != ReferenceQueue.NULL) q.enqueue(ref);
            }
        }

        synchronized (processPendingLock) {
            processPendingActive = false;
            processPendingLock.notifyAll();
        }
    }

2.5 软引用的特殊逻辑

SoftReference继承了Reference,但稍微修改下get逻辑:

    // 私有静态变量clock,JVM在GC时更新
    static private long clock;

    // 软引用的纪元,GC根据timestamp和clock判断是否回收
    private long timestamp;

    public SoftReference(T referent) {
        super(referent);
        // 创建软引用时,timestamp赋值为clock
        this.timestamp = clock;
    }

    public T get() {
        T o = super.get();
        // get时,如果timestamp不等于clock,timestamp赋值为clock
        if (o != null && this.timestamp != clock)
            this.timestamp = clock;
        return o;
    }

3. 源码分析

上面章节介绍了Java中对于引用的处理,接下来再介绍下JVM层的处理。

3.1 入口

share/gc/z/zReferenceProcessor.cpp

void ZReferenceProcessor::process_references() {
  ZStatTimer timer(ZSubPhaseConcurrentReferencesProcess);

  // 多线程并发处理
  ZReferenceProcessorTask task(this);
  _workers->run_concurrent(&task);

  // 更新SoftReference类的私有变量clock
  soft_reference_update_clock();

  // 更新日志和统计信息
  collect_statistics();
}

ZReferenceProcessorTask线程主要逻辑如下:
share/gc/z/zReferenceProcessor.cpp

void ZReferenceProcessor::work() {
  // 获取discovered引用链表
  oop* const list = _discovered_list.addr();
  oop* p = list;

  // 遍历整个链表
  while (*p != NULL) {
    const oop reference = *p;
    const ReferenceType type = reference_type(reference);

	// 判断该引用是否应该留在_discovered_list
    if (should_drop(reference, type)) {
      // 从链表移除,reference状态修改为active
      *p = drop(reference, type);
    } else {
      // 保留在链表中,引用置为inactive,前进一位
      p = keep(reference, type);
    }
  }

  // 将list追加到pending链表
  if (*list != NULL) {
    *p = Atomic::xchg(_pending_list.addr(), *list);
    if (*p == NULL) {
      _pending_list_tail = p;
    }

    // 清理discovered链表
    *list = NULL;
  }
}

share/gc/z/zReferenceProcessor.cpp

bool ZReferenceProcessor::should_drop(oop reference, ReferenceType type) const {
  const oop referent = reference_referent(reference);
  if (referent == NULL) {
  	// 这种情况表示,应用线程直接调用了Reference.enqueue()或Reference.clear(),从链表移除
    return true;
  }

  // 如果引用还是active,也应该从链表移除
  if (type == REF_PHANTOM) {
    return ZBarrier::is_alive_barrier_on_phantom_oop(referent);
  } else {
    return ZBarrier::is_alive_barrier_on_weak_oop(referent);
  }
}

3.2 软引用的淘汰策略

软引用的淘汰策略,需要结合2.5节中介绍的软引用特殊逻辑一起看。主要分为两种策略:

  • LRUCurrentHeapPolicy,上次GC的释放的MB*常量SoftRefLRUPolicyMSPerMB
    share/gc/shared/referencePolicy.cpp
_max_interval = (Universe::heap()->free_at_last_gc() / M) * SoftRefLRUPolicyMSPerMB;
  • LRUMaxHeapPolicy,上次GC后空闲的MB*常量SoftRefLRUPolicyMSPerMB
    share/gc/shared/referencePolicy.cpp
  size_t max_heap = MaxHeapSize;
  max_heap -= Universe::heap()->used_at_last_gc();
  max_heap /= M;

  _max_interval = max_heap * SoftRefLRUPolicyMSPerMB;

常量SoftRefLRUPolicyMSPerMB指的是每1MB允许软引用存活多少毫秒,默认是1000。

根据策略计算出_max_interval后,释放回收特定引用的算法如下:
share/gc/shared/referencePolicy.cpp

  // 获取SoftReference的私有成员变量timestamp的值
  jlong interval = timestamp_clock - java_lang_ref_SoftReference::timestamp(p);
  assert(interval >= 0, "Sanity check");

  // 如果timestamp小于_max_interval就不回收
  if(interval <= _max_interval) {
    return false;
  }

  return true;

4. 总结

本文介绍了引用的一些基本概念,和非强引用回收的基本流程。非强引用回收的过程设计的GC线程和Java线程ReferenceHandler的互动,交互过程非常有意思,值得学习。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值