前言
阅读《深入理解Java虚拟机》相关笔记。
判断哪些对象应该被收集。
引用计数算法
简单的一种判断对象是否应该被回的方法是引用计数算法。在对象中添加一个引用计数器,只要存在对该对象的应用,计数加一,引用失效时,计数减一。任何时刻计数器为零的对象就是不可能再被使用的。问题是单纯的引用计数算法无法较好的解决相互循环引用的问题。
/**
* testGC()方法执行后,objA和objB会被回收
* /
public class ReferenceCountingGC{
publci Object instance = null;
private static final int _1MB = 1024*1024;
// 占用内存,便于观察回收现象
private byte[] bigSize = new byte[2*_1MB];
public static void testGC(){
ReferenceCountingGC objA = new ReferenceCountingGC();
ReferenceCountingGc objB = new ReferenceCountingGC();
objA.instance = objB;
objB.instance = objA;
objA = null;
objB = null;
System.gc();
}
}
可达性分析算法
通过一系列称为“GC Roots”的根对象作为起始节点集,从这些节点开始,根据引用关系向下搜索,搜索过程所走过的路径称为“引用链”,如果某个对象到GC Roots间没有任何引用链相连,或者用图论的话来说就是从GC Roots到这个对象不可达时,则证明此对象是不可能再被使用的。
确定好GC Roots,从GC Roots开始向引用遍历,如果不能到达的对象,则被认为不可达,是可以被回首的对象 。
GC Roots:
- 在虚拟机栈(栈帧中的本地变量表)中引用的对象,譬如各个线程被调用的方法堆栈中使用到的参数、局部变量、临时变量等;
- 在方法区中类静态属性引用的对象,譬如Java类的引用类型静态变量;
- 在方法区中常量引用的对象,譬如字符串常量池(String Table)里的引用;
- 在本地方法栈中JNI(即通常所说的Native方法)引用的对象;
- Java虚拟机内部的引用,如基本数据类型对应的Class对象,一些常驻的异常对象(比如NullPointException、OutOfMemeoryError)等,还有系统类加载器;
- 所有被同步锁(synchronized关键字)持有的对象;
- 反映Java虚拟机内部情况的JMXBean、JVMTI中注册的回调、本地代码缓存。
除此之外还有一些临时性对象比如分代收集和局部回收中被其他区域对象引用。
引用类型
JDK1.2之前:如果reference类型的数据中存储的数值代表的是另外一块内存的起始地址,就称该reference数据是代表某块内存、某个对象的引用。
JDK1.2之后:
- 强引用是最传统的“引用”定义,是指在程序代码中普遍存在的引用赋值,即类似“Object obj = new Object()”。只要存在就不会回收掉的对象
- 软引用描述一些还有用,但非必须的对象。只有软引用关联着的对象,在系统将要发生内存溢出异常前,会把这些对象列入回收范围内进行二次回收,若内存仍然不足则抛出内存异常。SoftReference类实现软引用。
- 弱引用也描述非必须对象,但是强度比软引用更弱一点,被弱引用关联的对象只能生存到下一次垃圾收集发生为止。WeakReference类实现弱引用
- 虚引用也称为“幽灵引用”,最弱的引用关系,不会对生存时间造成任何影响。为一个对象设置虚引用关联的唯一目的只是为了能在这个对象被收集器回收时收到一个系统通知。PhantomReference类实现虚引用。
跳出回收
在可达性分析后如果不连通,则会被标记,然后进行一次筛选,条件是是否有必要执行finalize()方法。假如没有finalize()方法,或者finalize()方法已经被虚拟机调用过,就“没必要执行”。
有必要执行finalize()方法,对象会被放置F-Queue中,会有低优先级的Finalizer线程执行finalize()方法,之后会进行二次标记。finalize()方法是最后一次跳出回收的机会,如果在finalize()方法中重新与引用链上的对象建立关联即可,譬如将自己(this)赋值给某个类型变量或者对象的成员变量。
/**
* 1. 对象可以在被GC时自我拯救
* 2. 机会只有一次,因为一个对象的finalize()方法最多只会被系统自动调用一次
*
/
public class FinalizeEscapeGC {
public static FinalizeEscapeGC SAVE_HOOK = null;
public void isAlive(){
System.out.println("yes, i am still alive :)");
}
@Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("finalize method executed!");
FinalizeEscapeGC.SAVE_HOOK = this;
}
public static void main(String[] args) throws Throwable{
SAVE_HOOK = new FinalizeEscapeGC();
// 对象第一次成功拯救自己
SAVE_HOOK = null;
System.gc();
//因为Finalizer方法优先级很低,暂停0.5s,以等待
Thread.sleep(500);
if(SAVE_HOOK != null){
SAVE_HOOK.isAlive();
}else{
System.out.println("no. i am dead :(");
}
// 下面代码与上面完全相同,但因为finalize()方法只执行一次所以自救失败
SAVE_HOOK = null;
System.gc();
//因为Finalizer方法优先级很低,暂停0.5s,以等待
Thread.sleep(500);
if(SAVE_HOOK != null){
SAVE_HOOK.isAlive();
}else{
System.out.println("no. i am dead :(");
}
}
}
finalize()方法运行代价高昂、不确定性大,无法保证各个对象的调用顺序,被官方明确声明为不推荐使用语法。
本文探讨了Java虚拟机中的引用计数算法和可达性分析在垃圾回收中的作用,包括解决循环引用问题,GCRoots的定义,以及引用类型(强、软、弱、虚引用)的区别。重点讲解了对象回收的判断策略和finalize()方法在垃圾回收中的角色。





