简单的梳理JVM(四)——GC回收回收机制
目录
概述
垃圾收集 Garbage Collection 通常被称为“GC”,它诞生于1960年 MIT 的 Lisp 语言,经过半个多世纪,目前已经十分成熟了。
JVM中,程序计数器、虚拟机栈、本地方法栈都是随线程而生随线程而灭,栈帧随着方法的进入和退出做入栈和出栈操作,实现了自动的内存清理,因此,我们的内存垃圾回收主要集中于 java 堆和方法区中,在程序运行期间,这部分内存的分配和使用都是动态的。
一、GC回收流程
- 对内存中的对象判断是否有存活
- 通过收集器对非存活的对象,进行GC清理。
二、对象存活判断
1.引用计数
判断方式:
- 每个对象有一个引用计数属性,新增一个引用时计数加1,引用释放时计数减1,计数为0时可以回收。
存在问题:
- 此方法简单,无法解决对象相互循环引用的问题。
2.可达性分析
判断流程:
- 从GC Roots开始向下搜索,搜索所走过的路径称为引用链。当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的。不可达对象。
3.举个栗子
场景:
A 对象中引用了B对象,而B对象引用了C对象,C 中有引用了D对象,D中引用了B对象。
引用计数
当A对象没有引用时,清理了A对象,而B,C,D循环引用,则无法判断清理。
可达性分析
整个调用链就是 A——>B——>C——>D——>B 。整个路径的GC Root 就是A ,当A失效时,GC就会同时清理掉A,B,C,D 。
4.GC Roots包括:
- 虚拟机栈中引用的对象。
- 方法区中类静态属性实体引用的对象。
- 方法区中常量引用的对象。
- 本地方法栈中JNI引用的对象。
- 简单来说,也就是当对象出现为以上情况时,可以认为对象是存活的,不进行清理回收。否则,需要调用GC进行回收。
三、GC垃圾回收算法
1.标记 -清除算法
- 对内存中的对象进行可行分析,对判断可达的对象标记。
- 完成标记后,对未标记的(非可达对象)清理。
存在问题:
- 标记和清除的效率不高。
- 标记清除后,hui会产生很多不连续的空间碎片,导致后续分配大内存对象时,需要再次触发GC回收动作。
2.复制算法
- 把内存按照容量划分为大小相等的两块。
- 每次只使用其中一块,在快要使用完之前,把当前这块中存活的对象复制到另一块中.
- 然后把当前这块全部清理掉。
特点:
- 简单高效
主要问题:
- 内存减半,以及会对长期存活的对象,来回复制,降低效率。
3.标记压缩算法
- 对内存中对象进行标记,但不会立马处理。
- 把标记的对象移动到内存某一端。
- 清理边界之外的内存。
优点:
- 解决标记-清理算法的问题(标记清除后,hui会产生很多不连续的空间碎片,导致无法分配大内存对象)
四、GC收集器
1.Serial (-XX:+UseSerialGC)
算法使用:
- 新生代复制算法
- 老年代标记压缩算法
实现流程:
- 单线程去收集不可达的对象
- 停止所有工作线程
- 清理不可达对象
- 恢复程序正常执行
2.ParNew(-XX:+UseParNewGC)
算法使用:
- 新生代复制算法
- 老年代标记压缩算法
实现流程:
- 多线程收集不可达对象
- 停止所有工作线程
- 多线程清理不可达对象
- 恢复正常工作线程
参数控制:
- -XX:+UseParNewGC ParNew收集器
- -XX:ParallelGCThreads 限制线程数量
3.Parallel Scavenge(-XX:+UseParallelGC)
- 与ParNew收集器类似,但提供了参数可以自适应的调节策略,提高收集器的吞吐量。虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整这些参数以提供最合适的停顿时间或最大的吞吐量;
算法使用:
- 新生代复制算法
- 老年代标记压缩算法
实现流程:
- 多线程收集不可达对象
- 自适应的调节策略,选择合适的时间,停止所有工作线程
- 多线程清理不可达对象
- 恢复正常工作线程
参数控制:
- -XX:+UseParallelGC
- -XX:MaxGCPauseMillis 垃圾回收器最大停顿时间
- -XX:GCTimeRatio 吞吐量大小(0,100)默认最大99 吞吐量:(用户执行时间)/(GC暂停时间+用户执行时间)
4.Serial Old(-XX:+UseSerialOldGC)
- Parallel Old是Parallel Scavenge收集器的老年代版本,使用多线程和“标记-整理”算法。这个收集器是在JDK
1.6中才开始提供
参数控制:
- -XX:+UseParallelOldGC 使用Parallel收集器+ 老年代并行
5.CMS(重点)(-XX:+UseConcMarkSweepGC)
一种以获取最短回收停顿时间为目的的收集器。
过程步骤:
- 初始标记:仅仅标记GC ROOT能直接关联到的对象,对象往下关联的对象是不标记的。特点:会有STW,速度很快。
- 并发标记:与用户线程并行执行,对被GC ROOT直接标记的对象,向下进行标记处理。特点:无STW停顿,并发执行。
- 重新标记:修正在并发标记期间,因用户程序执行运作导致的产生变动的一部分需要标记清理对象,进行标记。特点:会有STW,但时间不长
- 并发清除:并行用户线程,执行回收处理。特点:无STW停顿,并发执行。
优点:
- 并发收集、低停顿
缺点:
- 产生大量空间碎片、并发阶段会降低吞吐量
参数控制:
- -XX:+UseConcMarkSweepGC 使用CMS收集器
- -XX:+ UseCMSCompactAtFullCollection Full GC后,进行一次碎片整理;整理过程是独占的,会引起停顿时间变长
- -XX:+CMSFullGCsBeforeCompaction 设置进行几次Full GC后,进行一次碎片整理
- -XX:ParallelCMSThreads 设定CMS的线程数量(一般情况约等于可用CPU数量)
Parallel Old(-XX:+UseParallelOldGC)
- Parallel Old收集器是Parallel Scavenge收集器的老年代版本,使用"标记-整理"算法,多核,自适应调整。
- 这个收集器是在JDK1.6版本中出现的,所以在JDK1.6之前,新生代的Parallel Scavenge只能和Serial Old这款单线程的老年代收集器配合使用。
- Parallel Old垃圾收集器和Parallel Scavenge收集器一样,也是一款关注吞吐量的垃圾收集器,和Parallel Scavenge收集器一起配合,可以实现对Java堆内存的吞吐量优先的垃圾收集策略。
G1
- 这东西有内容些多,以后新开一篇,仔细理一理。
总结
本章内容主要内容:
- 理清楚GC回收的流程以及判断依据
- 弄懂GC的三种回收算法
- 弄明白每个收集器使用了那些算法,以及各自的优缺点。