本章介绍了垃圾收集的算法,几款JDK1.7中的垃圾收集器特点以及动作原理。通过代码验证实例验证了Java虚拟机中自动分配及回收的主要规则。
哪些内存需要回收?什么时候回收?如何回收?
对象已死吗
引用计数算法
给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值加1,失效计数器就减1。
弊端:它很难解决对象之间相互循环引用的问题
可达性分析算法
通过一系列的“GCRoots“的对象作为起点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GCRoots没有任何引用链相连时,则证明此对象是不可用的。
GCRoots对象包括:
- 虚拟机栈中引用的对象
- 方法区中类静态属性引用的对象
- 方法区中常量引用的对象
- 本地方法中JNI引用的对象
引用类型
- 强引用(Object obj = new Object())
- 软引用(SoftReference类)
- 弱引用(WeakReference类)
- 虚引用(PhantomReference类)
标记对象死亡
第一次标记:进行可达性分析后发现没有GC Roots。
第二次标记:是否需要执行finalize()方法,需要则放F-Queue队列->Finalizer线程去执行(用try-finnaly替代finalizer()方法)。
回收方法区
- 永久代回收内容
- 废弃容量
- 无用的类
- 无用的类
- 该类所有的实例都已经被回收
- 加载该类的ClassLoader已经被回收
- 该类对应的java.lang.Class对象没有在任何地方被引用
- 配置参数:-verbose: class -XX:+TraceClassLoading、-XX:+TraceClassUnLoading
垃圾收集算法
标记-清除算法
复制算法示意图
标记-整理算法
分代收集算法
- 新生代:复制算法
- 老年代:标记-清理或者标记-整理
HotSpot的算法实现
枚举根节点
一组称为OopMap的数据结构,在类加载完成时,HotSpot就把对象内什么偏移量上是什么类型的数据计算出来,在JTI编译过程中,也会在特定的位置记录下栈和寄存器中哪些位置是引用。安全点
HotSpot没有为每条指令都生成OopMap,只是在特定的位置记录了这些信息,这些位置称为安全点。
选定标准:是否具有让程序长时间执行(指令序列复用)的特征
确保GC发生时所有线程都”跑“到最近的安全点上再停顿下来。
存在问题:当程序”不执行“时,无法响应JVM的中断请求?安全区域
是指在一段代码片段之中,引用关系不会发生变化。
垃圾收集器
收集算法是内存回收的方法论,垃圾收集器就是内存回收的具体实现。
Serial收集器(单线程收集器)
最基本、发展历史最悠久的收集器。
优点:简单而高效。
缺点:停顿时间长。
ParNew收集器(Serial多线程版本)
特征:
- Serial收集器所有参数(收集算法、Stop The World等)
- 除Serial收集器外,只有它能与CMS收集器配合工作
- 新生代收集器
Parallel Scavenge收集器
是一个新生收集器,它是使用复制算法的收集器,又是并行的多线程收集器。
目标:达到一个可控制的吞吐量(吞吐量=运行用户代码时间/运行时间+垃圾收集时间)
配置参数:-XX: MaxGCPauseMillis、-XX: GCTimeRatio
Serial Old收集器
Serial收集器的老年代版本,单线程和“标记-整理”算法。
作用:
- JDK1.5以及之间版本与Parallel Scavenge收集器搭配使用
- 作为CMS收集器的后备预案
Parallel Old收集器(JDK1.6中才开始使用)
Parallel Scavenge收集器的老年代版本,多线程和“标记-整理”算法。
CMS收集器
是一种以获取最短回收停顿时间为目标的收集器,标记-清除算法。
特征:重视服务的响应速度。
过程:
- 初始标记(Stop The World)
- 并发标记(Stop The World)
- 重新标记
- 并发清除
优点:并发收集、低停顿
缺点: - CMS收集器对CPU资源非常敏感
- CMS收集器无法处理浮动垃圾,可能出现失败而导致另一次Full GC的产生
- 浮动垃圾:并发清理阶段产生的新垃圾
- 收集结束时会有大量空间碎片产生
G1收集器(JDK7u4达到商用)
是当今收集器技术发展的最前沿成果之一,被视为JDK1.7虚拟机的一个重要进化,是面向服务端应用的垃圾收集器。
特征:
- 并行与并发
- 分代收集
- 空间整合(标志-清理、标记-整理混用)
- 可预测的停顿
区别:
- 将整个Java堆划分为多个大小相等的独立区域
- 可以有计划避免在整个Java堆中进行全区域的垃圾收集。
- 把内存“化整为零”的思路
过程:
- 初始标记
- 并发标记
- 最终标记
- 筛选回收
理解GC日志
- [DefNew:Default New Generation,Serial收集器
- [ParNew:Parallel New Generation,ParNew收集器
- PSYoungGen:Parallel Scavenge收集器(老年代和永久代同理)
垃圾收集器参数总结
内存分配与回收策略
- 对象优先在Eden分配
- 大对象直接进入老年代
- 长期存活的对象将进入老年代
- 动态对象年龄判断
- 空间分配担保
对象优先在Eden分配
- Minor GC:新生代的垃圾收集动作
- Major GC|Full GC:老年代GC(比Minor GC慢10倍以上)
大对象直接进入老年代
需要大量连续内存空间的Java对象(很长的字符串及数组)。
配置参数:-XX:PretenureSizeThreshold
长期存活的对象将进入老年代
内存回收根据对象年龄(Age)计数器来记录对象的年龄,默认15岁。
参数配置:-XX: MaxTenuringThreshold
动态对象年龄判断
相同年龄所有对象大小的总和大于Survivor空间的一半,直接进入老年代(不用等年龄到15)。
空间分配担保
当老年代空间<新生代晋升空间,会根据配置参数HandlePromotionFailure来担保是否继续操作。
配置参数:HandlePromotionFailure