1 对象存活判断
在Java堆中存放的对象,垃圾收集器工作时,需要判定哪些还活着,哪些已死去,从而对死去的对象执行回收,常用的对象存活判断方法如下
1.1 引用计数算法
这种算法给对象添加一个引用计数器,当有地方引用时,计数值加1,当引用失效时,计数值减1,当计数器为0时,对象就不在被使用。这种算法的优点在于实现简单,判定效率高。缺点在于很难解决对象之间的循环引用问题
1.2 可达性分析算法
这种算法通过GC Roots对象为起点,从这些节点往下搜索,搜索经过的路径称为引用链,当一个对象没有被引用链相连时,则证明对象不可用。在Java中GC Roots对象包括以下几种:
- 虚拟机栈(栈帧中的本地变量表)中引用的对象
- 方法区中的类静态属性引用的对象
- 方法区中常量引用的对象
- 本地方法栈中JNI引用的对象
1.3 引用分类
JDK1.2之后,扩展了引用的概念,以支持当内存空间还足够时,则能保留在内存之中;如果内存空间在进行垃圾收集后还是非常紧张,则可以抛弃这些对象
- 强引用:强引用指的是new指令出来的引用,只要引用还在,垃圾回收器就不会回收掉对象
- 软引用:软引用在系统发生内存溢出前,将这些对象进行回收,如果还不够将抛出内存溢出异常
- 弱引用:弱引用只能存活在下一次垃圾回收之前,不管内存是否足够
- 虚引用:虚引用不影响对象生存时间,也无法通过虚引用取得对象实例,在虚引用关联的对象被回收时,将会收到一个系统通知
1.4 回收方法区
方法区主要回收两部分内容:废弃常量和无用的类,判定废弃常量是通过判断没有任务String对象引用该常量时,则代表废弃。判定无用的类需要满足以下三个条件:
- 该类的所有实例都被回收
- 该类的ClassLoader被回收
- 该类的Class对象没有被引用
2 垃圾收集算法
2.1 标记清除算法
标记清除算法分为两个阶段
- 标记:首先标记出所有要回收的对象
- 清除:在标记完成后,统一清除所有被标记的对象
该算法主要的不足:
- 效率:标记和清除两个过程效率都不高
- 空间:标记清除后产生大量内存碎片,碎片太多导致大对象不够分配时,将触发另一个垃圾收集动作
2.2 复制算法
通过将内存划分成两个相同大小的两块,每次只使用其中一块,当一块使用完后,将存活的对象复制到另一块上,然后集中回收使用完的那块。
该算法主要的不足:
- 空间:可用的内存缩小了一半
该算法主要的优点:
- 简单:不用考虑内存碎片,只需移动堆顶指针,运行高效
现代商业的虚拟机采用这种算法回收新生代,通过将内存划分一个较大的Eden区,和两个较小的Survivor区,每次只使用Eden区和其中一个Survivor区,在回收时将存活的对象复制到另一个Survivor区,并清除Eden和当前Survivor区,HotSpot虚拟机默认Eden和Survivor比例为8:1:1,所以只会有10%的内存浪费。当Survivor区不够用时,需要依赖老年代进行内存担保
2.3 标记整理算法
标记整理算法分为两个阶段:
- 标记:首先标记出所有要回收的对象
- 整理:将所有的存活对象向一端移劝,然后清理掉端边界以外的内存
2.4 分代收集算法
根据对象的存活周期不同将内存划分成几块,一般分为新生代和老年代,根据不同年代的特点,选用不同的算法
- 新生代:使用复制算法,因为存活的对象不多,只有少量,复制代价小
- 老年代:使用标记清理或标记整理算法,因为存活对象多,复制代价大
3 垃圾收集器
垃圾收集算法是方法论,垃圾收集器是具体的实现
3.1 Serial收集器
Serial收集器使用复制算法并工作在新生代中,它使用单线程来回收,它只会使用一个CPU或一条收集线程去完成垃圾收集工作,在进行垃圾收集时,必须暂停其他所有的工作线程,直到它收集结束,常用于运行在Client模式下的虚拟机
3.2 ParNew收集器
ParNew收集器使用复制算法并工作在新生代中,它是Serial收集器的多线程版本,该收集器可以与CMS收集器配合,在单个CPU的环境下表现不一定比Serial好,因为多了线程切换的开销
3.3 Parallel Scavenge收集器
Parallel Scavenge收集器使用复制算法并工作在新生代中,它的目标是达到一个可控制的吞吐量(吞吐量 = 运行用户代码时间 / 总时间;其中总时间 = 运行用户代码时间 + 垃圾收集时间)
3.4 Serial Old收集器
Serial Old收集器使用的是标记整理算法并工作在老年代中,它同样是单线程收集器,可以在JDK 1.5前与Parallel Scavenge收集器搭配,也可以作为CMS收集器的后备方案
3.5 Parallel Old收集器
Parallel Old收集器是Parallel Scavenge收集器的老年代版本,使用多线程和标记整理算法
3.6 CMS收集器
CMS(Concurrent Mark Sweep)收集器使用的是标记清理算法,工作于老年代中,目标是获取最短的回收停顿时间。
CMS的工作过程分为以下四步:
- 初始标记:标记GC Roots直接关联的对象
- 并发标记:进行GC Roots Tracing
- 重新标记:修正并发标记期间因用户程序继续运行而导致的变动
- 并发清除
CMS收集器主要的不足:
- 对CPU资源敏感
- 无法处理浮动垃圾
- 无法处理空间碎片
3.7 G1收集器
G1收集器将整个Java堆划分成多个大小相等的独立区域(Region),新生代和老年代不在物理隔离,而是同属于Region。
G1收集器的工作过程如下:
- 初始标记:标记GC Roots直接关联的对象
- 并发标记:进行GC Roots Tracing
- 最终标记:修正并发标记期间因用户程序继续运行而导致的变动
- 筛选回收:回收阶段首先对各个Region的回收价值和成本进行排序,根据用户所期望的GC停顿时间来制定回收计划
G1收集器主要的特点:
- 并行与并发:
- 分代收集:
- 空间整合:
- 可预测的停顿:不仅追求低停顿,还能预测停顿时间