对于垃圾回收,JVM 是如何判断是否已经死亡了或者无需使用,而回收空间呢?主要有两种方法
引用计数法
引用计数法就是对于对象,如果有变量引用它,则对于这个对象的引用计数加一。如果在引用的计数为 0 的时候,则该变量则是已死亡,可以回收了。
实际流程这样的,有一个引用,被赋值某一对象,那么该对象的引用计数器加一。一个引用指向某一对象,如果修改这个引用的指向,那么将该对象的引用计数减一。
所以要获取全部的引用更新操作。相应增减目标对象引用计数器。
- 缺点
- 需要额外的空间存储计数器
- 繁琐的更新
- 无法处理循环对象
- a,b 相互引用,除此没有指向 a,b 的情况,a,b 实际已经死了。但是引用计数器不为 0。内存泄露
可达性分析
主流的采取的是可达性分析。
实质将一系列 GC Roots 作为初始存活对象合集,从该集合出发。探索能够到达的被该集合引用的对象,标记(mark),并将其加入该集合中。最终未被探索到的被定义为死亡,可以被回收。
GC Roots
简单的理解为是堆外指向对内的引用。一般包含如下几种
- Java 方法栈帧中的局部变量
- 已加载类的静态变量
- JNI handles
- 已经启动未停止的 Java 线程
但是这里又有问题,在多线程的环境下,会产生误报(把引用设置为 null,标记好后,其它线程产生了垃圾,即将活的变死了,这种内存是不会释放的),或者漏报(如果指向一个新的对象,这个对象可没有被标记为不能回收,垃圾回收器就直接给回收掉了)。就麻烦了。
面对这种问题,Java 虚拟机提出的方式是 stop-the-world
stop-the-word 及安全点
也就是,停止其他非垃圾回收的线程工做,直至完成垃圾回收。也就是垃圾回收的暂停时间(GC pause)
这种是通过安全点机制实现的。当 Java 请求垃圾回收(stw)时候,会等待所有线程达到安全点,才也许 stw 线程独占工作。
这里安全点并非是让其他线程停下,而是找到一个稳定的执行的环境状态。在该状态下堆栈不发生变化。
例如本地方法,Java 程序执行 JNI 执行本地代码,如果这段代码没有引用对象、调用 Java 方法或访问 Java 原方法。那么堆栈结构就不会发生变化,那么就是一个安全点。
只要不离开安全点,虚拟机回收垃圾就能和运行本地代码同时执行。还要其他,例如
- 解释执行字节码
- 执行即使编译器生成的机器码
- 线程阻塞
本文深入探讨了JVM如何判断对象是否可回收,重点介绍了引用计数法和可达性分析两种方法。引用计数法简单直观,但无法处理循环引用问题;可达性分析则从GC Roots出发,标记所有可达对象,未被标记的对象被视为垃圾。同时,文章讨论了多线程环境下产生的误报和漏报问题,以及Java虚拟机通过stop-the-world机制解决这些问题。

被折叠的 条评论
为什么被折叠?



