提示:参考深入理解Java虚拟机
Java虚拟机垃圾收集器
垃圾回收的区域
程序计数器,虚拟机栈,本地方法区都是随着线程生命周期的,而且在类的结构确定的时候就已经确定了,所以垃圾回收区域不包含这几个区域;Java堆和方法区有很明显的不确定性,堆存储的是对象实例,方法区的运行时常量池也在堆中,因为涉及到接口不同的实现类,需要的内存不同,方法执行时不同的分支需要的内存也不一样,只有运行时才知道会创建哪些对象。所以这部分是动态的,也是垃圾回收的主要区域
一、判断对象是否存活(回收前做的第一件事)
有两种比较出名的算法:
-
引用计数法:给对象添加引用计数器,一引用就加一,失效就减一;但是一般主流的虚拟机都不采用这个方法。这个方法虽然很简单,但是有些复杂情况没有考虑,比如对象间循环引用
-
可达性算法(主流商用语言的选择):
通过GC Root的跟对象作为起点集合,从这些节点开始根据引用关系向下搜索,搜索走过的路径叫做引用链,如果一个对象没有和GC Root有任何的引用链相连,就证明这个对象不会被使用了,可以回收掉(在前面OOM实战文章的代码中我们把对象放在list里面,这个list是Java方法运行时的局部变量,这个局部变量就可以作为GCRoot集)
可以作为GCRoot的对象: -
虚拟机栈本地变量引用的对象
-
方法区中类静态属性引用的对象
-
方法区中常量引用的对象(比如字符串常量池的引用)
-
本地方法栈引用的对象
-
虚拟机内部引用–基本数据类型对用的class对象,系统类加载器
-
所有被同步锁(synchronized)持有的对象
-
反映Java虚拟机内部情况的JMXBean等
(1.2后引用分强(new的),软,弱,虚(在localTHread中应用)
二、缓刑阶段
用可达性算法判断对象不可达,不会马上杀死对象,最少会有两次标记过程
可达性判断不可达标记,然后在标记的对象里面筛选这个对象有没有必要执行finalize()方法—覆盖finalize()或者没有被执行过finalize()方法
如果有必要执行,就会加入一个队列等死
但是如果在第二次标记前重新被引用就可以逃脱死刑
代码:
/**
* @author jc
*/
public class SaveObject {
private static SaveObject Save = null;
public void isAlive() {
System.out.println("我还活着!!!");
}
@Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("执行finalize()");
Save = this;
}
public static void main(String[] args) throws InterruptedException {
Save = new SaveObject();
Save = null;
System.gc();
Thread.sleep(500);
if (Save != null) {
Save.isAlive();
} else {
System.out.println("我没了!!!!");
}
Save = null;
System.gc();
Thread.sleep(500);
if (Save != null) {
Save.isAlive();
} else {
System.out.println("我没了!!!!");
}
}
}
结果
总结
大概描述了垃圾回收的主流思想:先判断对象该不该死,然后执行死刑