内存回收常用算法:
- 引用计数
- 可达性分析(GC Root)
如何判断一个对象的死亡?
1 | 当一个对象不再被任何存活的对象继续引用的时候,这个对象就死亡了。 |
引用计数
给每一个对象添加一个计数器
- 当有对象引用它的时候,计数器+1。
- 当有对象取消对它的引用时,计数就会-1。
1 | 当计数器的值为 0 时,即说明没有对象引用它,也就是这个对象死亡了。 |
这种算法简单,但是有个重大缺陷,那就是无法解决循环引用问题。
什么是循环引用问题呢?
比如对象A 引用 对象B,对象B 引用 对象A,那么 对象A 和 对象B 的计数器都为1。但是如果后续的运行环境再也用不到对象A 和 对象B,那么就造成了内存泄漏。
1 | public class ReferenceCountGC { |
可达性分析
别名:根搜索算法,追踪性垃圾收集,GC Root。
原理
首先,我们选取一些对象,这些对象是存活的,也被称为 GC Roots,然后根据这些对象的引用关系,凡是直接或者间接跟 GC Roots 相关联的对象,都是存活的。就像图中的连通性判断一样。
如何选取GC Roots
在堆结构周围的一些结构,其中引用的对象可以作为GC Roots,具体可以概括为:
- 虚拟机栈上(确切的说,是栈帧上的本地变量表)所引用的对象
- 本地方法栈引用的对象
- 方法区中的静态属性,常量引用
- Java 虚拟机的内部引用,常用数据类型的 Class 对象,常驻的异常对象,系统类加载器
- 所有被同步锁持有的对象
- 除此之外,还有一些临时的 GC Roots 可以加入进来。比如老年代中的对象一般都存活时间比较久,也就是大概率是活着的对象,也可临时作为 GC Roots。
标记死亡后如何自救
被判定为不可达的对象,并不立刻死亡。它仍然有次机会进行自救,需要重写 finalize()。
也就是可达性算法的逻辑大致是这样的:
- 第一次进行标记,凡是不可达 GC Roots 的对象,都暂时判定为死亡,只是暂时。
- 检查暂时被判定为死亡对象,检查是否有重写 finalize()方法:
- 如果有,则触发,对象可以在里面完成自救。
- 如果没有自救成功 或者 没有重写 finalize()方法,则宣告这个对象的死亡。
- 除此之外,这个对象中的 finalize()方法,只能被调用一次,一生只有一次自救机会。
1 | public class FinalizeEscapeGC { |
1 | Finalize method executed |