0
点赞
收藏
分享

微信扫一扫

Java虚拟机-垃圾回收器


一、如何查看垃圾回收器

查看JVM的默认垃圾回收器,可以看出JDK8中UseParallelGC即Parallel Scavenge+Serial Old

java -XX:+PrintCommandLineFlags -version
-XX:InitialHeapSize=266359616
-XX:MaxHeapSize=4261753856
-XX:+PrintCommandLineFlags
-XX:+UseCompressedClassPointers
-XX:+UseCompressedOops
-XX:-UseLargePagesIndividualAllocation
-XX:+UseParallelGC
java version "1.8.0_211"
Java(TM) SE Runtime Environment (build 1.8.0_211-b12)
Java HotSpot(TM) 64-Bit Server VM (build 25.211-b12, mixed mode)

查看垃圾回收器详细信息

java -XX:+PrintGCDetails -version
java version "1.8.0_211"
Java(TM) SE Runtime Environment (build 1.8.0_211-b12)
Java HotSpot(TM) 64-Bit Server VM (build 25.211-b12, mixed mode)
Heap
PSYoungGen total 76288K, used 3932K [0x000000076b500000, 0x0000000770a00000, 0x00000007c0000000)
eden space 65536K, 6% used [0x000000076b500000,0x000000076b8d7240,0x000000076f500000)
from space 10752K, 0% used [0x000000076ff80000,0x000000076ff80000,0x0000000770a00000)
to space 10752K, 0% used [0x000000076f500000,0x000000076f500000,0x000000076ff80000)
ParOldGen total 175104K, used 0K [0x00000006c1e00000, 0x00000006cc900000, 0x000000076b500000)
object space 175104K, 0% used [0x00000006c1e00000,0x00000006c1e00000,0x00000006cc900000)
Metaspace used 2366K, capacity 4480K, committed 4480K, reserved 1056768K
class space used 255K, capacity 384K, committed 384K, reserved 1048576K

二、常见垃圾回收器介绍

1.Serial

使用场景主要为客户端。是最基本,发展最悠久的单线程垃圾收集器,是JDK1.3.1前HotSpot新生代收集的唯一选择。分为新生代和老年代两个,分别采用了复制算法和标记-整理算法。

Java虚拟机-垃圾回收器_用户线程

1)特点

  • 针对新生代;
  • 采用复制算法;
  • 单线程收集;
  • 进行垃圾收集时,必须暂停所有工作线程,直到完成,即会"Stop The World"。

2)应用场景

  • 依然是HotSpot在Client模式下默认的新生代收集器;
  • 简单高效(与其他收集器的单线程相比);
  • 对于限定单核CPU的环境,没有线程开销,可以获得最高的单线程收集效率;
  • 在用户的桌面应用场景中,可用内存一般不大(几十M至一两百M),可以在较短时间内完成垃圾收集(几十MS至一百多MS),只要不频繁发生,可以接受。

3)使用方式

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

2.ParNew

ParNew垃圾收集器是Serial收集器的多线程版本。

Java虚拟机-垃圾回收器_用户线程_02

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的数量相同;

3.Parallel

Parallel Scavenge 收集器是一个新生代收集器,采用复制算法,并且是多线程收集器;与ParNew最大的不同,它关注的是垃圾回收的吞吐量(Throughput)。

Parallel Old 收集器是Parallel Scavenge 收集器的老年代版本,使用多线程和“标记-整理”算法。

JDK7和8环境下,默认使用 Parallel Scavenge(新生代)+ Serial Old(老年代)。

Java虚拟机-垃圾回收器_老年代_03

1)特点

  • 在注重吞吐量以及CPU资源敏感的场合,可以优先考虑Parallel Scavenge 加 Parallel Old 收集器。
  • 吞吐量 = 运行用户代码时间 / (运行用户代码时间 + 垃圾收集时间)

2)应用场景

  • 主要适合在后台运算而不需要太多交互的任务:
  • 停顿时间越短就越适合需要与用户交互的程序,良好的响应速度能提升用户体验,而高吞吐量则可以高效的利用CPU时间,尽快完成程序的运算任务。

3)使用方式

-XX:+UseParallelGC :使用Parallel作为垃圾收集器;
-XX:MaxGCPauseMillis:控制最大垃圾收集停顿时间
-XX:GCTimeRatio:设置吞吐量大小

4.CMS

CMS,Concurrent Mark Sweep,同样是老年代的收集器。它关注的是垃圾回收最短的停顿时间(低停顿),在老年代并不频繁GC的场景下,是比较适用的。

1)整体流程

  • 初始标记(initial mark),单线程执行,需要“Stop The World”,但仅仅把GC Roots的直接关联可达的对象给标记一下,由于直接关联对象比较小,所以这里的速度非常快。
  • 并发标记(concurrent mark),对于初始标记过程所标记的初始标记对象,进行并发追踪标记,此时其他线程仍可以继续工作。此处时间较长,但不停顿。
  • 重新标记(remark),在并发标记的过程中,由于可能还会产生新的垃圾,所以此时需要重新标记新产生的垃圾。此处执行并行标记,与用户线程不并发,所以依然是“Stop The World”,时间比初始时间要长一点。
  • 并发清除(concurrent sweep),并发清除之前所标记的垃圾。其他用户线程仍可以工作,不需要停顿。

Java虚拟机-垃圾回收器_用户线程_04

2)特点

  • 由于最耗费时间的并发标记与并发清除阶段都不需要暂停工作,所以整体的回收是低停顿的;
  • Mark Sweep算法会导致内存碎片比较多;
  • CMS的并发能力依赖于CPU资源,所以在CPU数少和CPU资源紧张的情况下,性能较差;
  • 并发清除阶段,用户线程依然在运行,所以依然会产生新的垃圾,此阶段的垃圾并不会再本次GC中回收,而放到下次。所以GC不能等待内存耗尽的时候才进行GC,这样的话会导致并发清除的时候,用户线程可以了利用的空间不足。所以这里会浪费一些内存空间给用户线程预留。

3)应用场景

  • CMS只会回收老年代和永久带(1.8开始为元数据区,需要设置CMSClassUnloadingEnabled),不会收集年轻带;
  • CMS是一种预处理垃圾回收器,它不能等到old内存用尽时回收,需要在内存用尽前,完成回收操作,否则会导致并发回收失败;所以CMS垃圾回收器开始执行回收操作,有一个触发阈值,默认是老年代或永久带达到92%。

4)使用方式

-XX:+UseConcMarkSweepGC 激活CMS收集器
-XX:ConcGCThreads 设置CMS线程的数量
-XX:+UseCMSInitiatingOccupancyOnly 只根据老年代使用比例来决定是否进行CMS
-XX:CMSInitiatingOccupancyFraction 设置触发CMS老年代回收的内存使用率占比
-XX:+CMSParallelRemarkEnabled 并行运行最终标记阶段,加快最终标记的速度
-XX:+UseCMSCompactAtFullCollection 每次触发CMS Full GC的时候都整理一次碎片
-XX:CMSFullGCsBeforeCompaction=* 经过几次CMS Full GC的时候整理一次碎片
-XX:+CMSClassUnloadingEnabled 让CMS可以收集永久带,默认不会收集
-XX:+CMSScavengeBeforeRemark 最终标记之前强制进行一个Minor GC
-XX:+ExplicitGCInvokesConcurrent 当调用System.gc()的时候,执行并行gc,只有在CMS或者G1下该参数才有效

5.G1

G1,Garbage First,在JDK 1.7版本正式启用,是当时最前沿的垃圾收集器。G1可以说是CMS的终极改进版,解决了CMS内存碎片、更多的内存空间登问题。虽然流程与CMS比较相似,但底层的原理已是完全不同。

JDK1.9 默认垃圾收集器G1。

1)特点

  • 高效益优先。G1会预测垃圾回收的停顿时间,原理是计算老年代对象的效益率,优先回收最大效益的对象。
  • 堆内存结构的不同。以前的收集器分代是划分新生代、老年代、持久代等。G1则是把内存分为多个大小相同的区域Region,每个Region拥有各自的分代属性,但这些分代不需要连续。

下面是老式收集器和G1收集器的分代对比:

Java虚拟机-垃圾回收器_老年代_05

Java虚拟机-垃圾回收器_老年代_06

  • 这样的分区可以有效避免内存碎片化问题。但会造成分代内存不连续,导致在GC搜索垃圾对象的时候需要全盘扫描找出引用内存所在。所以G1对于每个Region都维护一个Remembered Set,用于记录对象引用的情况。当GC发生的时候根据Remembered Set的引用情况去搜索。

2)整体流程

  • 初始标记(initial mark),标记了从GC Root开始直接关联可达的对象。STW(Stop the World)执行。
  • 并发标记(concurrent marking),并发标记初始标记的对象,此时用户线程依然可以执行。
  • 最终标记(Remark),STW,标记再并发标记过程中产生的垃圾。
  • 筛选回收(Live Data Counting And Evacuation),评估标记垃圾,根据GC模式回收垃圾。STW执行。

Java虚拟机-垃圾回收器_用户线程_07

3)应用场景

  • G1是一款面向服务端应用的垃圾收集器。
  • 大堆(6左右,>=)运行,低GC延迟(<=0.5s)要求,
  • GC频繁;收集或者压缩时间长;收集对象年龄差异大。

4)使用方式

-XX:+UseG1GC
不要设置年轻代大小 --Xmn;G1自动调整
XX:MaxGCPauseMillis:设置为能够满足90%请求的时间值。

6.ZGC

在JDK 11当中,加入了实验性质的ZGC。它的回收耗时平均不到2毫秒。它是一款低停顿高并发的收集器。ZGC几乎在所有地方并发执行的,除了初始标记的是STW的。所以停顿时间几乎就耗费在初始标记上,这部分的实际是非常少的。

Java虚拟机-垃圾回收器_用户线程_08

1)特点

  • 着色指针Colored Pointer。ZGC利用指针的64位中的几位表示Finalizable、Remapped、Marked1、Marked0(ZGC仅支持64位平台),以标记该指向内存的存储状态。相当于在对象的指针上标注了对象的信息。在这个被指向的内存发生变化的时候(内存在Compact被移动时),颜色就会发生变化。
  • 读屏障Load Barrier。由于着色指针的存在,在程序运行时访问对象的时候,可以轻易知道对象在内存的存储状态(通过指针访问对象),若请求读的内存在被着色了。那么则会触发读屏障。读屏障会更新指针再返回结果,此过程有一定的耗费,从而达到与用户线程并发的效果。

2)整体流程

与标记对象的传统算法相比,ZGC在指针上做标记,在访问指针时加入Load Barrier(读屏障),比如当对象正被GC移动,指针上的颜色就会不对,这个屏障就会先把指针更新为有效地址再返回,也就是,永远只有单个对象读取时有概率被减速,而不存在为了保持应用与GC一致而粗暴整体的Stop The World。

3)性能提升

ZGC的吞吐量与Parallel GC(优化吞吐量)大致相当,但平均暂停时间为1ms,最长为4ms。 与之相比G1和Parallel会有很多超过200ms的GC停顿。

三、补充

1.Stop-The-World

JVM在后台自动发起和自动完成的,在用户不可见的情况下,把用户正常的工作线程全部停掉,即GC停顿,这会带给用户不良的体验。从JDK1.3到现在,从Serial收集器-》Parallel收集器-》CMS-》G1,用户线程停顿时间不断缩短,但仍然无法完全消除。

2.为什么只有ParNew能与CMS收集器配合?

CMS是HotSpot在JDK1.5推出的第一款真正意义上的并发(Concurrent)收集器,第一次实现了让垃圾收集线程与用户线程(基本上)同时工作。CMS作为老年代收集器,但却无法与JDK1.4已经存在的新生代收集器Parallel Scavenge配合工作,因为Parallel Scavenge(以及G1)都没有使用传统的GC收集器代码框架,而另外独立实现;而其余几种收集器则共用了部分的框架代码。所以如果老年代使用了CMS,那新生代只能使用ParNew。

3.CMS垃圾回收中,既然Mark Sweep会造成内存碎片,那么为什么不把算法换成Mark Compact呢?

答案其实很简答,因为当并发清除的时候,用Compact整理内存的话,原来的用户线程使用的内存还怎么用呢?要保证用户线程能继续执行,前提的它运行的资源不受影响嘛。Mark Compact更适合“Stop the World”这种场景下使用。

举报

相关推荐

0 条评论