网站使用了seo优化工具怎么检测,公司网站建设与维护方案,2024新闻热点事件,品牌推广营销平台一、前文回顾
在 细说Java 引用#xff08;强、软、弱、虚#xff09;和 GC 流程#xff08;一#xff09; 我们对Java 引用有了总体的认识#xff0c;本文将继续深入分析 Java 引用在 GC 时的一些细节。
还是从我们在前文中提到的引用流程图里说起#xff0c;这里不清…一、前文回顾
在 细说Java 引用强、软、弱、虚和 GC 流程一 我们对Java 引用有了总体的认识本文将继续深入分析 Java 引用在 GC 时的一些细节。
还是从我们在前文中提到的引用流程图里说起这里不清楚的请回 细说Java 引用强、软、弱、虚和 GC 流程一 中查阅。 图 0-0—— 引用流程图 本文将重点关注图示{3}这部分细节
GC线程是如何把Reference对象收集到pending队列的Reference对象和我们正常的对象有什么不同为啥{1}是强引用{2}是非强引用按照GC可达性算法就算{1}不存在但通过{2}依然可以找到我们的对象那对象就是可达的啊 图 0-1—— GC可达性分析图
我们先来看引用对象和正常对象里字段的区别即上图对象weakReference里的referent和 xx_oo里的 _oo 的秘密
二、引用可达
按照GC可达性算法【图 0-1—— GC可达性分析图】中对象oo和 o均是可达的但是GC时GC线程扫描到引用对象weakReference时会跳过实例变量referent的扫描从而导致对象o不可达接下来让我们一探究竟。
1.1、 OopMapBlock 简析
1.1.1、 OopMapBlock 概述
GC扫描实例对象时会通过一个叫做OopMapBlock的类(C写的)这个类里存放了我们java类的引用类型基本类型不会存放的成员变量换言之OopMapBlock类里存在变量X那就可以顺藤摸瓜找到X对象否则X就是不可达
我们的Reference类对应的OopMapBlock类中就跳过了变量referent
实际使用时若我们在一个java类里定义了成百上千的引用类型变量那OopMapBlock岂不是也得存放成百上千的引用型变量这还了得所以为了避免这种情况OopMapBlock里只有2个变量 _offset连续实例变量中第一个实例变量位置;_count连续实例变量个数 注意这里只记录了一个java类里连续的实例变量静态变量本来就是GC-Root所以不需要记录如下图直接跳过静态变量递归遍历实例变量就可以知道java对象是否可达。
如果实例变量被静态变量隔开那就再来一个OopMapBlock把所有OopMapBlock放入数组即可所以一个java类中存在一个OopMapBlock数组。这个数组是JVM加载类时动态分析后生成的然后把这个数组存放在了 InstanceKlass 对象中可以查阅 JVM层面的JAVA类和实例Klass-OOP 了解InstanceKlass 的知识。
java 实例对象头部中有 Klass 类型指针这样GC线程扫描堆中实例对象时就可以通过InstanceKlass 对象找到这个OopMapBlock并据此构造引用链标记对象是否可达。 java中普通类在JVM层面对应的是InstanceKlass 对象java.lang.ref.Reference对应的是InstanceRefKlass对象以上区分就是为了针对类java.lang.ref.Reference做定制化的OopMapBlock从而跳过变量referent的引用扫描InstanceKlass 对象和InstanceRefKlass对象均存放再JVM方法区中 1.1.2、 OopMapBlock 代码
// Describes where oops are located in instances of this klass.
class OopMapBlock {public:// Byte offset of the first oop mapped by this block.int offset() const { return _offset; }void set_offset(int offset) { _offset offset; }// Number of oops in this block.uint count() const { return _count; }void set_count(uint count) { _count count; }private:int _offset;uint _count;
};InstanceKlass 使用了 OopMapBlock :
1.2、 GC时引用对象收集流程
解决完引用对象的可达性问题我们来看引用对象是怎么被发现和收集到 pendling 队列中的 即文章开始提到的【图 0-0—— 引用流程图】中图示{3}的细节。
1.2.1、标记阶段
这个阶段中GC线程和应用线程并发执行并没有产生 STWStop The world这个阶段主要做的是找到可以回收的引用对象并全部收集起来。 如下图所示我们假设有2个GC-ROOT分别为GC-ROOT_1和GC-ROOT_2 一个_discovered_list 队列用于临时存放 GC 线程在并发标记过程中发现需要回收的 Reference 对象 每一个 GC 线程都有一个_discovered_list 并发标记结束之后这些 GC 线程就会将各自在 _discovered_list 中收集到的 Reference 对象统一转移到 pending 队列中以便后续ReferencHandler线程消费 _discovered_list入队条件 Reference 对象引用的 referent 没有被 GC 标记过图示 obj_cReference 对象的状态不能是 inactive, 也就是说这个 Reference 对象还没有被应用线程处理过Reference 之前没有加入过 _discovered_list图示WeakReference_xreferent 不存在任何强引用链图示 obj_creferent指的就是obj_c内存充足的前提下referent 不存在任何软引用(若内存不足就忽略这条) _nonstatic_oop_maps 是 InstanceKlass 对象的变量存放就是我们之前提到的 OopMapBlock 数组 GC线程从GC-ROOT_1出发标记对象标记对象obj_a、obj_b状态为存活obj_c、obj_p、obj_q、obj_d、obj_f对象均不可达遍历到引用对象SofeReference_a发现SofeReference_a的referent即对象obj_b为存活所以放弃将SofeReference_a加入到_discovered_list队列中遍历到引用对象WeakReference_b发现WeakReference_b的referent即对象obj_c还未标记先将SofeReference_b加入到_discovered_list队列中这里采用头插法同理引用对象WeakReference_z、WeakReference_x 也加入到_discovered_list队列WeakReference_y不会遍历到已经是垃圾了不会入队到_discovered_list队列GC线程从GC-ROOT_2出发标记对象FinalReference_q对象因为后续要执行obj_q.finalize()方法所以需要将obj_q对象重新标记为复活状态同时将FinalReference_q对象加入到_discovered_list队列 1.2.2、二次确认阶段
本阶段将进一步确认阶段一中的_discovered_list将标记错误Reference对象的移出队列标记正确的Reference对象将其referent置为null即断开与obj对象的连接 注意FinalReference对象 FinalReference 对象不会断开与obj的连接方便后面执行obj.finalize()方法当后面执行完obj.finalize()方法后referent 才会被置为 null , 在下一轮 GC 的时候 这个 FinalReference 对象以及它的 referent (obj_q)对象就会被 GC 掉 如下图所示惊不惊喜意不意外SofeReference_a 对象竟然在我们的队列里这明显有问题啊问题是上面我们说了放弃将SofeReference_a 入队啊怎么回事 当阶段一中图示{3} 标记快于 {1}遍历到SofeReference_a 时发现obj_b还未标记为可达有可能进入_discovered_list 队列 WeakReference_x 对象也是标记错误的因为阶段一中应用线程并未暂停所有应用线程有可能将WeakReference_x自己处理了我们收集引用对象的目的就是为了给应用线程后续处理既然应用线程提前处理了那GC线程没必要多此一举 1.2.3、汇总阶段
在阶段一我们提到过每一个 GC 线程都有一个_discovered_list所以需要将这些线程的_discovered_list统一收集到一起放在_pending_list中然后再将数据转移到_reference_pending_list腾出_pending_list空间方便下次GC使用。
至此我们已经可以回答文章开始提到的问题了。
1.3、 软引用GC时的处理
我们都知道在内存不足时只有软引用的对象才会被回收那什么才是内存不足或者说只有软引用的对象在什么情况下一定会被回收我们需要有一个量化标准 发生Full GC时一定会回收软引用这很明显毋庸置疑 只有软引用的对象存活时间达到我们设定的生命周期阈值 JVM提供了参数-XX:SoftRefLRUPolicyMSPerMB 可以设置每 MB 的堆内存剩余空间允许只有软引用的对象存活的最大时长默认为 1000 单位为毫秒MS 参数-XX:SoftRefLRUPolicyMSPerMB 中有LRU说明这个参数可以按照LRU(Least Recently Used即最近最少使用)策略调整即并非所有的软引用对象一起被GC掉 参数-XX:SoftRefLRUPolicyMSPerMB 中有PerMB所以我们GC时需要计算剩余空间二者乘积就是我们要的最终结果 举例-XX:SoftRefLRUPolicyMSPerMB2000剩余空间为20MB则存活时间为40秒20 * 2000
1.3.1、 软引用LRU策略 如上图所示SoftReference 中有两个字段
clockclock 字段是由 JVM 来设置的在每一次发生 GC 的时候JVM 都会去更新这个时间戳。timestamp每次调用get()方法获取referent时更新为上次GC的时间 对于当前只有软引用的对象而言如果 clock - timestamp 剩余空间 * SoftRefLRUPolicyMSPerMB 时则当前只有软引用的对象就可以直接回收了也就是可以加入到我们在1.2.1小节中提到的_discovered_list 队列中了