Garbage Collection(垃圾收集),Java 中 GC 的对象是堆空间和永久区

一、GC的算法


1)、标记-清除:是现代垃圾回收算法的基本思想。标记-清除将垃圾回收分为两个阶段:标记阶段和清除阶段。一种可行的实现是,在标记阶段,首先通过根节点,标记所有从根节点开始可达的对象。因此,未标记的对象就是未被引用的对象。然后,在清除阶段,清除所有未被标记的对象。
GC 的算法及收集器GC 的算法及收集器
GC 的算法及收集器
2)、标记-压缩:标记-压缩算法适合用于存活对象较多的场合,如老年代。在标记-清除算法的基础上做了一些优化。和标记-清除算法一样,标记-压缩算法也首先需要从根节点开始,对所有可达对象做一次标记。但之后,并不是简单的清理未标记的对象,而是将所有的存活对象压缩到内存的另一端,之后,清理边界外所有的对象
GC 的算法及收集器GC 的算法及收集器GC 的算法及收集器
GC 的算法及收集器
3)、复制算法:①、与标记算法相比,复制算法是一种相对高效的回收方法。
  ②、不适用存活对象较多的场合,比如老年代。
  ③、将原有的内存空间分为两块(form、to),每次只是用一块,在垃圾回收时,将正在适用的内存存活对象复制一份到未使用的内存中,之后,清除正在适用的内存块中的所有对象,交换两个内存的角色,完成垃圾回收。
GC 的算法及收集器

复制算法的最大问题就是空间浪费严重,整合标记清理思想。(to就是浪费掉的内存空间,不计算在total内)
GC 的算法及收集器   GC 的算法及收集器   GC 的算法及收集器
GC 的算法及收集器

二、分代思想


依据对象的存活周期进行分类,短命对象归类为新生代,长命对象归为老年代。根据不同的特点,选取合适的收集算法:
①、少量对象存活,适合复制算法。
②、大量对象存活,适合标记-压缩算法。

三、可触及性


所有的算法,需要能够识别一个垃圾对象,因此需要给出一个可触及性的定义。
   ✔ 可触及性:从根节点栈中引用的对象,方法区中静态成员或者常量引用的对象《全局对象》或JNI方法栈中引用对象)可以触及到这个对象。
   ✔ 可复活的:一旦所有引用被释放,就是可复活状态,因为在finalize()中可能复活该对象。
   ✔ 不可触及的:在 finalize 后,可能会进入不可触及状态,不可触及的对象不可能复活,可以回收。
如下:第一次垃圾回收时,finallize方法中的 new CanReliveObj() 对象还未被清理,因此obj = this 是可用的。因此为可复活对象。但finalize后,new CanReliveObj()对象被清理,再次进行垃圾回收时,obj = null 为不可触及对象。
GC 的算法及收集器

注意:避免使用 finalize() 方法,操作不慎可能导致错误。且优先级低,何时被调用也不确定。尽量用 try-catch-finally 来代替。

四、Stop-The-World


Java 中一种全局暂停的现象,全局停顿,所有 Java代码停止,native(本地)代码可运行,但不能和 JVM交互。多半由 GC 引起的。Dump 线程、死锁检查、堆Dump 也会引起 Stop-The-World。

☛  GC为什么会有全局停顿?
类比在聚会的时候打扫房间,只有让大家停止活动,才能打扫干净房间,否则会不断有新垃圾产生。所带来的危害:长时间停止服务,没有响应。当遇到HA(High Available:高可用)系统,可能引起主备切换,严重危害生产环境。

五、垃圾收集器(内存回收的具体实现


1)、串行收集器Serial 收集器    -XX:+UseSerialGC(新生代、老年代都会使用串行收集器回收)
这个收集器是一个单线程的收集器,但它的单线程的意义并不仅仅说明它只会使用一个 CPU 或一条收集线程去完成垃圾收集工作,更重要的是在它进行垃圾收集时,必须暂停其他所有的工作线程,直到它收集结束。收集器的运行过程如下图所示:
GC 的算法及收集器
2)、并行收集器:【1】ParNew收集器:其实就是 Serial 收集器新生代的并行版本,多线程需要多核支持。
 -XX:+UseParNewGC(新生代并行,老年代串行)
-XX:ParallelGCThreads 限制线程数量
GC 的算法及收集器
  【2】Parallel Scanvenge收集器:类似于 ParNew,但更加关注吞吐量。
    -XX:+UseParallelGC  使用 Parallel Scanvenge 收集器:新生代并行,老年代串行。
  【3】Parallel Old收集器:是【2】的老年代版本。
    -XX:+UseParallelOldGC 使用Parallel Old收集器:新生代并行,老年代并行。
GC 的算法及收集器
各种参数设置-XX:MaxGCPauseMills :最大停顿时间,单位毫秒。
-XX:GCTimeRatio:0-100的取值范围,垃圾收集时间占总时间的比,默认99,即最大允许1%时间做GC。

注意:这两个参数是矛盾的。因为停顿时间和吞吐量不可能同时调优。我们一方面希望停顿时间少,另外一方面希望吞吐量高,其实这是矛盾的。因为:在 GC的时候,垃圾回收的工作总量是不变的,如果将停顿时间减少,那频率就会提高;既然频率提高了,说明就会频繁的进行 GC,那吞吐量就会减少,性能就会降低。

吞吐量 = 运行用户代码的时间/(运行用户代码时间+垃圾收集时间)。比如,虚拟机总共运行了100分钟,其中垃圾收集花掉1分钟,那吞吐量就是99%。

注意:以上所有的收集器当中,当执行GC时,都会 stop the world,但是下面的 CMS 收集器却不会这样。

3)、CMS收集器:CMS收集器(Concurrent Mark Sweep:并发标记清除)是一种以获取最短回收停顿时间为目标的收集器。适合应用在互联网站或者B/S系统的服务器上,这类应用尤其重视服务器的响应速度,希望系统停顿时间最短。
    ●  Concurrent Mark Sweep 并发标记清除,并发低停顿。
    ●  标记-清除算法。
    ●  并发阶段会降低吞吐量(因为停顿时间减少了,于是GC的频率会变高)
    ●  老年代收集器(新生代用ParNew)
    ●  -XX:+UseConcMarkSweepGC   打开这收集器

注意:这里的并发指的是与用户线程一起执行。

CMS收集器运行过程:(着重实现标记过程)
    ▶ 初始化标记:根直接关联对象,速度快。
    ▶ 并发标记:主要标记过程,标记全部对象(和用户线程一块)
    ▶ 重新标记:由于并发标记时,用户线程依然运行,因此在正式清理前,再做修正
    ▶ 并发清除:基于标记结果,直接清理对象(和用户线程一块)
GC 的算法及收集器

结论:其中,初始标记和重新标记时,需要 stop the world。整个过程中耗时最长的是并发标记和并发清除,这两个过程都可以和用户线程一起工作。

CMS收集器特点
  ♣ 尽可能降低停顿
  ♣ 会影响系统整体吞吐量和性能(用户线程运行过程中,分一半CPU去做GC,系统性能在GC阶段,反应速度就下降一半)
  ♣ 清理不彻底(因为在清理阶段,用户线程还在运行,会产生新的垃圾,无法清理)
  ♣ 因为和用户线程一起运行,不能在空间快满时再清理

-XX:CMSInitiatingOccupancyFraction 设置触发GC的阈值,如果不幸内存预留空间不够,就会引起 concurrent mode failure 我们来看一下 concurrent mode failure 的日志:碰到下图中的情况,我们需要使用串行收集器作为后备。
GC 的算法及收集器

问题:既然标记清除算法会造成内存空间的碎片化,CMS收集器为什么使用标记清除算法而不是使用标记整理算法?

   CMS 收集器更加关注停顿,它在做 GC 的时候是和用户线程一起工作的(并发执行),如果使用标记整理算法的话,那么在清理的时候就会去移动可用对象的内存空间,那么应用程序的线程就很有可能找不到应用对象在哪里。为了解决碎片的题,CMS 收集器会有一些整理上的参数,如下:
  ✔  -XX:+ UseCMSCompactAtFullCollection:Full GC后,进行一次整理。整理过程是独占的,会引起停顿时间变长
  ✔  -XX:+CMSFullGCsBeforeCompaction:设置进行几次Full GC后,进行一次碎片整理
  ✔  -XX:ParallelCMSThreads:设定CMS的线程数量

4)、G1收集器:是一款面向服务端应用的垃圾收集器。HotSpot开 发团队赋予它的使命是(在比较长期的)未来可以替换掉JDK 1.5 中发布的 CMS 收集器。与其他 GC 收集器相比,G1 具备如下特点。
   ☞ 并行与并发:G1能充分利用多CPU、多核环境下的硬件优势,使用多个CPU(CPU或者CPU核心)来缩短Stop-The-World停顿的时间,部分其他收集器原本需要停顿Java线程执行的GC动作,G1收集器仍然可以通过并发的方式让Java程序继续执行。
   ☞ 分代收集:与其他收集器一样,分代概念在G1中依然得以保留。虽然G1可以不需要其他收集器配合就能独立管理整个GC堆,但它能够采用不同的方式去处理新创建的对象和已经存活了一段时间、熬过多次GC的旧对象以获取更好的收集效果。
   ☞ 空间整合:与CMS的“标记—清理”算法不同,G1从整体来看是基于“标记—整理”算法实现的收集器,从局部(两个Region之间)上来看是基于“复制”算法实现的,但无论如何,这两种算法都意味着G1运作期间不会产生内存空间碎片,收集后能提供规整的可用内存。这种特性有利于程序长时间运行,分配大对象时不会因为无法找到连续内存空间而提前触发下一次GC。
   ☞ 可预测的停顿:这是G1相对于CMS的另一大优势,降低停顿时间是G1和CMS共同的关注点,但G1除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为M毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N毫秒,这几乎已经是实时Java(RTSJ)的垃圾收集器的特征了。

    在G1之前的其他收集器进行收集的范围都是整个新生代或者老年代,而G1不再是这样。使用G1收集器时,Java堆的内存布局就与其他收集器有很大差别,它将整个Java堆划分为多个大小相等的独立区域(Region),虽然还保留有新生代和老年代的概念,但新生代和老年代不再是物理隔离的了,它们都是一部分Region(不需要连续)的集合,它仍然属于分代收集器。不过,这些区域的一部分包含新生代,新生代的垃圾收集依然采用暂停所有应用线程的方式,将存活对象拷贝到老年代或者 Survivor 空间。老年代也分成很多区域,G1收集器通过将对象从一个区域复制到另外一个区域,完成了清理工作。这就意味着,在正常的处理过程中,G1完成了堆的压缩(至少是部分堆的压缩),这样也就不会有cms内存碎片问题的存在了。
GC 的算法及收集器
    在G1中,还有一种特殊的区域,叫 Humongous 区域。 如果一个对象占用的空间超过了分区容量50%以上,G1收集器就认为这是一个巨型对象。这些巨型对象,默认直接会被分配在年老代,但是如果它是一个短期存在的巨型对象,就会对垃圾收集器造成负面影响。为了解决这个问题,G1划分了一个Humongous区,它用来专门存放巨型对象。如果一个H区装不下一个巨型对象,那么G1会寻找连续的H分区来存储。为了能找到连续的H区,有时候不得不启动Full GC。

G1提供了两种GC模式,Young GC和Mixed GC,两种都是Stop The World(STW)的:对TLAB(Thread Local Allocation Buffer)空间中无法分配的对象,JVM会尝试在Eden空间中进行分配。如果Eden空间无法容纳该对象,就只能在老年代中进行分配空间。

【1】G1 Young GC:Young GC 主要是对Eden区进行GC,它在Eden空间耗尽时会被触发。在这种情况下,Eden空间的数据移动到Survivor空间中,如果Survivor空间不够,Eden空间的部分数据会直接晋升到年老代空间。Survivor区的数据移动到新的Survivor区中,也有部分数据晋升到老年代空间中。最终Eden空间的数据为空,GC停止工作,应用线程继续执行。
GC 的算法及收集器
GC 的算法及收集器
【2】G1 Mix GC:Mix GC不仅进行正常的新生代垃圾收集,同时也回收部分后台扫描线程标记的老年代分区。它的GC步骤分2步:
  ☞  全局并发标记(global concurrent marking)
  ☞  拷贝存活对象(evacuation)

在进行 Mix GC 之前,会先进行 global concurrent marking(全局并发标记)。 global concurrent marking 的执行过程是怎样的呢?

    在G1 GC 中,它主要是为 Mixed GC 提供标记服务的,并不是一次 GC 过程的一个必须环节。global concurrent marking 的执行过程分为五个步骤:
   ●  初始标记(initial mark,STW):在此阶段,G1 GC 对根进行标记。该阶段与常规的 (STW) 年轻代垃圾回收密切相关。
   ●  根区域扫描(root region scan):G1 GC 在初始标记的存活区扫描对老年代的引用,并标记被引用的对象。该阶段与应用程序(非 STW)同时运行,并且只有完成该阶段后,才能开始下一次 STW 年轻代垃圾回收。
   ●  并发标记(Concurrent Marking):G1 GC 在整个堆中查找可访问的(存活的)对象。该阶段与应用程序同时运行,可以被 STW 年轻代垃圾回收中断
   ●  最终标记(Remark,STW):该阶段是 STW 回收,帮助完成标记周期。G1 GC 清空 SATB 缓冲区,跟踪未被访问的存活对象,并执行引用处理。
   ●  清除垃圾(Cleanup,STW):在这个最后阶段,G1 GC 执行统计和 RSet 净化的 STW 操作。在统计期间,G1 GC 会识别完全空闲的区域和可供进行混合垃圾回收的区域。清理阶段在将空白区域重置并返回到空闲列表时为部分并发。

    G1 到现在可以知道哪些老的分区可回收垃圾最多。 当全局并发标记完成后,在某个时刻,就开始了Mix GC。这些垃圾回收被称作“混合式”是因为他们不仅仅进行正常的新生代垃圾收集,同时也回收部分后台扫描线程标记的分区。混合式垃圾收集如下图:
  GC 的算法及收集器

   混合式GC也是采用的复制的清理策略,当GC完成后,会重新释放空间。
GC 的算法及收集器

5)、ZGC收集器:JDK 11 的新特性,可通过JDK 11 进行系统学习

----如果喜欢,点个  红心♡  支持以下,谢谢----

相关文章:

  • 2021-07-13
  • 2021-04-13
  • 2021-11-19
  • 2021-11-13
猜你喜欢
  • 2021-05-19
  • 2021-12-17
  • 2021-08-19
  • 2021-06-17
  • 2022-01-18
  • 2021-11-16
  • 2021-07-12
相关资源
相似解决方案