JAVA中的垃圾回收器(1)

一)垃圾回收器概述:

1.1)按照线程数来区分:

串行回收指的是在同一时间端内只允许有一个CPU用于执行垃圾回收操作,此时工作线程被暂停,直至垃圾回收工作结束,在诸如单CPU处理器或者较小的应用内存等硬件平台不是特别优越的场合,出行回收器的性能表现可以超过并行回收期和并发回收器,所以串行回收默认被应用在客户端的client模式下面的JVM中,但是在并发能力比较强的CPU上,并行回收器产生的停顿时间要大于串行回收器,和串行回收相反,并行收集可以运用多个CPU同时进行垃圾回收,因此提升了应用的吞吐量,不过并行回收仍然和串行回收一样,采用独占式,会造成STW;

1.2)按照工作模式来分,可以分为并发式垃圾回收器和独占式垃圾回收器:

并发式垃圾回收器可以和用户线程交替进行工作,尽可能的来减少应用线程的停顿时间

独占式垃圾回收器一旦运行,就停止应用程序中的所有用户线程,直到垃圾回收器GC完成

JAVA中的垃圾回收器(1)_第1张图片

 

吞吐量:运行用户代码的时间占总运行时间的比例,总运行时间=程序的运行时间(a)+GC垃圾回收的总时间(b)

暂停时间=STW时间

收集频率:回收的频率低,不代表一次GC的时间短,大学洗衣服

一次攒一快洗(时间比较长)VS经常洗(一天一洗,时间比较短),频率越高,STW时间短一点

 

1)吞吐量:吞吐量越大越好,就是用户线程所执行的时间在整个JVM生命周期中所占用的时间越长,那么垃圾频率就越低,但是每一次执行GC,那么用户线程停止,STW时间就越长(类比于洗衣服),一次性的暂停时间就很长,用户体验感就可能很差劲,但是高吞吐量单位时间内用户线程做的事情更多

2)低延迟:注重每一次的暂停时间变短,用户线程暂停时间短,那么垃圾回收GC的频率就越高,因为暂停时间短,每一次GC都收集不了多少垃圾,但是线程频繁切换也需要时间,每一次本来就注重低延迟,要求GC垃圾回收短,况且线程上下文切换还消耗时间,每一次GC垃圾又回收不了多少,那么最终一共的STW时间肯定会比吞吐量的STW时间长(类比于洗衣服)高吞吐量和低延迟是矛盾的

这就类似于洗衣服,从宿舍去水房的时间和从水房回到宿舍的时间就类似于线程切换

1)高吞吐量比较好是因为这会让应用程序的最终用户感觉只有应用线程在做生产性工作,直觉上,吞吐量越高程序运行越快

2)低暂停时间比较好是因为从嘴中用户的角度来看不管是GC还是其他原因来说导致一个应用被挂起始终是不好的,这取决于应用程序的类型,有的时候甚至于说短暂的200毫秒暂停都有可能直接打断终端用户的体验,因此具有低的较大的暂停时间是日常重要的,特别是一个交互性的应用程序

3)不幸的是高吞吐量和低暂停时间是一对相互竞争的目标或者说是矛盾,如果以高吞吐量优先,那么必然需要降低垃圾GC收集的时间频率,每一次垃圾收集的时间长一些,这也会导致GC需要更长时间的来执行GC

4)相反,如果以低延迟为主要目标,那么为了降低每一次内存回收时候的暂停时间,只能频繁的进行内存回收,但是这又引起了年轻代的内存的缩减和导致最终吞吐量的下降

在设计或者使用GC算法的时候,必须要确定目标,一个GC算法只可能针对于两个目标之一,就是只是关注于较大吞吐量和最小暂停时间或者找到一个二者的初衷,现在的标准是在最大吞吐量的有限的情况下,降低停顿时间

和用户交互的程序,延迟要短一些,争取在垃圾回收的过程中多线程回收

有的是服务器端,吞吐量要高一些

G1垃圾回收器就是可以保证在给定停顿时间的基础上,尽量的提高吞吐量

JAVA中的垃圾回收器(1)_第2张图片

JDK7之前,实线,Serial OLD GC是CMS的后备方案

在JDK9中取消了红线组合

在JDK14中绿线会被删除

CMS和PSGC框架不同,不可以一起使用,PNGC和PSGC性能差不多

CMS:不能是在老年代空间满的时候进行使用,需要提前进行回收,因为CMS是并发的,在回收的时候用户线程还在执行,用户线程还有可能制造新的垃圾,所以需要提前进行回收,那如果说回收的比较晚,垃圾制造的速度比回收的速度还要快,可能CMS回收失败一旦失败,所以要使用SOGC作为备用方案,赶紧把用户线程停下来进行全部GC,应该达到一定阈值以后回收,单核CPU是单线程垃圾收集器比多线程垃圾收集器要高,因为防止进行大量的线程切换;

Serial和Serial old单线程垃圾回收器

ParNew针对于单线程的升级版本是多线程的垃圾回收器:

Parallel Scavenge/Parallel Old:吞吐量优先的垃圾回收期,以回收内存为主,速度比较低,这个垃圾回收器只是保证了吞吐量,但是实际程序是让用户有最少的等待时间

CMS:垃圾回收器可以保证最小的等待时间,就是快,不影响用户久等,不需要将垃圾全部清除掉,多进行几次GC不就行了嘛,需要手动指定

为什么CMS和Parallel Scavenge不能一起用,设计理念不同

G1:可控垃圾回收时间的垃圾回收器(JDK 9以后HotSpot默认的垃圾的回收器)

分成多个区,为什么分区算法是可控的?因为分区算法里面有很多区,再进行垃圾回收的时候,假设一共有4个区,他不会保证在这一次GC将A B C D四个区域的垃圾全部回收,而是保证的是可控时间,但是会保证时间到了就罢工,如果时间允许的话,G1垃圾回收器会多回收几个区域,如果时间不允许,我少干一点活,到点就下班;

分代算法为什么时间不可控?

ZGC:停顿时间极短,不超过10ms情况下尽量提高垃圾回收吞吐量的垃圾回收器

二)Serial(新生代单线程垃圾回收器)+Serial Old(老年代单线程垃圾回收器)

新生代使用serial的时候老年代默认使用Serial Old,在执行的时候必须停止所有的用户线程

Serial是最基本,历史最久远的垃圾回收器了,JDK1.3以前是回收新生代唯一的选择

Serial垃圾回收器是作为HotSpot虚拟机Client模式下默认的新生代垃圾回收器

Serial垃圾收集器采用复制算法,串行回收和STW机制的方式执行垃圾回收

除了年轻代以外,Serial垃圾收集器还提供了用于老年代垃圾收集的Serial Old垃圾回收器,Serial Old垃圾收集器同样也是采用了串行回收和STW机制,老年代使用的是标记整理算法

Serial Old是用于运行在客户端模式下面的默认的老年代的垃圾收集器

Serial Old在Server模式下面的主要有两个用途:

1)和新生代的Parallel Scavenge配合使用

2)作为CMS老年代收集器的后备垃圾收集方案

这俩收集器完全就是一个单线程的垃圾收集器,但是他的单线程的意义并不仅仅只是他只会使用一个CPU和一条收集线程来去完成垃圾收集工作,更重要的是在它进行垃圾收集的时候必须停止其他的工作线程,直到它垃圾收集结束

JAVA中的垃圾回收器(1)_第3张图片

-XX:PrintCommandLineFlags

-XX:+UseSerialGC

表明新生代使用Serial GC,老年代使用Serial Old GC

然后可以通过jps验证一下,jinfo -flag UseSerialGC +进程的ID

总结:只是适合于单核CPU,对于交互性比较强的应用而言,这种垃圾收集器是不能接受的,一般在JAVA WEB应用程序中是不会使用这种串行垃圾收集器的

优点:简单而高效,和其他收集器的单线程相比,对于限定单个CPU的环境来说Serial收集器由于没有线程交互的开销,专心于做垃圾收集自然可以活得最高的单线程执行收集效率,运行在客户端模式下的虚拟机是一个不错的选择,在用户的桌面应用场景中,可用于内存不大,可以在较短时间内完成垃圾收集,只要不是频繁的发生,使用穿行回收器是可以接受的

在HotSpot虚拟机中,使用-XX:+UseSerialGC参数可以指定新生代和老年代都是用串行垃圾收集器等价于新生代使用Serial GC老年代使用Serial Old GC

缺点:串行垃圾回收器会导致STW

三)parNew新生代收集器

1)ParNew新生代并行垃圾回收器+和Serial Old单线程串行垃圾回收器或者是CMS(老年代并行垃圾回收器一起使用

2)如果说Serial GC是年轻代中的单线程垃圾收集器,那么ParNew收集器就是Serial收集器的多线程版本,Par是Parallel的缩写,New是回收新生代

3)ParNew垃圾回收器是新生代的多线程垃圾回收器和Serial 没啥区别,在年轻代也是使用复制算法,STW,它是很多JVM在Server模式下面的新生代的默认的垃圾回收器

JAVA中的垃圾回收器(1)_第4张图片

1)对于新生代来说,回收次数频繁,使用并行方式比较高效,对于老年代,回收次数少,使用串行的方式高效,因为CPU并行需要切换资源,穿行可以省去切换线程资源

2)ParNew在服务器端模式下是多核CPU的场景,这个时候就不和客户端一样是一个单线程的垃圾回收器了,服务器端硬件更多一些,在老年代可以使用CMS或者是Serial Old,在JDK9中Serial Old不能再和ParewNew使用了,在JDK14CMS也被移除了,这个时候ParNew就比较尴尬了,对于新生代,使用多线程垃圾回收器,使得GC的时间更短,垃圾回收更高效STW时间更短,但是在老年代,标记整理算法效率比较差,涉及到内存碎片整理,所以说就是用单线程的了,单CPU:同一时刻只能由一个线程执行

3)设置线程数量不要超过CPU核数,防止多个线程抢夺CPU,和CPU核数相同越好

-XX:PrintCommandLineFlags -XX:+UseParNewSerialGC -XX:+UseConcMarkSweepGC

1)ParNew收集器运行在多CPU模式下,可以充分的利用多CPU,多核心等物理硬件资源优势,可以更快速的完成垃圾收集,来提升程序的吞吐量

2)但是在单个CPU的换进修改,ParNew收集器不必Serial收集器更高效,虽然Serial收集器是基于穿行回收,但是由于CPU不需要频繁的进行切换任务,因此可以有效地避免多线程交互过程中产生的一些额外开销,当前除了Serial以外,目前只有ParNew可以和CMS垃圾收集器配合工作

3)在程序中,开发人员可以通过选项-XX:+UseParNewGc来指定使用ParNew收集器来执行内存回收任务,他表示年轻代使用并行收集器并不影响老年代

4)使用-XX:ParallelGCThreads来限制线程数量,默认开启和CPU数据相同的线程数

四)Parallel Scavenge并行新生代垃圾回收器和Parallel Old老年代垃圾回收器:

1)吞吐量优先用户线程执行时间越长越好,JDK8默认GC,一开始来说Parallel Scavenge吞吐量的并行新生代垃圾收集器,一开始它是搭配Serial Old一起来做垃圾收集的,但是Parallel Scavenge本身在新生代是并行垃圾回收器,老年代用了一个串行的垃圾回收器,就不太好,所以最终Parallel Old老年代并行垃圾回收器出现了,Parallel Scavenge收集器在JDK1.6提供了用于执行老年代垃圾收集的Parallel Old垃圾收集器,用来代替来年代的Serial Old垃圾收集器,Parallel本身也是采用了标记压缩算法,STW;

2)Parallel Scavenge和ParNew垃圾回收器性能差不多,它们都是回收新生代的,但是底层使用的GC框架是不同的,是自成一派的,包括G1也是自成一派的;

3)HotSpot年轻代中除了拥有ParNew收集器是基于并行回收的以外,Parallel Scavenge收集器本身也采用了复制算法,并行回收和STW机制;

那么Parallel收集器的出现是否多此一举呢?

1)和ParNew收集器不同,Parallel Scavenge收集器的目标则是先打到一个可控制的吞吐量,他也是被称之为是吞吐量优先的垃圾回收器

2)自适应调节策略也是Parallel Scavenge的一个重要区别,就是在整个JVM运行的过程中,根据当前运行的情况来做一个性能监控,来调整内存的分配情况,来达到最优的策略;

1)凡是提高吞吐量的:一定是和后台运行的,不和用户交互性场景强的,高吞吐量可以高效的利用CPU时间,尽快完成程序的运算任务意味着一次STW暂停时间可能长一些主要适合那些在后台计算而不需要交互的任务(但是暂停时间优先就是适用于那些交互性比较强的任务)比如说那些执行批量处理,订单处理,工资支付,科学计算的程序,在这种模式下,年轻代的大小,Eden和Survivor的比例、晋升老年代的对象年龄等参数会被自动调整,已达到在堆大小、吞吐量和停顿时间之间的平衡点,自适应调节策略也是Parallel Scavenge收集器与ParNew收集器的一个重要区别

2)自适应调节策略:会根据当前JVM的运行情况进行性能监控

动态地去调整内存分配情况意识和达到最优的策略,内存分配情况来到到低延迟或者是吞吐量优先的策略,和用户交互性强的:保证低延迟,暂停时间要短,保证在服务后台进行数据运算的,要保证高吞吐量;

为什么在JDK1.6的时候要使用Parallel Old收集器代替Serial Old收集器呢?

在Parallel Old出来之前肯定是只能使用Serial Old收集器

意义非常大,因为此时如果没有Parallel Old的时候新生代垃圾回收器使用Parallel,而老年代使用Serial Old,此时使用Serial Old会存在一定的问题,因为既然新生代使用了Parallel,那么后台肯定是不和用户做大量交互的服务器端(交互性比较弱的)进行使用,通常服务器端硬件的配置比较高,肯定是多核CPU,如果服务器配置比较低的话,单核CPU,那么新生代老年代直接都是用串行垃圾回收不就行了吗,在一个高性能的场景下硬件配置比较高,多核CPU,新生代使用并行垃圾回收器效率比较高,充分利用CPU多核资源,老年代使用串行垃圾回收器,此时的性能肯定得不到最大的提升更好地发挥,拖累服务器性能的效果了,达不到最大吞吐量的一个效果了,更好的对硬件性能做发挥;

JAVA中的垃圾回收器(1)_第5张图片

在吞吐量优先的应用场景中,也就是服务器完成任务的数量越多越好,Parallel收集器和Parallel Old收集器的组合在服务器模式下的内存回收性能比较不错

参数设置:就是-XX:+UseParallelGC和-XX:+UseParallelOldGC当一个参数开启以后,另一个参数也会默认开启

JAVA中的垃圾回收器(1)_第6张图片

JAVA中的垃圾回收器(1)_第7张图片

最好CPU核数等于垃圾回收线程数,但是还是可以空闲出一些资源留给其他任务来执行

JAVA中的垃圾回收器(1)_第8张图片

-XX:+PrintCommandLineFlags -XX:+ParallelGC

JAVA中的垃圾回收器(1)_第9张图片

根据下面参数来了解自适应调节策略:

1)-XX:MaxGCPauseMillis:设置垃圾收集器最大停顿时间,就是STW的时间,默认是毫秒,此参数设置需要谨慎,为了尽可能地把停顿时间控制在MaxGCPauseMills以内,收集器在工作的时候会调整堆和其他的一些参数,对于用户来说,停顿时间越短,体验感越好,但是在服务器端,我们注重与整体的高并发,整体的吞吐量,所以服务器端适合于Parallel进行控制

2)-XX:GCTTimeRatio:垃圾收集时间占用的总时间的比例,用来衡量吞吐量的大小,默认是垃圾回收时间不超过1%,与前一个-XX:MaxGCPauseMillis参数具有一定的矛盾性,暂停时间越长,Radio参数就容易超过设定的比例

垃圾收集时间和吞吐量是互补的,吞吐量是对于服务器端来说执行任务越多越好

3)假设此时要是设置最大停顿时间是10ms,再进行垃圾回收的时候会尽可能的想办法把时间控制在10ms以内,要想实现垃圾回收的时间比较短,每一次垃圾回收比较短,那么就只能控制堆的大小,想让停顿时间短,那么垃圾回收的时间就比较短,垃圾回收器把堆控制的小一些,每一次GC时间就比较短,但是堆空间比较小,堆空间容易满,但是可能经常发生GC,GC频率增高,这样子吞吐量反而降低了,就会导致用户线程执行的总时长比较短,所以第一个参数使用需要谨慎;

4)自适应调节策略:尽量开发中的满足吞吐量和停顿时间,具有自动调节功能

5)-XX:+UseAdaptiveSizePolicy设置Parallel Scavenge的比例,京生老年代的对象的年龄等参数会被自动调整,已达堆大小,吞吐量和停顿时间之间的平衡点,再手动调优比较困难的场合,可以直接使用这种自适应的方式,进指定虚拟机的最大堆,目标的吞吐量和停顿时间,让虚拟机自己完成调优工作;

你可能感兴趣的:(java,开发语言)