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

前端岗面试30万字原题含答案

前端岗面试30万字原题含答案

我们正处在前端发展的一个微妙节点。 曾几何时,几句 HTML、CSS 加个 jQuery 特效就能轻松拿 Offer;后来,掌握 Vue 或 React 便能成为市场宠儿。但现在,当你翻开这本“前端岗面试30万字原题含答案”时,我们所面对的前端世界,已经悄然变成了一场 “冰与火之歌”。 大环境的“冰”:在存量博弈中寻找缺口 当下的技术招聘市场,用一个字形容就是 “卷”。互联网行业从野蛮生长步入精耕细作,HC(招聘名额)紧缩,而涌入的求职者却依旧庞大。大厂不再仅仅为了业务扩张而招人,更看重候选人的不可替代性。 你不仅要与同级的毕业生竞争,还要与众多因公司业务调整而释放出来的、经验丰富的中高级开发者同台竞技。这就导致了一个现象:面试难度呈指数级上升。以前“背八股”就能通关,现在面试官更擅长从一个简单的知识点出发,逐步深挖到你知识体系的盲区。 面试的“火”:从“会用”到“

By Ne0inhk
GoWeb必备理论

GoWeb必备理论

关于goweb,你不得不知道的知识 若是初学者可以借鉴GoWeb查阅本文。 HTTP状态码: 意义 每个状态码都是,http设计者对“网络通讯”中可能出现的情况的假设、预判。他就相当于现实世界的信号灯,就像大家一遇到404,就知道资源找不到了。一遇到500就知道服务器挂了。这种共识,也就是如今万维网的高效率的基础之一。 http状态码是日常开发,修改bug,的居家必备神器。咱们对常见状态码做了分类。 1、必须掌握的状态码 200 ok 最常见的状态码,代表请求完全正确,比如打开网页、调用api啥的。 301 moved permanently 资源永久迁移(例:访问时a.com会被从定项到b.com) 302 Found (部分资源,临时迁移) 400 Bad request(请求出错,参数缺少什么的..) 401 unauthorized(没有登入) 403 forbidden(

By Ne0inhk
Flutter for OpenHarmony:web 拥抱 Web 标准的桥梁(Wasm GC 与 DOM 互操作) 深度解析与鸿蒙适配指南

Flutter for OpenHarmony:web 拥抱 Web 标准的桥梁(Wasm GC 与 DOM 互操作) 深度解析与鸿蒙适配指南

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net 前言 随着 Flutter 3.x 全面拥抱 Wasm(WebAssembly),Dart 团队推出了全新的 package:web 来取代老旧的 dart:html。 package:web 是基于最新的 JS Interop 机制构建的,它不仅性能更好,而且兼容 Wasm GC 标准。 虽然这个库通过名字看是为 “Web” 平台的,但对于 OpenHarmony 开发者来说,了解它有着特殊的意义: 1. 混合开发:鸿蒙原生支持 ArkWeb (WebView),在 Flutter 中通过 JS互操作与 Web 页面交互是常见需求。 2.

By Ne0inhk

前端新手必学:5分钟搞定postcss-px-to-viewport

快速体验 1. 打开 InsCode(快马)平台 https://www.inscode.net 2. 点击'项目生成'按钮,等待项目生成完整后预览效果 输入框内输入如下内容: 请创建一个面向新手的postcss-px-to-viewport教学示例,要求:1. 从创建Vue/React项目开始 2. 分步讲解安装和配置过程 3. 提供最简单的配置示例 4. 包含常见错误排查方法 5. 最终输出一个可运行的demo项目。请使用最基础的配置,并添加大量注释和说明文字。 作为一名前端新手,在开发移动端页面时,最头疼的问题之一就是如何让页面在不同尺寸的设备上都能正常显示。传统的px单位在移动端适配中显得力不从心,这时候就需要用到postcss-px-to-viewport这个神器了。今天我就来分享一下我的学习心得,手把手教你如何快速上手这个工具。 1. 为什么要用postcss-px-to-viewport 在移动端开发中,我们经常需要根据设备宽度来调整元素尺寸。postcss-px-to-viewport可以将px单位自动转换为vw单位(视窗宽度单位),实现真正的响应式布局

By Ne0inhk