48 jvm性能优化之垃圾收集器

Stop-The-World
所谓的Stop the World机制,简称STW,即在执行垃圾收集算法时,Java应用程序的其他所有除了垃圾收集收集器线程之外的线程都被挂起。
此时,系统只能允许GC线程进行运行,其他线程则会全部暂停,等待GC线程执行完毕后才能再次运行。
这些工作都是由虚拟机在后台自动发起和自动完成的,是在用户不可见的情况下把用户正常工作的线程全部停下来,这对于很多的应用程序,尤其是那些对于实时性要求很高的程序来说是难以接受的。
所以在服务器项目中,应该设计能够去减少Stop-The-World 问题。
注意:市面上所有的垃圾收集器都有Stop-The-World问题,开发中尽量不要调用 System.gc();
,有可能会导致Stop-The-World问题。

1)停止所有的java执行线程(“stop the world”)
可达性分析必须在一致性的快照中进行,一致性指的是不可以出现分析过程中对象引用关系还在不断变化的情况。这点是导致GC进行时必须停顿所有java线程的一个原因。
2)准确式GC:当系统停下来时,不需要一个不漏的检查完所有执行上下文和全局的引用位置。HotSpot的实现是:在特定位置上(即安全点),虚拟机通过OopMap数据结构在类加载时,将对象内什么偏移量上是什么类型的数据计算出来,并存储到其中,来达到这个目的。在OopMap的协助下,HotSpot可以快速且准确的完成GCRoots的枚举。

Jdk垃圾收集器迭代版本

jdk1.7 默认垃圾收集器Parallel Scavenge(新生代)+Parallel Old(老年代)

jdk1.8 默认垃圾收集器Parallel Scavenge(新生代)+Parallel Old(老年代)

jdk1.9 默认垃圾收集器G1

-XX:+PrintCommandLineFlags 参数可查看默认设置收集器类型

-XX:+PrintGCDetails亦可通过打印的GC日志的新生代、老年代名称判断

jinfo -flag UseParallelGC 37492 | jinfo -flag UseParallelOldGC 37492 |jinfo -flag UseG1GC 37492 相关垃圾回收器参数 进程ID

-XX:+PrintCommandLineFlags 查看命令行相关参数

java -XX:+PrintCommandLineFlags -version

image.png

Jdk7种核心的收集器
1.串行回收器:Serial、Serial old (采用单线程回收垃圾 适合于堆内存空间比较小 个人小项目

2.并行回收器:ParNew、Parallel Scavenge、Parallel old 多核多线程、堆内存空间比较大
3.并发回收器:CMS、G1(分区算法) 减少GC暂停用户线程时间尽可能最短。
并发垃圾收集和并行垃圾收集的区别
(A)、并行(Parallel)
指多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态;
如ParNew、Parallel Scavenge、Parallel Old;

(B)、并发(Concurrent)

   指用户线程与垃圾收集线程同时执行(但不一定是并行的,可能会交替执行);
  用户程序在继续运行,而垃圾收集程序线程运行于另一个CPU上;    
  如CMS、G1(也有并行);
image.png

新生代收集器:SerialGC、Parallel Scavenge、ParNew
老年代收集器:SerialOldGC、Parallel old、CMS
整体收集器:G1

JDK8(含Jdk8)之前的组合关系:
Serial/Serial 01d、Serial/CMS、 ParNew/Serial 01d、 ParNew/CMS、Paral1el Scavenge/Serial 01d、Paral1el Scavenge/Parallel 01d、G1;

GC日志评估指标

1.吞吐量 运行用户代码占总时间的比例
总运行时间:程序的运行时间+内存回收的时间
比如程序运行时间100s/内存回收时间 垃圾回收1s 100/101
2.GC负荷 与吞吐量相反,指应用花在GC上的时间百分比
总运行时间:程序的运行时间+GC内存回收的时间
1/101

3.暂停时间 应用花在GC stop-the-world 的时间 暂时时间越小越好

4.GC频率 次数 GC频率越小,stw暂停时间越大
5.Footprint 一些资源大小的测量,比如堆的大小

6.反应速度 从一个对象变成垃圾道这个对象被回收的时间
Jdk7种核心的收集器
1.串行回收器:Serial、Serial old (采用单线程回收垃圾 适合于堆内存空间比较小 个人小项目

2.并行回收器:ParNew、Parallel Scavenge、Parallel old 多核多线程、堆内存空间比较大
3.并发回收器:CMS、G1(分区算法) 减少GC暂停用户线程时间尽可能最短。
并发垃圾收集和并行垃圾收集的区别
(A)、并行(Parallel)
指多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态;
如ParNew、Parallel Scavenge、Parallel Old;

(B)、并发(Concurrent)

   指用户线程与垃圾收集线程同时执行(但不一定是并行的,可能会交替执行);
  用户程序在继续运行,而垃圾收集程序线程运行于另一个CPU上;    
  如CMS、G1(也有并行);

Jdk7收集器组合的关系
image.png

串行垃圾回收器
SerialOldGC与SerialGC
最古老,最稳定、效率高、可能会产生较长的停顿
–新生代、老年代使用串行回收
–新生代复制算法
–老年代标记-压缩
-XX:+UseSerialGC
单线程在收集垃圾的时候,必须暂停当前所有的工作线程,直到清理垃圾完垃圾,工作线程才可以继续执行,Stop The World
-XX:+PrintCommandLineFlags -XX:+UseSerialGC
主要应用于:桌面应用程序堆内存空间很小

依然是HotSpot在Client模式下默认的新生代收集器;
也有优于其他收集器的地方:
简单高效(与其他收集器的单线程相比);
对于限定单个CPU的环境来说,Serial收集器没有线程交互(切换)开销,可以获得最高的单线程收集效率;
在用户的桌面应用场景中,可用内存一般不大(几十M至一两百M),可以在较短时间内完成垃圾收集(几十MS至一百多MS),只要不频繁发生,这是可以接受的
并行垃圾回收器
ParNew并行回收器
ParNew垃圾收集器是Serial收集器的多线程版本,主要用于新生代垃圾收集器
1、特点
除了多线程外,其余的行为、特点和Serial收集器一样;
如Serial收集器可用控制参数、收集算法、Stop The World、内存分配规则、回收策略等;
2.应用场景:
在Server模式下,ParNew收集器是一个非常重要的收集器,因为除Serial外,目前只有它能与CMS收集器配合工作;
但在单个CPU环境中,不会比Serail收集器有更好的效果,因为存在线程交互开销。
3.参数配置:
"-XX:+UseConcMarkSweepGC":指定使用CMS后,会默认使用ParNew作为新生代收集器;
"-XX:+UseParNewGC":强制指定使用ParNew;
"-XX:ParallelGCThreads":指定垃圾收集的线程数量,ParNew默认开启的收集线程与CPU的数量相同;
-XX:+PrintCommandLineFlags -XX:+UseParNewGC
吞吐量Parallel Scavenge优先回收器
(A)、有一些特点与ParNew收集器相似
新生代收集器;
采用复制算法;
多线程收集;
(B)、主要特点是:它的关注点与其他收集器不同
CMS等收集器的关注点是尽可能地缩短垃圾收集时用户线程的停顿时间;
而Parallel Scavenge收集器的目标则是达一个可控制的吞吐量(Throughput);
关于吞吐量与收集器关注点说明详见本节后面;

(A)、"-XX:MaxGCPauseMillis"

  控制最大垃圾收集停顿时间,大于0的毫秒数;
  MaxGCPauseMillis设置得稍小,停顿时间可能会缩短,但也可能会使得吞吐量下降;
  因为可能导致垃圾收集发生得更频繁;

(B)、"-XX:GCTimeRatio"
设置垃圾收集时间占总时间的比率,0 GCTimeRatio相当于设置吞吐量大小;

  垃圾收集执行时间占应用程序执行时间的比例的计算方法是:
  1 / (1 + n)
  例如,选项-XX:GCTimeRatio=19,设置了垃圾收集时间占总时间的5%--1/(1+19);

  默认值是1%--1/(1+99),即n=99;

CMS垃圾收集器
CMS(Concurrent Mark Sweep)收集器,以获取最短回收停顿时间【也就是指Stop The World的停顿时间】为目标,多数应用于互联网站或者B/S系统的服务器端上。其中“Concurrent”并发是指垃圾收集的线程和用户执行的线程是可以同时执行的。
CMS是基于“标记-清除”算法实现的,整个过程分为4个步骤:
1、初始标记(CMS initial mark)。
2、并发标记(CMS concurrent mark)。
3、重新标记(CMS remark)。
4、并发清除(CMS concurrent sweep)。
注意:“标记”是指将存活的对象和要回收的对象都给标记出来,而“清除”是指清除掉将要回收的对象。
其中,初始标记、重新标记这两个步骤仍然需要“Stop The World”。
A.初始标记只是标记一下GC Roots能直接关联到的对象,速度很快。
B.并发标记阶段【也就说明不会阻碍业务线程继续执行,因为它所以还会有下面要说的“重新标记”阶段了】就是进行GC Roots Tracing【其实就是从GC Roots开始找到它能引用的所有其它对象】的过程。
C.重新标记阶段则是为了修正并发标记期间因用户程序继续动作而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段稍长一些,但远比并发标记的时间短。

CMS收集器的动作步骤如下图所示,在整个过程中耗时最长的并发标记和并发清除过程收集器线程都可以与用户线程一起工作,因此,从总体上看,CMS收集器的内存回收过程是与用户线程一起并发执行的:
image.png

优缺点
优点:
1.CMS收集器初始化标记与重新标记只需要短暂的stw操作,不会非常耗时。
2.CMS收集器降低GC线程在清理垃圾过程中,对用户线程暂停的时间。
3.CMS收集器可以实现用户线程与GC线程同时执行,整体可以实现低延迟。
缺点:
1.CMS收集器不向其他的收集器是必须等到老年代堆内存满的时候,才开始清理堆内存垃圾,
而是提前根据设定的堆内存达到一定阈值的时候,开始清理堆内存垃圾,确保CMS在回收
垃圾的过程中有足够的空间支持。

2.当CMS在清理回收垃圾过程中,发现运行期间内存无法满足需要,就会开启
一次可能出现“Concurrent Mode Failure" 失败而导致另一次Full GC的产生,
虚拟机会临时采用备选方案:Serial Old收集器重新进行老年代垃圾收集,这时候停顿
时间非常长。
4.因为CMS采用标记清除算法,每次回收完垃圾会造成空间的不连续性,产生大量
的垃圾碎片问题,而只能使用空闲列表执行分配内存。

为什么CMS收集器采用标记清除而不是标记整理算法?
因为CMS收集器采用并行的方式,清除垃圾与用户线程可以同时运行,
为了保证用户线程与GC线程同时运行,所以采用标记清除算法,
如果采用标记整理算法,有可能会导致移动内存地址,会发生的stw问题。

优点总结:
1.并发收集器 GC线程可以与用户线程同时并发执行
2.降低用户线程等待时间
缺点总结:
1.会发生内存碎片化(标记清除)
2.用户线程空间不足,无法存放大对象的情况下,有可能会触发FULLGC
3.消耗CPU资源(与用户线程同时执行)
4.CMS收集器无法处理浮动垃圾 在并发标记阶段如果产生了新的垃圾对象
,CMS将无法对这些垃圾对象进行标记,最终会导致这些新产生的垃圾对象
没有被及时的回收,从而只能在下一次执行GC时释放这些之前未被回收的对相关。
CMS收集器参数设置
-XX:+UseConcMarkSweepGc 手动指定使用CMS收集器执行内存回收任务。
开启该参数后会自动将-XX: +UseParNewGc打开。即: ParNew (Young区用) +CMS (0ld区用) +Serial 0ld的组合。

-XX:CMS1nitiatingOccupanyFraction设置堆内存使用率的阈值,一旦达到该阈值,便开始进行回收。
JDK5及以前版本的默认值为68,即当老年代的空间使用率达到68%时,会执行一 次CMS 回收。 JDK6 及以上版本默认值为92%
如果内存增长缓慢,则可以设置一个稍大的值,大的阈值可以有效降低CMS的触发频率,减少老年代回收的次数可以较为明显地改善应用程序性能。反之,如果应用程序内存使用率增长很快,则应该降低这个阈值,以避免频繁触发老年代串行收集器。因此通过该选项便可以有效降低Full GC的执行次数。

-XX: +UseCMSCompactAtFullCollection用于指定在执行完Full GC后对内存空间进行压缩整理,以此避免内存碎片的产生。不过由于内存压缩整理过程无法并发执行,所带来的问题就是停顿时间变得更长了。
-XX:CMSFullGCsBeforeCompaction设置在执行多少次Full GC后对内存空间进行压缩整理。

-XX:ParallelCMSThreads 设置CMS的线程数量。
CMS 默认启动的线程数是(ParallelGCThreads+3)/4, ParallelGCThreads是年轻代并行收集器的线程数。当CPU资源比较紧张时,受到CMs收集器线程的影响,应用程序的性能在垃圾回收阶段可能会非常糟糕。

发垃圾收集和并行垃圾收集的区别
(A)、并行(Parallel)
指多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态;
如ParNew、Parallel Scavenge、Parallel Old;

(B)、并发(Concurrent)

   指用户线程与垃圾收集线程同时执行(但不一定是并行的,可能会交替执行);
  用户程序在继续运行,而垃圾收集程序线程运行于另一个CPU上;    
  如CMS、G1(也有并行);

Jdk7收集器组合的关系

image.png

新生代收集器:SerialGC、Parallel Scavenge、ParNew
老年代收集器:SerialOldGC、Parallel old、CMS
整体收集器:G1

JDK8(含Jdk8)之前的组合关系:
Serial/Serial 01d、Serial/CMS、 ParNew/Serial 01d、 ParNew/CMS、Paral1el Scavenge/Serial 01d、Paral1el Scavenge/Parallel 01d、G1;

GC日志评估指标

1.吞吐量 运行用户代码占总时间的比例
总运行时间:程序的运行时间(100s)+内存回收的时间 (1s)
比如程序运行时间100s/内存回收时间 垃圾回收1s 100/101=99%
2.GC负荷 与吞吐量相反,指应用花在GC上的时间百分比
总运行时间:程序的运行时间+GC内存回收的时间
1/101 1%

3.暂停时间 应用花在GC stop-the-world 的时间 暂时时间越小越好

4.GC频率 次数 GC频率越小,stw暂停时间越大 GC回收频率越小、stw越小的情况下GC回收频率越大
6.反应速度 从一个对象变成垃圾道这个对象被回收的时间

吞吐量就是CPU用于运行用户代码的时间与CPU总消耗时间的比值,即吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间)。
比如:应用程序运行了100s,其中垃圾收集花费1s,那么吞吐量占比为99%。
100/100+1

暂停时间:暂停时间”是指一个时间段内应用程序线程暂停总共消耗时间6s

串行垃圾回收器
SerialOldGC与SerialGC
最古老,最稳定、效率高、可能会产生较长的停顿
–新生代、老年代使用串行回收
–新生代复制算法
–老年代标记-压缩
-XX:+UseSerialGC
单线程在收集垃圾的时候,必须暂停当前所有的工作线程,直到清理垃圾完垃圾,工作线程才可以继续执行,Stop The World
-XX:+PrintCommandLineFlags -XX:+UseSerialGC
主要应用于:桌面应用程序堆内存空间很小

依然是HotSpot在Client模式下默认的新生代收集器;
也有优于其他收集器的地方:
简单高效(与其他收集器的单线程相比);
对于限定单个CPU的环境来说,Serial收集器没有线程交互(切换)开销,可以获得最高的单线程收集效率;
在用户的桌面应用场景中,可用内存一般不大(几十M至一两百M),可以在较短时间内完成垃圾收集(几十MS至一百多MS),只要不频繁发生,这是可以接受的
并行垃圾回收器
ParNew并行回收器
ParNew垃圾收集器是Serial收集器的多线程版本,主要用于新生代垃圾收集器
1、特点
除了多线程外,其余的行为、特点和Serial收集器一样;
如Serial收集器可用控制参数、收集算法、Stop The World、内存分配规则、回收策略等;
2.应用场景:
在Server模式下,ParNew收集器是一个非常重要的收集器,因为除Serial外,目前只有它能与CMS收集器配合工作;
但在单个CPU环境中,不会比Serail收集器有更好的效果,因为存在线程交互开销。
3.参数配置:
"-XX:+UseConcMarkSweepGC":指定使用CMS后,会默认使用ParNew作为新生代收集器;
"-XX:+UseParNewGC":强制指定使用ParNew;
"-XX:ParallelGCThreads":指定垃圾收集的线程数量,ParNew默认开启的收集线程与CPU的数量相同;
-XX:+PrintCommandLineFlags -XX:+UseParNewGC
吞吐量Parallel Scavenge优先回收器
(A)、有一些特点与ParNew收集器相似
新生代收集器;
采用复制算法;
多线程收集;
(B)、主要特点是:它的关注点与其他收集器不同
CMS等收集器的关注点是尽可能地缩短垃圾收集时用户线程的停顿时间;
而Parallel Scavenge收集器的目标则是达一个可控制的吞吐量(Throughput);
关于吞吐量与收集器关注点说明详见本节后面;

(A)、"-XX:MaxGCPauseMillis" (减少用户线程暂停时间)

  控制最大垃圾收集停顿时间,大于0的毫秒数;
  MaxGCPauseMillis设置得稍小,停顿时间可能会缩短,但也可能会使得吞吐量下降;
  因为可能导致垃圾收集发生得更频繁;

(B)、"-XX:GCTimeRatio" (吞吐量优先)
设置垃圾收集时间占总时间的比率,0 GCTimeRatio相当于设置吞吐量大小;

  垃圾收集执行时间占应用程序执行时间的比例的计算方法是:

  例如,选项-XX:GCTimeRatio=19,设置了垃圾收集时间占总时间的5%--1/(1+19);

假设-XX:GCTimeRatio=99 ,则垃圾收集时间为1/(1+99),默认值为99,即1%时间用于垃圾收集。
CMS垃圾收集器
CMS(Concurrent Mark Sweep)收集器,以获取最短回收停顿时间【也就是指Stop The World的停顿时间】为目标,多数应用于互联网站或者B/S系统的服务器端上。其中“Concurrent”并发是指垃圾收集的线程和用户执行的线程是可以同时执行的。
CMS是基于“标记-清除”算法实现的,整个过程分为4个步骤:
1、初始标记(CMS initial mark)。
2、并发标记(CMS concurrent mark)。
3、重新标记(CMS remark)。
4、并发清除(CMS concurrent sweep)。
注意:“标记”是指将存活的对象和要回收的对象都给标记出来,而“清除”是指清除掉将要回收的对象。
其中,初始标记、重新标记这两个步骤仍然需要“Stop The World”。
A.初始标记只是标记一下GC Roots能直接关联到的对象,速度很快。
B.并发标记阶段【也就说明不会阻碍业务线程继续执行,因为它所以还会有下面要说的“重新标记”阶段了】就是进行GC Roots Tracing【其实就是从GC Roots开始找到它能引用的所有其它对象】的过程。
C.重新标记阶段则是为了修正并发标记期间因用户程序继续动作而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段稍长一些,但远比并发标记的时间短。
CMS收集器的动作步骤如下图所示,在整个过程中耗时最长的并发标记和并发清除过程收集器线程都可以与用户线程一起工作,因此,从总体上看,CMS收集器的内存回收过程是与用户线程一起并发执行的:

image.png

优缺点
优点:
1.CMS收集器初始化标记与重新标记只需要短暂的stw操作,不会非常耗时。
2.CMS收集器降低GC线程在清理垃圾过程中,对用户线程暂停的时间。
3.CMS收集器可以实现用户线程与GC线程同时执行,整体可以实现低延迟。
缺点:
1.CMS收集器不向其他的收集器是必须等到老年代堆内存满的时候,才开始清理堆内存垃圾,
而是提前根据设定的堆内存达到一定阈值的时候,开始清理堆内存垃圾,确保CMS在回收
垃圾的过程中有足够的空间支持。

2.当CMS在清理回收垃圾过程中,发现运行期间内存无法满足需要,就会开启
一次可能出现“Concurrent Mode Failure" 失败而导致另一次Full GC的产生,
虚拟机会临时采用备选方案:Serial Old收集器重新进行老年代垃圾收集,这时候停顿
时间非常长。
4.因为CMS采用标记清除算法,每次回收完垃圾会造成空间的不连续性,产生大量
的垃圾碎片问题,而只能使用空闲列表执行分配内存。

为什么CMS收集器采用标记清除而不是标记整理算法?
因为CMS收集器采用并行的方式,清除垃圾与用户线程可以同时运行,
为了保证用户线程与GC线程同时运行,所以采用标记清除算法,
如果采用标记整理算法,有可能会导致移动内存地址,会发生的stw问题。

优点总结:
1.并发收集器 GC线程可以与用户线程同时并发执行
2.降低用户线程等待时间
缺点总结:
1.会发生内存碎片化(标记清除)
2.用户线程空间不足,无法存放大对象的情况下,有可能会触发FULLGC
3.消耗CPU资源(与用户线程同时执行)
4.CMS收集器无法处理浮动垃圾 在并发标记阶段如果产生了新的垃圾对象

,CMS将无法对这些垃圾对象进行标记,最终会导致这些新产生的垃圾对象
没有被及时的回收,从而只能在下一次执行GC时释放这些之前未被回收的对相关。
CMS收集器参数设置
-XX:+UseConcMarkSweepGc 手动指定使用CMS收集器执行内存回收任务。
开启该参数后会自动将-XX: +UseParNewGc打开。即: ParNew (Young区用) +CMS (0ld区用) +Serial 0ld的组合。

-XX:CMS1nitiatingOccupanyFraction设置堆内存使用率的阈值,一旦达到该阈值,便开始进行回收。
JDK5及以前版本的默认值为68,即当老年代的空间使用率达到68%时,会执行一 次CMS 回收。 JDK6 及以上版本默认值为92%
如果内存增长缓慢,则可以设置一个稍大的值,大的阈值可以有效降低CMS的触发频率,减少老年代回收的次数可以较为明显地改善应用程序性能。反之,如果应用程序内存使用率增长很快,则应该降低这个阈值,以避免频繁触发老年代串行收集器。因此通过该选项便可以有效降低Full GC的执行次数。

-XX: +UseCMSCompactAtFullCollection用于指定在执行完Full GC后对内存空间进行压缩整理,以此避免内存碎片的产生。不过由于内存压缩整理过程无法并发执行,所带来的问题就是停顿时间变得更长了。
-XX:CMSFullGCsBeforeCompaction设置在执行多少次Full GC后对内存空间进行压缩整理。

-XX:ParallelCMSThreads 设置CMS的线程数量。
CMS 默认启动的线程数是(ParallelGCThreads+3)/4, ParallelGCThreads是年轻代并行收集器的线程数。当CPU资源比较紧张时,受到CMs收集器线程的影响,应用程序的性能在垃圾回收阶段可能会非常糟糕。

你可能感兴趣的:(48 jvm性能优化之垃圾收集器)