JVM常用垃圾收集器及GC算法解读
作者:云淡风qin
GC的种类
1.Minor GC
对象从新生代区域消失的过程,我们称之为 “minor GC”。
清理整个YouGen(年轻代)
的过程,eden、S0\S1的清理都会由于MinorGC Allocation Failure(YoungGen区内存不足),而触发minorGC。
2.Major GC
OldGen区内存不足,触发Major GC。
3.Full GC
Full GC 是清理整个堆空间—包括年轻代和永久代。
Full GC 触发的场景:
1)手动调用 System.gc()
2)promotion failed
(年代晋升失败,比如eden区的存活对象晋升到S区放不下,又尝试直接晋 升到Old区又放不下,那么Promotion Failed,会触发FullGC)
3)CMS的Concurrent-Mode-Failure
由于CMS回收过程中主要分为四步:
- 1.CMS initial mark
- 2.CMS Concurrent mark
- 3.CMS remark
- 4.CMS Concurrent sweep。在2中gc线程与用户线程同时执行,那么用户线程依旧可 能同时产生垃圾,如果这个垃圾较多无法放入预留的空间就会产生CMS-Mode-Failure, 切换 为SerialOld单线程做mark-sweep-compact。
4)新生代晋升的平均大小大于老年代
的剩余空间 (为了避免新生代晋升到老年代失败) 当使用G1,CMS 时,FullGC发生的时候 是 Serial+SerialOld。 当使用ParalOld时,FullGC发生的时候是ParallNew +ParallOld.
GC的判定
1.引用计数法:指的是如果某个地方引用了这个对象就+1,如果失效了就-1,当为 0 就会回收但是 JVM 没有用这种方式,因为无法判定相互循环引用(A 引用 B,B 引用 A)的情况。
2.引用链法(可达性分析/根搜索): 通过一种 GC ROOT 的对象(方法区中静态变量引用的对象等-static 变量)来判断,如果有一条链能够到达 GC ROOT 就说明该对象不能回首,不能到达 GC ROOT 就说明可以回收
GCRoots有哪些
- 类,由系统类加载器加载的类。这些类从不会被卸载,它们可以通过静态属性的方式持有对象的引用。
- 注意,一般情况下由自定义的类加载器加载的类不能成为GC Roots
- 线程,存活的线程
- Java方法栈中的局部变量或者参数
- JNI方法栈中的局部变量或者参数
- JNI全局引用
- 用做同步监控的对象
- 被JVM持有的对象,这些对象由于特殊的目的不被GC回收。这些对象可能是系统的类加载器,一些重要的异常处理类,一些为处理异常预留的对象,以及一些正在执行类加载的自定义的类加载器。但是具体有哪些前面提到的对象依赖于具体的JVM实现。
但是当满足上述条件时,一个对象比不一定会被回收
。
当一个对象不可达 GC Root 时,这个对象并不会立马被回收
,而是出于一个死缓的阶段,若要被真正的回收需要经历两次标记。
- 如果对象在可达性分析中没有与 GC Root 的引用链,那么此时就会被第一次标记并且进行一次筛选,筛选的条件是是否有必要执行 finalize()方法。当对象没有覆盖 finalize()方法或者已被虚拟机调用过,那么就认为是没必要的。
- 如果该对象有必要执行 finalize()方法,那么这个对象将会放在一个称为 F-Queue 的对队列中,虚拟机会触发一个 Finalize()线程去执行,此线程是低优先级的,并且虚拟机不会承诺一直等待它运行完,这是
- 因为如果 finalize()执行缓慢或者发生了死锁,那么就会造成 F- Queue 队列一直等待,造成了内存回收系统的崩溃。GC 对处于 F-Queue 中的对象进行。
第二次被标记,这时,该对象将被移除”即将回收”集合,等待回收。
GC垃圾收集器
新生代收集器
1.Serial 垃圾收集器(单线程、复制算法)
串行收集器是最古老,最稳定以及效率高的收集器,使用停止复制方法,只使用一个线程去串行回收;垃圾收集的过程中会Stop The World(服务暂停)
;参数控制:使用-XX:+UseSerialGC可以使用Serial+Serial Old模式运行进行内存回收(这也是虚拟机在Client模式下运行的默认值)
缺点:是串行效率较低。
2.ParNew 垃圾收集器(Serial+多线程)
ParNew收集器其实就是Serial收集器的多线程版本,使用停止复制方法。新生代并行,其它工作线程暂停。
参数控制:使用-XX:+UseParNewGC
开关来控制使用ParNew+Serial Old
收集器组合收集内存;使用-XX:ParallelGCThreads
来设置执行内存回收的线程数。
3.Parallel Scavenge 收集器(多线程复制算法、高效)JDK1.8 默认采用
的新生代收集器。
ParallelScavenge收集器类似ParNew收集器,Parallel收集器更关注CPU吞吐量,即运行用户代码的时间/总时间,使用停止复制算法。可以通过参数来打开自适应调节策略,虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整这些参数以提供最合适的停顿时间或最大的吞吐量;也可以通过参数控制GC的时间不大于多少毫秒或者比例。
参数控制:使用-XX:+UseParallelGC开关控制使用Parallel Scavenge+Serial Old收集器组合回收垃圾(这也是在Server模式下的默认值);使用-XX:GCTimeRatio
来设置用户执行时间占总时间的比例,默认99,即1%的时间用来进行垃圾回收。使用-XX:MaxGCPauseMillis
设置GC的最大停顿时间(这个参数只对Parallel Scavenge有效),用开关参数-XX:+UseAdaptiveSizePolicy
可以进行动态控制
,如自动调整Eden/Survivor比例,老年代对象年龄,新生代大小等,这个参数在ParNew下没有。
老年代收集器
1.Serial Old收集器(单线程标记整理算法 )
老年代收集器,单线程收集器,串行,使用"标记-整理"算法(整理的方法是Sweep(清理)和Compact(压缩),主要是运行在 Client 默认的 java 虚拟机默认的年老代垃圾收集器。
2.Parallel Scavenge 收集器
多线程机制与Parallel Scavenge差不错,使用标记整理
(与Serial Old不同,这里的整理是Summary(汇总)和Compact(压缩),汇总的意思就是将幸存的对象复制到预先准备好的区域,而不是像Sweep(清理)那样清理废弃的对象)算法,在Parallel Old执行时,仍然需要暂停
其它线程。Parallel Old在多核计算中很有用。这个收集器是在JDK 1.6中,与Parallel Scavenge
配合有很好的效果。
参数控制: 使用-XX:+UseParallelOldGC
开关控制使用Parallel Scavenge +Parallel Old组合收集 器进行收集。
3.CMS 收集器(多线程标记清除算法)
Concurrent mark sweep(CMS)收集器是一种年老代垃圾收集器,其最主要目标是获取最短垃圾回收停顿时间
,和其他年老代使用标记-整理算法不同,它使用多线程的标记-清除算法。
整个过程分为6个步骤,其中初始标记、重新标记这两个步骤仍然需要“Stop The World
”
- 初始标记
- 只是标记一下 GC Roots 能直接关联的对象,速度很快,仍然需要暂停所有的工作线程。
- 并发标记
- 进行 GC Roots 跟踪的过程,和用户线程一起工作,不需要暂停工作线程。
- 重新标记
- 为了修正在并发标记期间,因用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,仍然
- 需要暂停所有的工作线程。
- 并发清除
- 清除 GC Roots 不可达对象,和用户线程一起工作,不需要暂停工作线程。由于耗时最长的并发标记和并发清除过程中,垃圾收集线程可以和用户现在一起并发工作,所以总体上来看CMS 收集器的内存回收和用户线程是一起并发地执行。
优点:并发收集、低停顿
缺点:产生大量空间碎片、并发阶段会降低吞吐量
4.G1收集器
Garbage first 垃圾收集器是目前垃圾收集器理论发展的最前沿成果,相比与 CMS 收集器,G1 收集器两个最突出的改进是:
- 基于标记-整理算法,不产生内存碎片。
- 可以非常精确控制停顿时间,在不牺牲吞吐量前提下,实现低停顿垃圾回收。
G1特点:
- G1通过将内存空间分成区域(Region)的方式避免内存碎片问题
- Eden, Survivor, Old区不再固定、在内存使用效率上来说更灵活
- G1可以通过设置预期停顿时间(Pause Time)来控制垃圾收集时间避免应用雪崩现象
- G1在回收内存后会马上同时做合并空闲内存的工作、而CMS默认是在STW(stop the world)的时候做
- G1会在Young GC中使用、而CMS只能在O区使用
G1 收集器避免全区域垃圾收集,它把堆内存划分为大小固定的几个独立区域,并且跟踪这些区域的垃圾收集进度,同时在后台维护一个优先级列表,每次根据所允许的收集时间,优先回收垃圾最多的区域。区域划分和优先级区域回收机制,确保 G1 收集器可以在有限时间获得最高的垃圾收集效率。
GC垃圾收集器的选择
JVM给了三种选择:串行收集器、并行收集器、并发收集器。
但是串行收集器只适用于小数据量的情况,所以这里的选择主要针对并行收集器和并发收集器。
吞吐量优先的并行收集器
如上文所述,并行收集器主要以到达一定的吞吐量为目标,适用于科学技术和后台处理等。Parallel Scavenge + ParallelOld
响应时间优先的并发收集器
如上文所述,并发收集器主要是保证系统的响应时间,减少垃圾收集时的停顿时间。适用于应用服务器、电信领域ParNew + CMS
GC垃圾收集算法
标记-清除算法 (Mark Sweep)
算法分为2个阶段:
- 1.标记处需要回收的对象
- 2.回收被标记的对象。
标记算法分为两种:
1.引用计数算法(Reference Counting)
2.可达性分析算法(Reachability Analysis)。由于引用技术算法无法解决循环引用的问题,所以这里使用的标记算法均为可达性分析算法。
缺点:产生了大量的非连续内存,内存碎片化严重,后续可能发生大对象不能找到可利用空间的问题。
复制算法 (Copying)
为了解决效率与内存碎片问题,复制(Copying)算法出现了,它将内存划分为两块相等的大小,每次使用一块,当这一块用完了,就讲还存活的对象复制到另外一块内存区域中,然后将当前内存空间一次性清理掉。这样的对整个半区进行回收,分配时按照顺序从内存顶端依次分配,这种实现简单,运行高效。
不过这种算法将原有的内存空间减少为实际的一半,代价比较高。
缺点:可用内存变为原来的一半,长期存活的对象复制频率高
标记-整理算法(Mark-Compact)
标记阶段和 Mark-Sweep 算法相同,标记后不是清理对象,而是将存活对象移向内存的一端。然后清除端边界外的对象
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。