本文已被Github仓库收录 https://github.com/silently9527/JavaCore
前几天写了一篇《JVM性能调优实战:让你的IntelliJ Idea纵享丝滑》,其中有对GC垃圾回收器的选择尝试,本篇我们就来详细的看看JVM中常见的垃圾回收器有哪些以及每个垃圾回收器的特点,这也是面试的时候经常被问的内容
在聊垃圾回收器之前,我们先来看看JVM堆内存的区域划分是怎么样的,看下图
-XX:MaxTenuringThreshold
设置Minor GC和Full GC的区别:Minor GC是指发生在新生代的垃圾收集行为,由于对象优先在Eden区分配,并且很多对象都是朝生夕死,所以触发的频率相对较高;由于采用的复制算法,所以一般回收速度非常快。Full GC是指发生在老年代的垃圾收集行为,Full GC的速度一般会比Minor GC慢10倍以上;所以不能让JVM频繁的发生Full GC
为了能够更好的适应不同程序的内存情况,JVM也不一定要求必须达到年龄15岁才能晋身到老年代,如果在Survivor区中相同年龄的所有对象大小总和大于Survivor区空间的一半,年龄大于或者等于这个年龄的对象将会直接进入到老年代
System.gc()
注意:大对象会直接在老年代分配内存,可以通过参数-XX:PretenureSizeThreshold
控制对象的大小,通常遇到的大对象是很长的字符串或者数组,如果分配了一大群大对象只是临时使用,生命很短暂,那么就会频繁的发生Full GC,但是此时的新生代的空间还有空闲;写代码的时候,这种情况应该避免,特别是在创建数组的时候要当心
空间担保
在新生代发生Minor GC的时候,JVM会先检查老年代中可分配的连续空间是否大于新生代所有对象的总和,如果大于,那么本次Minor GC就可以安全的执行;如果不大于,那么JVM会先去检查参数HandlePromotionFailure
设置值是否允许空间担保失败,如果允许,JVM会继续检查老年代可分配的连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,尽管这次Minor GC是有风险的,JVM也会尝试一次Minor GC;如果不允许担保失败,那么JVM直接进行Full GC
虽然担保有可能会失败,导致饶一圈才能进行GC,但是还是建议把这个参数打开,可以避免JVM频繁的Full GC
从上图可以看出:
CMS和Serial Old同为老年代回收器,为何相互会有连线呢?
这是个单线程收集器,发展历史最悠久的收集器,当它在进行垃圾收集工作的时候,其他线程都必须暂停直到垃圾收集结束(Stop The World)。
虽然Serial收集器存在Stop The World的问题,但是在并行能力较弱的单CPU环境下往往表现优于其他收集器;因为它简单而高效,没有多余的线程交互开销;Serial对于运行在Client模式下的虚拟机来说是个很好的选择
使用-XX:+UseSerialGC
参数可以设置新生代使用这个Serial收集器
ParNew收集器是Serial收集器的多线程版本;除了使用了多线程进行垃圾收集以外,其他的都和Serial一致;它默认开始的线程数与CPU的核数相同,可以通过参数-XX:ParallelGCThreads
来设置线程数。
从上面的图可以看出,能够与CMS配合使用的收集器,除了Serial以外,就只剩下ParNew,所以ParNew通常是运行在Server模式下的首选新生代垃圾收集器
使用-XX:+UseParNewGC
参数可以设置新生代使用这个并行回收器
Parallel Scavenge收集器依然是个采用复制算法的多线程新生代收集器,它与其他的收集器的不同之处在于它主要关心的是吞吐量,而其他的收集器关注的是尽可能的减少用户线程的等待时间(缩短Stop The World的时间)。吞吐量=用户线程执行时间/(用户线程执行时间+垃圾收集时间),虚拟机总共运行100分钟,其中垃圾收集花费时间1分钟,那么吞吐量就是 99%
停顿时间越短适合需要和用户进行交互的程序,良好的响应能够提升用户的体验。而高效的吞吐量可以充分的利用CPU时间,尽快的完成计算任务,所以Parallel Scavenge收集器适用于后台计算型任务程序。
-XX:MaxGCPauseMillis
可以控制垃圾收集的最大暂停时间,需要注意不要以为把这个时间设置的很小就可以减少垃圾收集暂用的时间,这可能会导致发生频繁的GC,反而降低了吞吐量
-XX:GCTimeRatio
设置吞吐量大小,参数是取值范围0-100的整数,也就是垃圾收集占用的时间,默认是99,那么垃圾收集占用的最大时间 1%
-XX:+UseAdaptiveSizePolicy
如果打开这个参数,就不需要用户手动的控制新生代大小,晋升老年代年龄等参数,JVM会开启GC自适应调节策略
Serial Old收集器也是个单线程收集器,适用于老年代,使用的是标记-整理算法,可以配合Serial收集器在Client模式下使用。
它可以作为CMS收集器的后备预案,如果CMS出现Concurrent Mode Failure,则SerialOld将作为后备收集器。(后面CMS详细说明)
Parallel Old收集器可以配合Parallel Scavenge收集器一起使用达到“吞吐量优先”,它主要是针对老年代的收集器,使用的是标记-整理算法。在注重吞吐量的任务中可以优先考虑使用这个组合
-XX:+UseParallelOldGc
设置老年代使用该回收器。
XX:+ParallelGCThreads
设置垃圾收集时的线程数量。
CMS收集器是一种以获取最短回收停顿时间为目标的收集器,在互联网网站、B/S架构的中常用的收集器就是CMS,因为系统停顿的时间最短,给用户带来较好的体验。
-XX:+UseConcMarkSweepGC
设置老年代使用该回收器。
-XX:ConcGCThreads
设置并发线程数量。
CMS采用的是标记-清除算法,主要分为了4个步骤:
初始化标记和重新标记这两个步骤依然会发生Stop The World,初始化标记只是标记GC Root能够直接关联到的对象,速度较快,并发标记能够和用户线程并发执行;重新标记是为了修正在并发标记的过程中用户线程产生的垃圾,这个时间比初始化标记稍长,比并发标记短很多。整个过程请看下图
优点
缺点
-XX:CMSInitiatingoccupancyFraction
来设置;如果回收阀值设置的太大,在CMS运行期间如果分配大的对象找不到足够的空间就会出现“Concurrent Mode Failure”失败,这时候会临时启动SerialOld GC来重新进行老年代的收集,这样的话停顿的时间就会加长。-XX:+UseCMSCompactAtFullCollecion
,如果启用,在Full GC的时候开启内存碎片整理合并过程,由于内存碎片整理的过程无法并行执行,所以停顿的时间会加长。考虑到每次FullGC都要进行内存碎片合并不是很合适,所以CMS又提供了另一个参数-XX:CMSFullGCsBeforeCompaction
来控制执行多少次不带碎片整理的FullGC之后,来一次带碎片整理GCG1是一款面向服务端应用的垃圾回收器。
虽然在G1中依然保留了新生代和老年代的概念,但是采用的是一种完全不同的方式来组织堆内存,它把整个堆内存分割成了很多大小相同的区域(Region),并且新生代和老年代在物理上也不是连续的内存区域,请看下图:
每个Region被标记了E、S、O和H,其中H是以往算法中没有的,它代表Humongous,这表示这些Region存储的是巨型对象,当新建对象大小超过Region大小一半时,直接在新的一个或多个连续Region中分配,并标记为H。Region区域的内存大小可以通过-XX:G1HeapRegionSize
参数指定,大小区间只能是2的幂次方,如:1M、2M、4M、8M
-XX:InitiatingHeapOccupancyPercent
设置阈值百分比,此参数与CMS中-XX:CMSInitiatingoccupancyFraction
的功能类似;混合GC会回收新生代和部分老年代内存,注意是部分老年代而不是全部老年代;G1会跟踪每个Region中的垃圾回收价值,在用户指定的垃圾收集时间内优先回收价值最大的region文中或许会存在或多或少的不足、错误之处,有建议或者意见也非常欢迎大家在评论交流。
最后,请朋友们不要白嫖我哟,希望朋友们可以点赞评论关注三连,因为这些就是我分享的全部动力来源🙏
我已经从零开始手写了简易版springmvc,以及编写了详细的说明文档,希望能够帮助伙伴们深入理解springmvc核心原理,有需要的朋友欢迎关注公众号:贝塔学JAVA
,回复源码
即可