JVM第十一天-GC-常见垃圾回收器
每次都要经历一个过程。。。 就是从有道云笔记向****迁移,奈何粘过来图片,格式全没了,还要重新用markdown格式来一遍,为啥直接用makrdown呢? 因为我觉得markdown对于图片的存放比较麻烦,还是有道云笔记比较好,图片直接粘,写笔记时无阻断,多端同步。。 所以,麻烦是麻烦,但是对于我来说,尽可能放在一个不会丢的地方是最省心的了。。。
不多bb。。 继续开干今天的东西。。。
常见的垃圾回收器
所谓回收垃圾,自然需要有一个东西来执行这个工作,也就是垃圾回收器。
我们之前说的垃圾回收算法,也是要结合垃圾回收器组合来说的,因为是某个垃圾回收器使用了某种算法进行了垃圾回收。
图上就是常见的一些垃圾回收器。
左侧的6个:Serial,ParNew,Parallel Scavenge,CMS,Serial Old,Parallel Old是分代模型下的垃圾回收器。
其中,Serial,ParNew,Parallel Scavenge是年轻代的垃圾回收器。
CMS,Serial Old,Parallel Old是老年代的垃圾回收器。
你可以看到它们的虚线,来表示它们可以互相进行的组合。
而右侧G1开始,就已经开始脱离分代模型。 它是逻辑分代,物理不分代的垃圾回收器(也就是一个垃圾回收器就够了)
ZGS,Shenandoah是逻辑物理都不分代的垃圾回收器。
Epsilon是jdk debug时才会用到的垃圾回收器,我们无需关心。
虽然可以组合的情况很多,但实际上常用的组合一共有三种:
1、Serial+Serial Old
2、Parallel Scavenge + Parallel Old
3、CMS+ParNew+Serial Old
如果你不做任何设置,垃圾回收组合在JDK8默认是Parallel Scavenge + Parallel Old
接下来我们分别对每一组的垃圾回收器进行讲解:
1、Serial+Serial Old
Serial
Serial是最开始jdk诞生时使用的年轻代垃圾回收器,是一个使用单线程进行垃圾回收的垃圾回收器,使用复制算法。
在触发垃圾回收时,会停止所有运行的工作线程。不过这个停止不是立马停止,而是会让工作线程到一个安全点再暂停住。
初期内存比较小的时候,使用这种没有问题,但随着现代计算机的内存增大,这种基本已经没人用了,因为单线程回收速度太慢了。
Serial Old
和Serial是一对,是用于在老年代进行垃圾回收的,使用标记整理算法。
硬伤也一样,单线程,太慢!
2、Parallel Scavenge + Parallel Old
Paraller Scavenge
新生代收集器,复制算法,Serial的多线程版本。他主要追求高吞吐量,高效利用CPU。吞吐量一般为99%, 吞吐量= 用户线程时间/(用户线程时间+GC线程时间)。适合后台应用等对交互响应要求不高的场景。
Paraller Old
Serial Old的多线程版本。 使用的是标记整理算法,吞吐量优先
3、CMS+ParNew+Serial Old
ParNew
也是多线程的年轻代垃圾回收器,使用的也是复制算法,和Paraller Scavenge差不多,属于Paraller Scavenge的增强版本,用于配合与CMS进行使用。
举个例子,ParNew可以在CMS的并发阶段同时进行工作。
PS和PN的区别延伸阅读:
https://docs.oracle.com/en/java/javase/13/gctuning/ergonomics.html#GUID-3D0BB91E-9BFF-4EBB-B523-14493A860E73
ConcurrentMarkSweep (CMS)
一种老年代的垃圾回收器,CMS使用了并发标记清除算法。
高并发、低停顿,追求最短GC回收停顿时间。适合重视服务响应速度的应用(如网站),和 Parallel Old的追求目标相反。
CMS是1.4版本后期引入,CMS是里程碑式的GC垃圾回收器。它开启了并发回收的垃圾回收理念。
什么叫并发回收呢?
这个要类比之前的垃圾回收器,之前的垃圾回收器在回收的时候需要停止所有正在运行的线程,才开始能进行垃圾清理,在这个期间,程序是卡住的。
而并发回收就是说,开启了一种可能:我工作线程的运行和你垃圾回收的线程可以同时进行工作,而不需要我工作线程停下等你。(图上可以看到,蓝色箭头代表工作线程,绿色箭头代表垃圾回收线程)
并发垃圾回收就是因为无法忍受STW(STOP THE WORLD)这种情况才诞生的。将STW大幅降低时间(200ms)
但是CMS毛病较多,因此目前没有任何一个JDK版本默认是CMS的垃圾回收器,只能手工指定。
它的清理过程分为四个阶段:初始标记、并发标记、重新标记、并发清除。
初始标记
初始标记阶段是需要STW的,但只标记那些根对象,因此这个过程是比较快的
并发标记
并发标记,是在工作线程运行的同时,垃圾回收器也在开始标记那些垃圾对象。
重新标记
这个阶段是STW的,因为并发标记是在工作线程也运行的过程中一起进行标记的,因此有可能在这个过程中出现了多标(运行期间新产生的垃圾),漏标(运行期间本是垃圾的对象又有了引用指向它,使得它不是垃圾了)的现象,
因此此阶段需要重新进行标记,但因为变动的数量一般比较少,所以这个阶段的STW也不会很长。
并发清除
最后,对最终标记了是垃圾的对象,进行并发清除。
但实际上依然会存在垃圾浮动的问题:在这个并发清除期间,运行时又产生了新的垃圾对象,将只能等待下一轮垃圾回收去将其回收了。
CMS缺点
你可以看到,我们当前的组合是CMS+ParNew+Serial Old,前面两个都能理解,但怎么多出来个Serial Old呢?别急,听我说。
1、Memory Fragmentation(内存碎片)
因为CMS使用的是并发标记清除算法,还记得标记清除有什么问题吗? 没错,会产生大量的内存碎片的问题。
CMS就是因为使用了这种垃圾清理算法,可能会导致在某个时刻,老年代里都是大对象,碎片化又很严重,当年轻代升级要放入老年代,放不下的时候,会触发FGC,这时候Serial Old就登场了。
FGC会让Serial Old使用标记整理算法去回收老年代区域内存,但我们知道,Serial Old是单线程的,很慢,而且要命的是,它会导致STW!
CMS设计之初适用的可能只是几百兆,几个G的内存,但触发FGC的时候,其实效果也还算能接受。
但是现代计算机,随随便便32个G的内存,然后等待着这个单线程的垃圾收集器去慢慢清理垃圾,直到清理完成,程序才能动,
2、Floating Garbage(垃圾浮动)
异常1:Concurrent Mode Failure
产生:if the concurrent collector is unable to finish reclaiming the
unreachable objects before the tenured generation fills up, or if an
allocation cannot be satisfiedwith the available free space blocks in
the tenured generation, then theapplication is paused and the
collection is completed with all the applicationthreads stopped
如果一次并发回收后存在一些浮动垃圾未被回收,而此时老年代又满了,会报这个错,然后会启动Serial Old清理老年代
异常2:PromotionFailed
解决方案:
降低触发CMS的阈值,让老年代尽可能的保持多的空间:
–XX:CMSInitiatingOccupancyFraction 92% 默认是92%,可以降低这个值,让CMS保持老年代足够的空间,尽可能的不去触发FCG。
但无论如何,如果一直向老年代放入对象,那么迟早有一个时刻,会导致FCG问题。
CMS使用的并发算法
并发清理垃圾带来的问题就是垃圾线程工作的同时,应用线程可能也在进行产生垃圾,或者使得一个垃圾变得不是垃圾(重新有了引用指向它),因此对于这种情况,主要使用三色标记+指定算法来实现(后面针对这块算法会专门讨论)
因此CMS采用的是三色标记 + Incremental Update
除了上述三组基于分代模型的组合垃圾回收器之外,还有下面这些向不分代趋势步进的垃圾回收器:
G1(10ms)
它的吞吐量比CMS要少15%左右。
逻辑分代,物理不分代。
并发标记算法:G1也是并发回收的,因此和CMS差不多,它也是三色标记,但不同的对于变动垃圾的处理算法方式,它采用的是 SATB。 因此是 三色标记+SATB。
ZGC (1ms)
通过此垃圾回收器,使得java的性能可以接近PK C++
逻辑不分代,物理不分代。
并发标记算法:ColoredPointers + LoadBarrier
ColoredPointers :颜色指针。 在JVM中,一个指针在没有压缩的情况下,是占用64位。其中会拿出三位用来标记一个指针的指向是否变化过,当垃圾回收的时候,会扫描通过这些3位鉴定出是变动过的指针
Shenandoah
逻辑不分代,物理不分代。
并发标记算法:ColoredPointers + WriteBarrier
除此之外,还有一种额外的垃圾回收器:
Eplison
jdk debug自用,实际不会进行任何的垃圾回收工作。我们也接触不到,了解即可。
垃圾收集器跟内存大小的关系
Serial 适用于几十兆
PS 适用于上百兆 - 几个G
CMS 适用于20G左右
G1 适用于上百G
ZGC 适用于 4T - 16T(JDK13)
常见垃圾回收器组合参数设定:(JDK1.8)
XX:+UseSerialGC = Serial New (DefNew) + Serial Old
小型程序。默认情况下不会是这种选项,HotSpot会根据计算及配置和JDK版本自动选择收集器
-XX:+UseParNewGC = ParNew + SerialOld
这个组合已经很少用(在某些版本中已经废弃)
https://stackoverflow.com/questions/34962257/why-remove-support-for-parnewserialold-anddefnewcms-in-the-future
-XX:+UseConc(urrent)MarkSweepGC = ParNew + CMS + Serial Old
-XX:+UseParallelGC = Parallel Scavenge + Parallel Old (1.8默认)
【PS + SerialOld】
-XX:+UseParallelOldGC = Parallel Scavenge + Parallel Old
-XX:+UseG1GC = G1
查看默认GC的方式
Linux中没找到默认GC的查看方法,而windows中会打印UseParallelGC
java +XX:+PrintCommandLineFlags -version
通过GC的日志来分辨
Linux下1.8版本默认的垃圾回收器到底是什么?
1.8.0_181 默认(看不出来)Copy MarkCompact
1.8.0_222 默认 PS + PO