# GC 需要完成的三件事情:

① 哪些内存需要回收?

② 什么时候回收?

③ 如何回收?

# GC Area

程序计数器、虚拟机栈、本地方法栈三个区域随线程而生,随线程而灭,所以这几个区域不需要过多考虑回收问题,因为线程结束时,内存自然就跟着回收了,而 java 堆和方法去不同;

# 为何 GC

在堆里存放几乎 java 中所有对象实例,垃圾收集器对其回收前需要确定这些对象之中哪些还 “存活” 着;

# 引用计数算法 - 判断对象是否存活的算法

① 实现简单,判定效率也很高;

② 使用:微软 COM 技术、使用 ActionScript3 的 FlasPlayer、Python 语言和在游戏领域被广泛应用的 Squirrel 中都使用了引用计数算法进行内存管理;

③ java 虚拟机并没有使用计数算法来管理内存(很难解决对象之间互相循环引用的问题);

package com.demo.test;
 
 
/**
 * 验证结果:虚拟机并没有因为这两个对象互相引用就不回收他们,侧面说明虚拟机并不是通过引用计数算法来判断对象是否存活的
 */
public class Test {
 
 
    public Object instance = null;
 
 
    private static final int _1MB = 1024*1024;
 
 
    // 占点内存,以便能在 GC 日志中看清楚是否被回收过
    private byte[] bigSize = new byte[2*_1MB];
 
 
    public static void testGC(){
        Test objA = new Test();
        Test objB = new Test();
        objA.instance = objB;
        objB.instance = objA;
 
 
        objA = null;
        objB = null;
 
 
        System.gc();
    }
    public static void main(String[] args) {
        testGC();
    }
}

# 可达性分析算法 - 判断对象是否存活算法

① 当一个对象到 GC Roots 没有任何引用链相连,即从 GC Roots 到这个对象不可达时,证明此对象是不可用的,会被判定为可回收对象;

# 可作为 GC Roots 的对象包括下面几种

① 虚拟机栈(栈帧中的本地变量表)中引用的对象;

② 方法区中类静态属性引用的对象;

③ 方法区中常量引用的对象;

④ 本地方法栈中 JNI(即一般说的 Native 方法)引用的对象;

# JDK1.2 之后,java 对引用的概念进行扩充

① 强引用:指程序代码中普遍存在的,类似 Object obj = new Object ();这类引用,只要存在,GC 永远不会回收;

② 软引用:描述一些还有用但不是必须的对象,在即将内存溢出时会将这些对象第二次回收,如第二次回收还没有足够内存,则抛内存溢出异常;JDK1.2 后提供了 SoftReference 类;

③ 弱引用:描述非必需对象,强度比软引用弱些,被弱引用关联的对象只能生存到下一次垃圾回收发生之前,无论当前内存是否足够,都会回收掉;JDK1.2 后提供了 WeakReference 类;

④ 虚引用:最弱,一个对象是否有虚引用完全不会对其生存时间构成影响,也无法通过虚引用来获取一个对象实例,其存在唯一目的就是能在这个对象被收集器回收时收到一个系统通知;JDK1.2 后提供了 PhantomReference 类;

# 两次标记过程

① 第一次标记:即便对象与 GC Roots 不可达,但也不是 “非死不可”,它将会被第一次标记并且进行一次筛选,筛选条件 —— 此对象是否有必要执行 finalize () 方法;

② 没有必要执行 finalize () 方法情况:

  • (1)对象没有覆盖 finalize () 方法;
  • (2)finalize () 方法已被虚拟机调用过;

③ 若有必要执行 finalize () 方法,这个对象会先进入 F-Queue 队列,并在稍后由虚拟机自动建立的、低优先级的 Finalizer 线程去执行它。

④ 执行过程:指虚拟机会触发这个方法,但并不承诺等待它运行结束,可能遇到执行缓慢或死循环,导致 F-Queue 中其他对象永久等待,甚至导致内存回收机制崩溃;

⑤ 第二次标记:GC 将对 F-Queue 中的对象进行第二次小规模标记,如对象要在 finalize () 中拯救自己,需要‌重新与引用链上的任何一个对象建立关联,否则就真被回收了;

package com.demo.test;
 
 
/**
 * 运行结果:
 * 任何一个对象的 finalize 方法只会被系统自动调用一次,如果对象面临下一次回收,它的 finalize 方法不会被再次执行
 * > finalize method executed!
 * > yes,i am still alive :)
 * > no,i am dead :(
 */
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();
        // 因为 finalize 方法优先级很低,所以暂停 0.5 秒以等待它
        Thread.sleep(500);
        if (SAVE_HOOK!=null){
            SAVE_HOOK.isAlive();
        }else {
            System.out.println("no,i am dead :(");
        }
 
 
 
 
        // 下面这段代码与上面完全相同,但是这次自救却失败了
        SAVE_HOOK = null;
        System.gc();
        Thread.sleep(500);
        if (SAVE_HOOK!=null){
            SAVE_HOOK.isAlive();
        }else {
            System.out.println("no,i am dead :(");
        }
    }
}

# 不建议使用 finalize ()

作者不建议使用 finalize () 方法拯救对象,要避免使用它,它是一种为了使 c/c++ 程序员更容易接受它所做的一个妥协,其运行代价高昂,不确定性大...,其所做的工作,我们使用 try-finally 或其他方式可以做的更好;

# 回收方法区

① 方法区 ->HotSpot 虚拟机中的永久代;

② java 虚拟机规范中说过可以不要求虚拟机在方法区实现垃圾回收,而且在方法区中进行垃圾回收 “性价比” 比较低;

③ 永久代垃圾回收分两部分:

  • 废弃常量和无用的类:与 java 堆中对象的清理类似,可以清除常量池中的其他类(接口)、方法、字段等,但是判定一个 “无用的类” 相对比较苛刻,需要满足 3 个方面才会清理:

① 该类中所有的实例都已经被回收,也就是 java 堆中不存在该类的任何实例;

② 加载该类的 ClassLoader 已经被回收;

③ 该类对应的 java.lang.Class 对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法;

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

# 垃圾收集算法

① 标记 - 清除算法

  • (1)方法:首先标记所有需要回收的对象,标记完成后统一清除;
  • (2)缺点:效率问题;空间问题(空间碎片)

② 复制算法

  • (1)方法:内存划分 2 块(Eden 空间和 Survivor 空间),每次只使用一块,当一块用完后,就将还存活着的对象复制到另一块,然后再把已使用过的内存空间一次清理掉。
  • (2)代价:将内存缩小了
  • (3)HotSpot:Eden 空间:Survivor 空间 = 8:1
  • (4)Survivor 空间不足时需要依赖其他内存(这里指老年代)进行分配担保(类似银行贷款担保)

③ 标记 - 整理算法:

  • (1)特点:根据老年代特点而指定的一种算法,标记过程与 “标记 - 清除” 算法一样,但后续是让所有存活的对象都向一端移动,然后直接清理掉边界以外的内存;

④ 分代收集算法

  • (1)当前商业虚拟机的垃圾收集都采用 “分代收集” 算法;
  • (2)一般把 java 堆分为新生代和老年代;
  • (3)新生代:每次垃圾回收都有大批对象死去,只有少量存活,可选用复制算法,只需要付出少量存货对象的复制成本就可以完成收集。
  • (4)老年代:因老年代对象存活率高、没有额外空间对它进行分配担保,就必须使用 “标记 - 清理” 或者 “标记 - 整理” 算法来进行回收;

# HotSpot 的算法实现

① 枚举根节点

  • (1)特点:敏感、GC 停顿、“一致性”,GC 进行时必须停顿所有 Java 执行线程(Sun 称为 “Stop the world”),即枚举根节点总是要停顿的;
  • (2)当前主流 java 虚拟机使用的都是准确式 GC,HotSpot 中是使用一组称为 OopMap 的数据结构来达到这个目的的(知道哪里存放着对象引用,快速且准确地完成 GC Roots 枚举);

② 安全点:

  • (1)在 “特定的位置” 记录 OopMap 信息,这里的位置就是安全点,即程序执行时并非在所有地方都停下来开始 GC,只有到达安全点时才能暂停
  • (2)安全点选定以程序 “是否具有让程序长时间执行的特征” 为标准,“长时间执行” 最明显特征就是指令序列服用,如方法调用、循环跳转、一场跳转等(所以具有这些功能的指令才会产生 Safepoint)
  • (3)如何在 GC 发生时让所有线程(不包括 JNI 调用的线程)都跑到最近的安全点上再停顿下来:有两种方案,抢先式中断和主动式中断,前者少用,后者让各个线程轮询一个标志(若为真,则自己中断挂起,轮询标志的地点与安全点重合);

③ 安全区域:

  • (1)程序没有分配 CPU 时间,如线程处于 sleep 状态或 block 状态,此时线程无法相应 jvm 的中断请求,这种情况需要安全区域解决;
  • (2)在安全区域中任意地方开始 GC 都是安全的;

# HotSpot 垃圾收集器

(JDK1.7 Update14 之后的,这个版本正式提供了商用的 G1 收集器,之前的 G1 仍处于实验状态)

(上面展示了 7 种作用于不同分代的垃圾收集器,两者之间连线的意思可以搭配使用,按其所属区域分为新生代收集器和老年代收集器)

① Serial 收集器:“单线程”,JDK1.3 开始,至今依然是虚拟机运行在 Client 模式下默认的新生代收集器,简单而高效,Client 模式下很好的选择;

② ParNew 收集器:Serial 收集器多线程版本,其他与之类似,但它是许多运行在 Server 模式下的虚拟机中首选的新生代收集器,除了 Serial 外,只有他可以与 CMS 配合工作

③ Paraller Scavenge 收集器:使用复制算法,并行的多线程收集器,目标是达到一个可控制的吞吐量(吞吐量 = 运行用户代码时间 /(运行用户代码时间 + 垃圾回收时间))

④ Serial Old 收集器:单线程,标记 - 整理算法,Client 模式下虚拟机使用,若在 Server 模式下(在 JDK1.5 及之前与 Parallel Scavenge 搭配或作为 CMS 后备方案 - 在并发发生 Concurrent Mode Failure 时使用)

⑤ Paraller Old 收集器:多线程,标记 - 整理算法,JDK1.6 提供,吞吐量优先

⑥ CMS 收集器:JDK1.5,强交互应用,划时代意义的收集器,以获取最短回收停顿时间为目标,适用于当前互联网或 B/S 系统服务端,标记 - 清除算法,运作过程(初始标记 - 并发标记 - 重新标记 - 并发清除),与用户线程并发执行,低停顿,缺点(对 CPU 资源敏感,无法处理浮动垃圾,标记 - 清除算法的空间碎片)

# G1 收集器

① 面向服务端,目标 - 替换 CMS

② 有点:并行 / 并发,分代收集,空间整合(标记 - 整理算法),可预测的停顿(几乎是实时 - RTSJ 的垃圾回收器特征)

③ 堆内存布局变化:堆分成多个大小相等独立区域 Region,虽保留老年代和新生代概念,但他们不在是物理隔离的了,G1 后台维护 Region 列表,择价值最大的 Region 回收

④ 使用 Remember Set 避免全堆扫描,每个 Region 有一个 Remember Set,

⑤ 操作步骤:初始标记 - 并发标记 - 最终标记 - 筛选回收

⑥ 反应停顿时间测试和吞吐量测试是作者从网上搜集的一些数据,真实生产环境暂无

# 理解 GC 日志

① GC 发生时间

② 停顿类型: GC,Full GC-stop-the-world

③ GC 发生区域:ParNew 等

④ GC 前该内存区域已使用容量 - GC 后已使用容量

⑤ 该内存区域 GC 所占用的时间,秒

15. 垃圾收集器参数总结参考

http://blog.csdn.net/huxian1234/article/details/17163023