JVM 可达性分析算法

JVM 可达性分析算法

说实话,开源社区里面有很多人都在讲: 可达性算法中 JVM 会进行两次标记,第一次会标记所有对象,并找到继承实现了 finalize() 方法的对象,并查看该对象是否存在“自救”,这些内容都与《深入理解 Java 虚拟机》(后文简称为 ‘书’)中 3.2.4 生存还是死亡?这一小节存在出入,或者说几乎所有的博客都是通过阅读这一小节然后得到令人一知半解的回答,整个逻辑有点混乱,前后不搭,所有我打算总结一下

说明:虽然主要讲堆内存,但方法区也有 GC,只是条件更为苛刻(所有实例被回收、ClassLoader 被回收等)。

首先会先说明可达性分析算法的规则

其次会了解一下 finalize 方法,包括它的作用时机和作用次数

最后再来说一下 JVM 垃圾收集器根据 可达性分析算法 和 Java 对象机制怎么“两次标记”,怎么回收 Java 堆中的对象空间

最后的最后我会按照我的理解来完全比喻一次

可达性分析算法

垃圾收集器一般采用的是可达性分析算法,内容是: 如果某个对象没有通过引用链连接到 GC Roots 就表明这个对象不可达,所以会被判定为可回收对象

GC Roots 是一个数据结构,里面可以包含很多的内容,我们挑选其中一个:对象是否被栈内 reference 类型强引用

也就是在方法中 new 出来的对象会被强引用连接,如下图所示

根据 JVM 内存区域的分配我们可知,new Student(); 是在 Java 堆中开辟了一块内容,用于存放 Student 相关内容,(在这里需要用到方法区我没有详细标出)

Java 中取消了指针,但事实上对象也就相当于指针作用,例如变量 s 它的内容就是刚刚创建在 Java 堆上对应对象的数据

如果将 s 设置为 null 则代表将指针指向的位置设置为空,如下图

那么在这个时候 Java 堆刚刚开辟的空间 Student 就会被可达性算法检测为“GC Roots 到该对象不可达”

finalize 方法

首先,finalize 是所有类中都存在的一个方法,它只有在 GC 过程中会被触发使用,而且它只会被使用一次, 并且它已经被弃用了

在书中说到:

它并不能等同于C和C++语言中的析构函数,而是Java刚诞生时为了使传统CC程序员更容易接受 Java 所做出的一项妥协,它的运行代价高昂、不确定性大、无法保证各个对象的调用顺序,如今已被官方明确声明为不推荐使用的语法。有的教材中描述 finalize 适合做“关闭外部资源”之类的清理性工作,这完全是对 finalize 用途的一种自我安慰。finalize 能做的使用try-finally或者其他方式都可以做得更好更及时所以笔者建议大家完全可以忘掉Java语言的这个方法

同时在 Java 源码中也如是说到:

A subclass overrides the finalize method to dispose of system resources or to perform other cleanup.

子类重写finalize方法以处置系统资源或执行其他清理。

The general contract of finalize is that it is invoked if and when the Java virtual machine has determined that there is no longer any means by which this object can be accessed by any thread that has not yet died, except as a result of an action taken by the finalization of some other object or class which is ready to be finalized.The finalize method may take any action, including making this object available again to other threads; the usual purpose of finalize, however, is to perform cleanup actions before the object is irrevocably discarded. For example, the finalize method for an object that represents an input/output connection might perform explicit I/O transactions to break the connection before the object is permanently discarded.

finalize 的一般约定是:如果 Java 虚拟机确定任何尚未死亡的线程都无法再访问此对象(没被 GC Roots 引用连接),则调用 finalize。

除非是由于其他已准备就绪的对象或类的 finalize 所采取的行动。finalize方法可以采取任何行动,包括使此对象再次可用于其他线程;(这句话的核心是解释 finalize 方法的一个关键特性:对象可以在自己的 finalize 方法中“复活”自己,或者复活其他对象。)

然而,finalize 的通常目的是在对象被不可撤销地丢弃之前执行清理操作。例如,表示输入/输出连接的对象的finalize方法可能会在对象被永久丢弃之前执行显式I/O事务以断开连接。

The finalize method is never invoked more than once by a Java virtual machine for any given object.

Java虚拟机对任何给定对象调用finalize方法的次数都不会超过一次。

The use of finalization can lead to problems with security, performance, and reliability.

使用 finalize 可能会导致安全性、性能和可靠性方面的问题。

其中虚拟机并不承诺 finalize 的内容会被执行完成,如果一个对象的 finalize 方法执行非常缓慢,或者发生了死循环(例如 while(true)),将导致 F-Queue 队列一直堆积,最终可能导致整个内存回收子系统崩溃,甚至 OOM(内存溢出)。

两次标记

当满足以下两种需求时,才会触发两次标记(如果 finalize 没被重写,那么在第一次 GC 标记后就会直接给对象空间清除了)

  • Java 堆中的对象不被 GC Roots 引用连接
  • finalize 被重写

第一次标记:

  • Step 1(可达性分析): 发现对象不可达。
  • Step 2(第一次标记/筛选): 判断该对象是否有必要执行 finalize() 方法。
    • 如果对象没有覆盖 finalize() 方法,或者 finalize() 已经被虚拟机调用过,那么虚拟机视为“没有必要执行”。—— 这种情况下,对象直接被判定为“死亡”,等待回收,不会进入 F-Queue。
    • 只有“有必要执行”的对象,才会被放入 F-Queue。

接下来会由虚拟机自动建立的、低调度优先级的 Finalizer 线程去调用队列中对象的 finalize 方法,对象如果在 finalize 中将自己赋值给一个变量(即赋值给 GC Roots 中的一个对象)

第二次标记:

第二次标记所做的事情是看队列中的对象是否自救成功

当对象自救后,垃圾处理器在进行二次标记处理时就会发现该对象还存在引用,则会取消对其的空间释放

public class Zombie { static Zombie savedInstance; // 一个存活的静态引用 @Override protected void finalize() throws Throwable { System.out.println("Finalize called!"); savedInstance = this; // 关键行动:将即将被销毁的当前对象赋给静态变量 // 对象现在“复活”了! } public static void main(String[] args) throws InterruptedException { Zombie z = new Zombie(); z = null; // 删除唯一引用,z 变得不可达 // 建议GC,第一次 System.gc(); Thread.sleep(1000); if (savedInstance != null) { System.out.println("Zombie object is alive!"); } else { System.out.println("Zombie object is dead."); } // 再次尝试回收(注意:一个对象的finalize最多被JVM调用一次) savedInstance = null; System.gc(); Thread.sleep(1000); System.out.println("Process finished."); } }

从上面的两次标记过程我们不难发现,标记实际也可以被称为处理,但是只有当 finalize 被重写后的对象才存在两次标记

超绝比喻(至少我是那么觉得的)

想象这是一个超级大的,人人自危的修仙世界

  • GC Roots - 各个宗门,它会根据你的作用来决定你是否是“宗门弟子”(GC Roots 引用)
  • Java 堆 - 存在洪荒野兽的修仙世界
  • 垃圾收集器 - 一个规则怪兽,某个时间中,他会杀死所有没被宗门庇护的修仙者
  • finalize 方法 - 修仙者的“保命符”,可能是能让修仙者直接成为某个宗门的弟子,(但是只能使用一次)

Read more

Java霸主未逝:不可撼动的生态与新特性的革命潜力

Java霸主未逝:不可撼动的生态与新特性的革命潜力

🧑 博主简介:ZEEKLOG博客专家,历代文学网(PC端可以访问:https://literature.sinhy.com/#/?__c=1000,移动端可微信小程序搜索“历代文学”)总架构师,15年工作经验,精通Java编程,高并发设计,Springboot和微服务,熟悉Linux,ESXI虚拟化以及云原生Docker和K8s,热衷于探索科技的边界,并将理论知识转化为实际应用。保持对新技术的好奇心,乐于分享所学,希望通过我的实践经历和见解,启发他人的创新思维。在这里,我希望能与志同道合的朋友交流探讨,共同进步,一起在技术的世界里不断学习成长。 技术合作请加本人wx(注明来自ZEEKLOG):foreast_sea Java霸主未逝:不可撼动的生态与新特性的革命潜力 引言:在编程语言的巨变时代重新审视Java 在技术飞速演进的时代,编程语言的世界仿佛一片汹涌的海洋,每天都有新的语言和框架涌现,声称要颠覆现有秩序。从Python在数据科学和人工智能领域的崛起,到Go语言在并发处理和高性能网络服务中的优异表现,再到Rust在系统编程和安全关键型应用中的强势进攻,似乎每一种语

By Ne0inhk

AI大模型实用(三)Java快速实现智能体整理(Springboot+LangChain4j)

目录 1.1 简介 1.2 示例 步骤一: 添加pom 步骤二:配置 步骤三:流式输出 步骤四: 正常输出 步骤五: 【类似函数调用】AI Service接口 1.3 调试问题 问题1: ClassNotFoundException: dev.langchain4j.exception.IllegalConfigurationException 问题2: overriding is disabled 问题3 :dev.langchain4j.exception.IllegalConfigurationException 1.4  langchain4j与springAI对比 1.1 简介 一个基于 Java 的库,旨在简化自然语言处理(NLP)和大型语言模型(LLM)

By Ne0inhk
【Java 开发日记】阻塞队列有哪些?拒绝策略有哪些?

【Java 开发日记】阻塞队列有哪些?拒绝策略有哪些?

目录 阻塞队列有哪些? 拒绝策略有哪些? 面试回答 阻塞队列有哪些? 在Java的java.util.concurrent包里面,阻塞队列的实现挺多的,我们可以根据它的功能和结构来记,主要分这么几类: 1. 按容量划分: * 有界队列: 就是队列有固定的容量。 * ArrayBlockingQueue: 最经典的一个,底层是数组,创建时必须指定大小。它的生产和消费用同一把锁,性能相对稳定。 * LinkedBlockingQueue: 底层是链表,它既可以是有界的(构造时指定容量),也可以默认是无界的(默认是Integer.MAX_VALUE,几乎相当于无界)。它的生产和消费用了两把锁,在高并发场景下吞吐量通常比ArrayBlockingQueue更高。 * 无界队列: 理论上是无限的,只要内存够就能一直放。 * PriorityBlockingQueue: 一个支持优先级排序的无界队列。元素必须实现Comparable接口,或者构造时传入Comparator。它出队的顺序是按优先级来的,不是先进先出 * DelayQueue: 一个很特殊的队

By Ne0inhk
Java网络聊天室——OverThinker-ChatRoom

Java网络聊天室——OverThinker-ChatRoom

—项目专栏— 🚀 Java Chatroom 实时聊天室系统 一个基于 Spring Boot 和 WebSocket 技术实现的轻量级实时聊天室项目。 ✨ 项目概述 这是一个采用 前后端分离 架构的 Web 聊天应用。它专注于提供一个稳定、实时的消息通信平台,支持用户认证、好友管理、以及核心的一对一私聊功能。 特性描述实时通信基于 WebSocket 实现,消息秒级推送。核心功能用户注册登录、好友列表、私聊会话、消息历史记录。后端架构Spring Boot 配合 MyBatis,快速构建 RESTful API。前端技术传统 HTML/CSS/JavaScript + jQuery,轻量易维护。 📸 界面展示 (Screenshots) 登录与注册 登录页面 注册页面 聊天主界面 ⚡ 项目体验说明 先看说明!

By Ne0inhk