细说 Java 引用(强、软、弱、虚)和 GC 流程(二)
一、前文回顾
在 细说Java 引用(强、软、弱、虚)和 GC 流程(一) 我们对Java 引用有了总体的认识,本文将继续深入分析 Java 引用在 GC 时的一些细节。
还是从我们在前文中提到的引用流程图里说起,这里不清楚的请回 细说Java 引用(强、软、弱、虚)和 GC 流程(一) 中查阅。

本文将重点关注图示{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线程和应用线程并发执行,并没有产生 STW(Stop 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_c;
- Reference 对象的状态不能是 inactive, 也就是说这个 Reference 对象还没有被应用线程处理过,Reference 之前没有加入过 _discovered_list,图示WeakReference_x;
- referent 不存在任何强引用链,图示 obj_c,referent指的就是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:SoftRefLRUPolicyMSPerMB=2000,剩余空间为20MB;则存活时间为40秒(20 * 2000);
1.3.1、 软引用LRU策略

如上图所示,SoftReference 中有两个字段:
- clock:clock 字段是由 JVM 来设置的,在每一次发生 GC 的时候,JVM 都会去更新这个时间戳。
- timestamp:每次调用
get()方法获取referent时,更新为上次GC的时间;
对于当前只有软引用的对象而言,如果 clock - timestamp >= 剩余空间 *
SoftRefLRUPolicyMSPerMB时,则当前只有软引用的对象就可以直接回收了,也就是可以加入到我们在1.2.1小节中提到的_discovered_list队列中了;
相关文章:
细说 Java 引用(强、软、弱、虚)和 GC 流程(二)
一、前文回顾 在 细说Java 引用(强、软、弱、虚)和 GC 流程(一) 我们对Java 引用有了总体的认识,本文将继续深入分析 Java 引用在 GC 时的一些细节。 还是从我们在前文中提到的引用流程图里说起,这里不清…...
CSS通过webkit-scrollbar设置滚动条样式
查看::-webkit-scrollbar-*各项关系 以下图为例,可以分别定义滚动条背景、滚动轨道、滚动滑块的样式。 需要先给外部容器设置高度,再设置overflow: auto,最后设置三个webkit属性。 <!DOCTYPE html> <html lang"en">…...
Win10配置VSCode的C/C++编译环境
GNU(编译器工具集合)包含了g、gcc和gdb等编译器。MinGW(Minimalist GNU for Windows)是一个适用于Windows操作系统的最小化的GNU工具集,它包括了GCC编译器(包括g)以及其他一些必要的库和工具。M…...
数据结构与算法再探(七)查找-排序
查找 一、二分查找 二分查找是一种高效的查找算法,适用于在已排序的数组或列表中查找特定元素。它通过将搜索范围逐步减半来快速定位目标元素。理解二分查找的“不变量”和选择左开右闭区间的方式是掌握这个算法的关键。 二分查找关键点 不变量 在二分查找中&a…...
【C语言】指针(5)
前言:上篇文章的末尾我们使用了转移表来解决代码冗余的问题,那我们还有没有什么办法解决代码冗余呢?有的这就是接下来要说的回调函数。 往期文章: 指针1 指针2 指针3 指针4 文章目录 一,回调函数二,qsort实现快速排序1…...
大数据组件(四)快速入门实时数据湖存储系统Apache Paimon(2)
Paimon的下载及安装,并且了解了主键表的引擎以及changelog-producer的含义参考: 大数据组件(四)快速入门实时数据湖存储系统Apache Paimon(1) 利用Paimon表做lookup join,集成mysql cdc等参考: 大数据组件(四)快速入门实时数据…...
PLC通讯
PPI通讯 是西门子公司专为s7-200系列plc开发的通讯协议。内置于s7-200 CPU中。PPI协议物理上基于RS-485口,通过屏蔽双绞线就可以实现PPI通讯。PPI协议是一种主-从协议。主站设备发送要求到从站设备,从站设备响应,从站不能主动发出信息。主站…...
前端js进阶,ES6语法,包详细
进阶ES6 作用域的概念加深对js理解 let、const申明的变量,在花括号中会生成块作用域,而var就不会生成块作用域 作用域链本质上就是底层的变量查找机制 作用域链查找的规则是:优先查找当前作用域先把的变量,再依次逐级找父级作用域直到全局…...
Scrum方法论指导下的Deepseek R1医疗AI部署开发
一、引言 1.1 研究背景与意义 在当今数智化时代,软件开发方法论对于项目的成功实施起着举足轻重的作用。Scrum 作为一种广泛应用的敏捷开发方法论,以其迭代式开发、快速反馈和高效协作的特点,在软件开发领域占据了重要地位。自 20 世纪 90 …...
LINUX安装使用Redis
参考 Install Redis on Linux | Docs 安装命令 sudo apt-get install -y lsb-release curl gpgcurl -fsSL https://packages.redis.io/gpg | sudo gpg --dearmor -o /usr/share/keyrings/redis-archive-keyring.gpgsudo chmod 644 /usr/share/keyrings/redis-archive-keyrin…...
基于java新闻管理系统,推荐一款开源cms内容管理系统ruoyi-fast-cms
一、项目概述 1.1 项目背景 在信息高速流通的当下,新闻媒体行业每天都要处理和传播海量信息。传统的新闻管理模式依赖人工操作,在新闻采集、编辑、发布以及后续管理等环节中,不仅效率低下,而且容易出现人为失误。同时࿰…...
054 redisson
文章目录 使用Redisson演示可重入锁读写锁信号量闭锁获取三级分类redisson分布式锁 package com.xd.cubemall.product.config;import org.redisson.Redisson; import org.redisson.api.RedissonClient; import org.redisson.config.Config; import org.springframework.context…...
【数据结构】(12) 反射、枚举、lambda 表达式
一、反射 1、反射机制定义及作用 反射是允许程序在运行时检查和操作类、方法、属性等的机制,能够动态地获取信息、调用方法等。换句话说,在编写程序时,不需要知道要操作的类的具体信息,而是在程序运行时获取和使用。 2、反射机制…...
java实现二维码图片生成和编解码
java实现二维码图片生成和编解码 在wutool中,封装了二维码工具类,基于google的zxing库,实现二维码图片生成、编码和解码。 关于wutool wutool是一个java代码片段收集库,针对特定场景提供轻量解决方案,只要按需选择代…...
Java基础常见的面试题(易错!!)
面试题一:为什么 Java 不支持多继承 Java 不支持多继承主要是为避免 “菱形继承问题”(又称 “钻石问题”),即一个子类从多个父类继承到同名方法或属性时,编译器无法确定该调用哪个父类的成员。同时,多继承…...
hugging face---transformers包
一、前言 不同于计算机视觉的百花齐放,不同网络适用不同情况,NLP则由Transformer一统天下。transformer是2017年提出的一种基于自注意力机制的神经网络架构,transformers库是hugging face社区创造的一个py库,通过该库可以实现统一…...
网络安全防护指南:筑牢网络安全防线(510)
一、网络安全的基本概念 (一)网络的定义 网络是指由计算机或者其他信息终端及相关设备组成的按照一定的规则和程序对信息收集、存储、传输、交换、处理的系统。在当今数字化时代,网络已经成为人们生活和工作中不可或缺的一部分。它连接了世…...
微信小程序实现拉卡拉支付
功能需求:拉卡拉支付(通过跳转拉卡拉平台进行支付),他人支付(通过链接进行平台跳转支付) 1.支付操作 //支付 const onCanStartPay async (obj) > {uni.showLoading({mask: true})// 支付接口获取需要传…...
git从本地其他设备上fetch分支
在 Git 中,如果你想从本地其他设备上获取分支,可以通过以下几种方式实现。不过,需要注意的是,Git 本身是分布式版本控制系统,通常我们是从远程仓库(如 GitHub、GitLab 等)拉取分支,而…...
【干货教程】Windows电脑本地部署运行DeepSeek R1大模型(基于Ollama和Chatbox)
文章目录 一、环境准备二、安装Ollama2.1 访问Ollama官方网站2.2 下载适用于Windows的安装包2.3 安装Ollama安装包2.4 指定Ollama安装目录2.5 指定Ollama的大模型的存储目录 三、选择DeepSeek R1模型四、下载并运行DeepSeek R1模型五、常见问题解答六、使用Chatbox进行交互6.1 …...
(LeetCode 每日一题) 3442. 奇偶频次间的最大差值 I (哈希、字符串)
题目:3442. 奇偶频次间的最大差值 I 思路 :哈希,时间复杂度0(n)。 用哈希表来记录每个字符串中字符的分布情况,哈希表这里用数组即可实现。 C版本: class Solution { public:int maxDifference(string s) {int a[26]…...
椭圆曲线密码学(ECC)
一、ECC算法概述 椭圆曲线密码学(Elliptic Curve Cryptography)是基于椭圆曲线数学理论的公钥密码系统,由Neal Koblitz和Victor Miller在1985年独立提出。相比RSA,ECC在相同安全强度下密钥更短(256位ECC ≈ 3072位RSA…...
树莓派超全系列教程文档--(62)使用rpicam-app通过网络流式传输视频
使用rpicam-app通过网络流式传输视频 使用 rpicam-app 通过网络流式传输视频UDPTCPRTSPlibavGStreamerRTPlibcamerasrc GStreamer 元素 文章来源: http://raspberry.dns8844.cn/documentation 原文网址 使用 rpicam-app 通过网络流式传输视频 本节介绍来自 rpica…...
React第五十七节 Router中RouterProvider使用详解及注意事项
前言 在 React Router v6.4 中,RouterProvider 是一个核心组件,用于提供基于数据路由(data routers)的新型路由方案。 它替代了传统的 <BrowserRouter>,支持更强大的数据加载和操作功能(如 loader 和…...
[10-3]软件I2C读写MPU6050 江协科技学习笔记(16个知识点)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16...
在Ubuntu中设置开机自动运行(sudo)指令的指南
在Ubuntu系统中,有时需要在系统启动时自动执行某些命令,特别是需要 sudo权限的指令。为了实现这一功能,可以使用多种方法,包括编写Systemd服务、配置 rc.local文件或使用 cron任务计划。本文将详细介绍这些方法,并提供…...
Python如何给视频添加音频和字幕
在Python中,给视频添加音频和字幕可以使用电影文件处理库MoviePy和字幕处理库Subtitles。下面将详细介绍如何使用这些库来实现视频的音频和字幕添加,包括必要的代码示例和详细解释。 环境准备 在开始之前,需要安装以下Python库:…...
pikachu靶场通关笔记22-1 SQL注入05-1-insert注入(报错法)
目录 一、SQL注入 二、insert注入 三、报错型注入 四、updatexml函数 五、源码审计 六、insert渗透实战 1、渗透准备 2、获取数据库名database 3、获取表名table 4、获取列名column 5、获取字段 本系列为通过《pikachu靶场通关笔记》的SQL注入关卡(共10关࿰…...
推荐 github 项目:GeminiImageApp(图片生成方向,可以做一定的素材)
推荐 github 项目:GeminiImageApp(图片生成方向,可以做一定的素材) 这个项目能干嘛? 使用 gemini 2.0 的 api 和 google 其他的 api 来做衍生处理 简化和优化了文生图和图生图的行为(我的最主要) 并且有一些目标检测和切割(我用不到) 视频和 imagefx 因为没 a…...
使用Spring AI和MCP协议构建图片搜索服务
目录 使用Spring AI和MCP协议构建图片搜索服务 引言 技术栈概览 项目架构设计 架构图 服务端开发 1. 创建Spring Boot项目 2. 实现图片搜索工具 3. 配置传输模式 Stdio模式(本地调用) SSE模式(远程调用) 4. 注册工具提…...
