常见GC算法
- 引用计数法: 每个对象都有一个计数器, 对象被引用一次, 计数器+1, 当对象引用失败一次. 计数器-1, 当对象计数器等于0, 说明对象没有被应用, 就可GC
优: 运行过程中, 可随时检查对象计数器, 进行GC, 且GC过程, 应用无需暂停, 执行速度快(单个对象GC不会影响其他对象), 内存不足, OOM
缺: 存在循环引用问题(A引用B, B引用A, A=null, B=null. A,B永远不会GC), 随时都在GC, 占用CPU
- 标记清除: 先标记( 从root进行可达性分析, 标记被引用的对象), 再清除(清除那些没被引用的对象)
优; 解决循环引用问题
缺: 效率低下, 需要遍历所有对象, 碎片化严重, 清理出来的内存不连续, 当new大对象时, 容易爆OOM
- 标记压缩: 标记需要回收的区域, 进行回收, 将碎片化的空间进行压缩
优: 解决标记清除的碎片化问题
缺: 需要移动内存位置, 效率降低
- 复制算法: 将空间一分为二(只使用其中一块空间进行存储), 进行清除的时候, 将存活对象复制到另外一块空间, 原本的空间全部清除, 解决移动内存问题
优: 解决标记清除存在的内存移动问题
缺: 对空间的浪费较为严重, 不适用内存空间垃圾较少的情况
复制算法在JVM年轻代的应用
1. GC开始前, 对象分布于Eden, s0区, s1区为空
2. GC开始, Eden中的存活对象全部复制到s1中, s0区中存活对象根据他们的年龄值决定去向使用-XX:MaxTenuringThreshold设置年龄阈值, 超过该阈值, s0中对象移到老年代, 未达到, 对象则移动到s1中
3. GC完成, 清空Eden, s0区域, s0与s1交换角色, 重复步骤1, 直到"s1"被填满, 然后将"s1"中对象全部移到老年代中
优: 垃圾对象较多时, 效率高, 无碎片化
缺: 垃圾较少, 不适用, 如:老年代, s0/s1一个时刻只能使用其中一块, 内存使用率低
分代算法: 年轻代采用复制清除, 老年代使用标记清除/压缩
垃圾收集器及内存分配
- 串行垃圾收集器: GC过程, 只有一个线程工作, 且应用要停止运行(Stop-the-world), 等待GC完成.
/*** 测试GC收集器* @author regotto*/
public class GcTest {public static void main(String[] args) {ArrayList<Object> objects = new ArrayList<>();while (true) {if (System.currentTimeMillis() % 2 == 0) {//产生大量废弃对象objects.clear();} else {for (int i = 0; i < 10000; i++) {Properties properties = new Properties();properties.put("key:" + i, "value:" + System.currentTimeMillis());objects.add(properties);}}try {Thread.sleep(new Random().nextInt(100));} catch (InterruptedException e) {e.printStackTrace();}}}
}
设置VM Optional: 使用串行GC器, 打印GC细节
运行结果如下:
日志解读(使用第一行数据):
GC: 年轻代GC; Full GC: 所有空间全部GC
DefNew: 使用串行GC器
4416k -> 512k(4928k): GC前年轻代对象占4416k空间, GC年轻代对象占512k空间, 总共4928k空间
0.0019950secs: GC花费时间
7318k -> 3975k(15872k): 堆空间GC情况
- 并行垃圾回收器: 在串行GC的基础上, 变为多线程进行GC操作(存在Stop-the-world), 其余与串行GC一样
- ParNew垃圾收集器
只能在年轻代工作(只是将串行GC变为并行GC), 使用-XX:+UseParNewGC设置, 老年代依旧采用串行GC
测试代码在上一个代码的基础上修改VM options
运行过程中, 发现相比于SerialGC, ParNew在GC上存在一定的提升 - ParallelGC垃圾收集器
与ParNew一样, 新增多个与吞吐量相关的参数, 操作更加灵活
- ParNew垃圾收集器
-XX:+UseParallelGC 年轻代使用ParallelGC垃圾回收器,老年代使用串行回收器。
-XX:+UseParallelOldGC 年轻代使用ParallelGC垃圾回收器,老年代使用ParallelOldGC垃圾回收器。
-XX:MaxGCPauseMillis 设置最大的垃圾收集时的停顿时间,单位为毫秒 需要注意的是,ParallelGC为了达到设置的停顿时间,可能会调整堆大小或其他 的参数,如果堆的大小设置的较小,就会导致GC工作变得很频繁,反而可能会 影响到性能。 该参数使用需谨慎。
-XX:GCTimeRatio 设置垃圾回收时间占程序运行时间的百分比,公式为1/(1+n)。 它的值为0~100之间的数字,默认值为99,也就是垃圾回收时间不能超过1%
-XX:UseAdaptiveSizePolicy 自适应GC模式,垃圾回收器将自动调整年轻代、老年代等参数,达到吞吐量、 堆大小、停顿时间之间的平衡。 一般用于,手动调整参数比较困难的场景,让收集器自动进行调整。
测试代码与前一个一样, 只修改VM options, 运行结果如下:
- CMS垃圾处理器
CMS(Concurrent Mark Sweep): 针对老年代(对老年代GC进行改进), 使用标记清除算法, -XX:UseConcurrentMarkSweepGC设置.
执行过程如下:
InitialMarking: 标记root, 出现Stop-the-world
Marking: 标记对象, 与应用线程同时运行
preclean: 预清理, 与应用线程同时运行
finalMarking: 再次标记, 由于与应用线程同时运行, 前期的标记并不能解决问题, 此过程出现Stop-the-world
sweeping: 并发清除, 与应用线程同时运行
resizing: 调整堆大小, 清理碎片, 压缩空间
resetting: 重置, 等待触发下一次CMS, 与用户线程同时运行
程序打印的日志也是按照上面的流程, 程序运行结果如下:
G1垃圾收集器(jdk1.7使用)
G1取消传统的新生代, 老年代物理划分, 将内存空间变为若干个区域, 每个区域包含逻辑上的新生代, 老年代. G1存在YoungGC, MixedGC, FullGC, 在不同的条件下触发
优: 每一块区域存在多种状态(Old, Eden, Humongous, Survivor)解决碎片化问题, 即使在正常处理过程中, 都能解决内存压缩问题
G1的YoungGC
Eden区空间耗尽触发, EdenGC, Eden中数据移动到Survivor区(Survivor满了, 数据移动到新的Survivor区, 部分数据移动到Old区), 部分数据移动到Old区, 当前Eden清空, 变为未使用区
RememberSet(记忆集合)
RememberSet解决新生代寻找根对象的问题, 每一个区域初始化都生成一个RememberSet, 该集合保存其他对象引用"我"的记录, 扫描RememberSet就能得出对象之间的引用关系, 而不再需要对新生代, 老年代中所有对象进行扫描.
G1的MixedGC
为避免堆内存被耗尽, JVM启动MixedGC, 回收所有的Young区, 回收部分Old区(MixedGC不是FullGC).
使用-XX:InitiatingHeapOccupancyPercent=n(老年代占整个堆大小百分比阈值) 决定MixedGC什么时候触发
MixedGC分为2个步骤: 全局并发标记(前5个步骤), 拷贝存活对象(第6个步骤)
G1相关参数
-XX:+UseG1GC 使用 G1 垃圾收集器
-XX:MaxGCPauseMillis 设置期望达到的最大GC停顿时间指标(JVM会尽力实现,但不保证达到),默认 值是 200 毫秒。
-XX:G1HeapRegionSize=n 设置的 G1 区域的大小。值是 2 的幂,范围是 1 MB 到 32 MB 之间。目标是根 据最小的 Java 堆大小划分出约 2048 个区域。 默认是堆内存的1/2000。
-XX:ParallelGCThreads=n 设置 STW 工作线程数的值。将 n 的值设置为逻辑处理器的数量。n 的值与逻辑 处理器的数量相同,最多为 8。
-XX:ConcGCThreads=n 设置并行标记的线程数。将 n 设置为并行垃圾回收线程数 (ParallelGCThreads) 的 1/4 左右。
-XX:InitiatingHeapOccupancyPercent=n 设置触发标记周期的 Java 堆占用率阈值。默认占用率是整个 Java 堆的 45%。
G1日志输出参数
‐XX:+PrintGC 输出GC日志
‐XX:+PrintGCDetails 输出GC的详细日志
‐XX:+PrintGCTimeStamps 输出GC的时间戳(以基准时间的形式)
‐XX:+PrintGCDateStamps 输出GC的时间戳(以日期的形式,如 2013‐05‐ 04T21:53:59.234+0800)
‐XX:+PrintHeapAtGC 在进行GC的前后打印出堆的信息
‐Xloggc:../logs/gc.log 日志文件的输出路径
测试
代码运行完毕自动将GC日志输入到项目下的gc.log中
使用GC Easy进行分析(http://gceasy.io)
将gc.log上传该网站, 就能进行GC分析, 获取分析报告