垃圾回收器和算法

2020-10-28  本文已影响0人  定金喜

1.垃圾回收器种类

先看一下经典图片


垃圾回收

它们之间的连线,说明了它们之间能配合使用,每种垃圾回收器我们先讲解一番,再来讲解这张图吧。

新生代收集器:Serial、ParNew、Parallel Scavenge
老年代收集器:CMS、Serial Old、Parallel Old
整堆收集器: G1

几个重要的概念:
并行收集:多条垃圾回收线程同时工作,例如ParNew,Parallel Scavenge
等都是多线程的垃圾回收器,但是它们进行垃圾回收的时候,用户线程暂时停止服务

并发收集:用户线程和垃圾回收线程同时工作,例如CMS的并发标记阶段,是两种线程同时工作的,垃圾回收线程不会影响用户线程,没有Stop The World的情况

吞吐量:吞吐量计算公式是:执行代码的时间/(执行代码的时间+垃圾回收的时间),在互联网中吞吐量也可以理解为单位时间内处理请求的量,可以按照TPS和QPS的定义去理解,但是共同点就是吞吐量大表示能处理用户请求的量肯定是越大的,Parallel Scavenge是可以设置吞吐量的回收器,一般它不能和CMS回收器配合使用,据说是因为底层的代码架构不同导致,而且CMS比较关注的是并发处理,对于并发请求较多的场景比较适合,并发太大其实会导致吞吐量的下降(每个系统肯都有一个界限),而计算任务,定时任务这种一般不需要进行并发交互的场景,是保证吞吐量优先,一般是使用Parallel Scavenge搭配Parallel Old最佳。

1.1 Serial 收集器

历史最悠久的回收器,采用的是单线程,是工作在client模式下的默认回收器,如果对于jvm的clint模式和server模式不清楚,可以参考https://www.cnblogs.com/wxw7blog/p/7221756.html这篇文章,总而言之,一般在本地运行的时候用client模式比较好,因为启动较快,很多可能都是懒加载的方式,但是部署到服务器上的用server模式,虽然启动较慢,但是运行速度较快。
这种回收器采用复制算法,如果在单cpu的情况下,该回收器效率较快,能超过ParNew等多线程回收器,因为它不存在线程的切换等,专心做垃圾收集自然可以获得最高的单线程收集效率。但是它在进行垃圾收集时,必须暂停其他所有的工作线程,直到它结束(Stop The World)。

Serial
Serial一般配合Serial Old一起使用,但是都存在Stop The World,开发者一般在本地可以使用此回收器,真正的生产环境一般不会使用。

使用场景:
HotSpot在Client模式下默认的新生代收集器;
在用户的桌面应用场景中,可用内存一般不大(几十M至一两百M),可以在较短时间内完成垃圾收集(几十MS至一百多MS),只要不频繁发生,这是可以接受的

相关参数:
"-XX:+UseSerialGC":添加该参数来显式的使用串行垃圾收集器;

1.2 ParNew 收集器

Serial的多线程版本回收器,除了使用多线程外其余行为均和Serial收集器一模一样(参数控制、收集算法、Stop The World、对象分配规则、回收策略等)。ParNew使用的线程数默认与系统的CPU数一致,如果CPU数量较多,可以使用-XX:ParallelGCThreads来限制一下收集器使用的线程个数,和Serial一样存在Stop The World问题。而且它是除了Serial外唯一能和CMS配合使用的回收器。在单CPU的系统中,其收集效率可能没Serial高,它是首选的Server模式下的新生代收集器,生产环境中一般会与CMS组合使用。


ParNew

与CMS收集器关系:CMS作为老年代收集器,但却无法与JDK1.4已经存在的新生代收集器Parallel Scavenge配合工作,因为Parallel Scavenge(以及G1)都没有使用传统的GC收集器代码框架,而另外独立实现,而其余几种收集器则共用了部分的框架代码,所以一般CMS默认使用的年轻代收集器就是ParNew。

使用场景:
在Server模式下,ParNew收集器是一个非常重要的收集器,因为除Serial外,目前只有它能与CMS收集器配合工作;但在单个CPU环境中,不会比Serail收集器有更好的效果,因为存在线程交互开销。

相关参数:
"-XX:+UseConcMarkSweepGC":指定使用CMS后,会默认使用ParNew作为新生代收集器
"-XX:+UseParNewGC":强制指定使用ParNew
"-XX:ParallelGCThreads":指定垃圾收集的线程数量,ParNew默认开启的收集线程与CPU的数量相同

1.3 Parallel Scavenge 收集器

与吞吐量相关,所以也叫吞吐量收集器,与ParNew收集器有很多相同的地方,都是采用多线程收集,使用复制算法的新生代回收器,它的目标识达到一个可控制的吞吐量。所谓吞吐量就是CPU用于运行用户代码的时间与CPU运行的总耗时时间的比值,即吞吐量=用户代码的运行时间/(垃圾收集器时间+用户代码的运行时间)。

使用场景:
高吞吐量为目标,即减少垃圾收集时间,让用户代码获得更长的运行时间;
当应用程序运行在具有多个CPU上,对暂停时间没有特别高的要求时,即程序主要在后台进行计算,而不需要与用户进行太多交互;例如,那些执行批量处理、订单处理、工资支付、科学计算的应用程序。

相关参数:
"-XX:MaxGCPauseMillis":允许的值是一个大于0的毫秒数,收集器尽可能保证内存回收花费的时间不超过此值,但是大家不要误解为把此值设置小一点就能使系统的垃圾收集速度变得更快,降低GC的停顿时间是以牺牲吞吐量和新生代空间来换取的,系统把新生代设置小一点,收集300M的新生代肯定比500M的新生代快吧,但是这样就会导致垃圾收集的频率会高一点,原来可能需要10秒收集一次,每次停顿100毫秒,现在可能需要5秒收集一次,每次70毫秒,虽然停顿时间下降了,但是吞吐量其实是降低了
"-XX:GCTimeRatio":是一个大于0且小于100的整数,就是垃圾收集时间占总时间的比例,相当于吞吐量的倒数,例如如果把此值设置为19,那么允许的最大GC时间占总时间的比例为5%(1/(1+19)),默认值是99,就是允许最大1%(即1/(1+99))的垃圾收集时间比例。
"-XX:+UseAdptiveSizePolicy":这是一个开关参数,打开这个开关之后,就不需要手动设置新生代大小(-Xmn)、Eden和Survivor区的比例(-XX:SurvivorRatio)、晋升老年代的对象大小(-XX:PretenureSizeThreshold,默认是0,意思就是无论多大都在Eden先分配)等细节参数,虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整这些参数以提供最合适的停顿时间或者最大的吞吐量,这种调节方式叫做GC自适应调节方式。一种比较好的适合新手的方式是,可以设置好基本的内存参数(如-Xms,-Xms,-Xss等),然后设置MaxGCPauseMillis(更关注停顿时间)或者GCTimeRatio(更关注吞吐量)参数给虚拟机设定一个优化目标,具体细节参数的调整工作委托给虚拟机完成。自适应调节策略也是该收集器与ParNew收集器的一个重要区别。

1.4 Serial Old 收集器

是Serial收集器的老年代版本,采用标记-整理算法(还有压缩,Mark-Sweep-Compact),也是采用单线程收集。


Serial Old

使用场景:
主要也是使用在Client模式下的虚拟机中,也可在Server模式下使用。
作为CMS收集器的后备方案,在并发收集Concurent Mode Failure时使用,CMS的垃圾清理和引用线程是并行进行的,如果在并行清理的过程中老年代的空间不足以容纳应用产生的垃圾,则会抛出“concurrent mode failure”,老年代的垃圾收集器从CMS退化为Serial Old,所有应用线程被暂停,停顿时间变长。

1.5 Parallel Old 收集器

是从jdk1.6才开始提供的Parallel Scavenge收集器的老年代版本,采用标记-整理算法,使用的也是多线程收集。


Parallel Old

应用场景:
注重高吞吐量以及CPU资源敏感的场合,都可以优先考虑Parallel Scavenge+Parallel Old 收集器。

相关参数:
"-XX:+UseParallelOldGC":指定使用Parallel Old收集器;

1.6 CMS 收集器

一种以获取最短回收停顿时间为目标的老年代回收器,第一种能并发收集的回收器,采用标记-清除算法。目前很大一部分Java应用集中在互联网站或者B/S系统服务器上,这类应用尤其重视服务的响应速度,希望系统停顿时间最短,以给用户带来较好的体验,CMS收集器就非常适合这类应用的需要。它的运作过程较前几种收集器来说更复杂一点,整个过程有四个步骤:
初始标记:标记GC Roots能直接引用到的对象,速度很快但是存在Stop The World问题;
并发标记:进行GC Roots Tracing 的过程,找出存活对象且用户线程可并发执行;
重新标记:为了修正并发标记期间因用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,仍然存在Stop The World问题。
并发清除:对标记的对象进行清除回收
CMS收集器的内存回收过程是与用户线程一起并发执行的。


CMS

CMS的主要优点是并发,低停顿,但是它也有明显的问题:
1.对CPU资源非常敏感
2.无法处理浮动垃圾
3.产生大量的内存碎片

1.7 G1 收集器

2 垃圾回收算法

3 重要的JVM参数配置和GC日志分析

参考文章:
https://www.cnblogs.com/cxxjohnson/p/8625713.html
https://www.cnblogs.com/wxw7blog/p/7221756.html
https://www.cnblogs.com/chenpt/p/9803298.html
https://blog.csdn.net/g7n3f/article/details/50828793

上一篇下一篇

猜你喜欢

热点阅读