引用计数算法

原理

给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器就减1;任何时刻计数器为0的对象就是不可能再被使用的。

优点

  • 实现简单
  • 判定效率高

缺点

  • 很难解决对象之间相互循环引用的问题

可达性分析算法

商用程序语言(Java、C#、Lisp)的主流实现实现中,都是通过可达性分析(Reachability Analysis)来判定对象是否存活的。

原理

通过一些列的称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为”引用链”,当一个对象到GC Roots没有任何引用链相连(用图论的话来说,就是GC Roots到这个对象不可达)时,则证明此对象是不可用的,暂处于”缓刑”阶段。

再谈引用

JDK1.2以前

  • 如果reference类型的数据中存储的数值代表的是另外一块内存的起始地址,就称这块内存代表着一个引用。
  • 只有被引用或者没有被引用两种状态

希望:

当内存空间还足够时,则保留在内存之中;

如果内存空间在进行垃圾收集后还是非常紧张,则可以抛弃这些对象。

JDK1.2以后

  • 强引用(Strong Reference)
    • Object obj=new Object()
  • 软引用(Soft Reference)
    • 还有用但并非必须的对象
    • 在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围之中进行第二次回收。如果这次回收还没有足够的内存,才会抛出内存溢出异常
    • 加速JVM对垃圾内存的回收速度、维护系统的运行安全、防止内存溢出(OOM)等问题的产生
  • 弱引用(Weak Reference)
    • 只能生存到下一次垃圾收集发生之前
    • 当垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象
  • 虚引用(Phantom Reference)
    • 也称“幽灵引用”、“幻影引用”
    • 一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用了来取得一个对象实例。
    • 为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知

宣告对象死亡

是否执行finalize()

第一次,GC Roots无任何引用链相连,会被第一次标记并且进行一次有没有必要执行finalize()方法的筛选,finalize()方法是对象逃脱死亡的最后一次机会。

  • 没有必要执行finalize()方法
    • 没有覆盖finalize()方法
    • finalize()方法已经被虚拟机调用过
  • 有必要执行finalize()方法
    • 对象被放置在F-Queue队列中,并在稍后由一个虚拟机自动创建的、低优先级的Finalizer线程去执行
      • 虚拟机触发,并不承诺会等待运行结束
        • 如果一个对象在finalize()方法中执行缓慢或死循环,将很可能导致F-Queue队列中其它对象永久处于等待,甚至导致整个内存回收系统崩溃

是否关联到引用链

GC将对F-Queue队列中的对象进行小规模的标记

  • 如果对象重新与引用链上的任何一个对象建立关联(this赋值给某个类变量或者对象的成员变量)
    • 将被移除出“即将回收”集合。
  • 对象没有逃脱
    • 被回收

不建议使用finalize()

  • 运行代价高昂
  • 不确定性大
  • 无法保证各个对象的调用顺序
  • finalize()能做的所有工作,用try-finally或者其它方式都可以做的更好、更及时
  • 推荐忘掉finalize()的存在

对象拯救示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
/**
* output: <br/>
* finalize method executed<br/>
* alive <br/>
* dead
*/
public class FinalizeEscapeGC {
public static FinalizeEscapeGC SAVE_HOOK = null;

@Override
protected void finalize() throws Throwable {
// TODO Auto-generated method stub
super.finalize();
System.out.println("finalize method executed");
FinalizeEscapeGC.SAVE_HOOK = this;
}

public static void main(String[] args) throws InterruptedException {
SAVE_HOOK = new FinalizeEscapeGC();
// 第一次拯救
SAVE_HOOK = null;
System.gc();

// finalize优先级低,暂停当前线程等待
Thread.sleep(500);

if (SAVE_HOOK != null) {
System.out.println("alive");
} else {
System.out.println("dead");
}
/**
* 将SAVE_HOOK赋值为null后,调用gc,执行finalize()<br/>
* finalize()方法中SAVE_HOOK被赋值<br/>
* 所以判断时SAVE_HOOK不为null
*/
// 第二次拯救
SAVE_HOOK = null;
System.gc();

// finalize优先级低,暂停当前线程等待
Thread.sleep(500);

if (SAVE_HOOK != null) {
System.out.println("alive");
} else {
System.out.println("dead");
}
/**
* 将SAVE_HOOK赋值为null后,调用gc<br/>
* SAVE_HOOK的finalize已经被执行过了<br/>
* 所以这次不执行finalize()<br/>
* 所以判断时SAVE_HOOK为null<br/>
* 任何一个对象的finalize()方法都只会被系统自动调用一次<br/>
* 如果对象面临下一次回收,它的finalize()方法不会被再次执行
*/
}
}

回收方法区

回收内容

永久代的垃圾回收主要回收两部分内容

  • 废弃常量
    • 没有任何String对象引用常量池中的常量
    • 没有其他地方引用了这个字面量
  • 无用的类
    • 该类的所有实例都已经被回收
    • 加载该类的ClassLoader已经被回收
    • 该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类

参数

  • 是否对类进行回收:-Xnoclassgc
  • 查看类加载和卸载信息
    • -verbose:class
      • Product版的虚拟机中使用
    • -XX:+TraceClassLoading
      • Product版的虚拟机中使用
    • -XX:+TraceClassUnLoading
      • FastDebug版的虚拟机中使用

在大量使用反射、动态代理、CGLib等ByteCode框架、动态生成JSP以及OSGi这类频繁自定义ClassLoader的场景都需要虚拟机具备类卸载的功能,以保证永久代不会溢出。

收获

学习知识不要上来就抓细节,很多人都说这本书太老了,如今代码已经日新月异。但是不理解思想,光看变化,很容易晕头转向。