JVM垃圾回收算法

2024-07-29 18:41:34 浏览数 (2)

前言

1.什么是对象垃圾

一般有两种方法来判断:

  • 引用计数器:为每个对象创建一个引用计数,有对象引用时计数器 1,引用被释放 时计数 -1,当计 数器为 0 时就可以被回收。它有一个缺点不能解决循环引用的问 题;
  • 可达性分析:从 GC Roots 开始向下搜索,搜索所走过的路径称为引用链。当一个对 象到 GC Roots 没有任何引用链相连时,则证明此对象是可以被回收的

2.垃圾回收的几种常用算法

标记-清除算法、复制算法、标记-整理算法、分代收集算法

为了比较形象从网上找来了一些图片。

2.1 标记-清除算法

分为标记和清除两个阶段,首先标记出所有需要回收的对象,标记完成后统一回收所有被标记的对象

缺点:标记和清除两个过程效率都不高;标记清除之后会产生大量不连续的内存碎片。

2.2 复制算法

把内存分为大小相等的两块,每次存储只用其中一块,当这一块用完了,就把存活的对象全部复制到另一块上,同时把使用过的这块内存空间全部清理掉,往复循环。

缺点:内存使用率不高,只有原来的一半。

2.3 标记-整理算法

先对可用的对象进行标记,然后所有被标记的对象向一段移动,最后清除可用对象边界以外的内存,如下图。

2.4.分代收集算法

把堆内存分为新生代和老年代,新生代又分为 Eden 区、From Survivor 和 To Survivor。一般新生代中的对象基本上都是朝生夕灭的,每次只有少量对象存活,因此采用复制算法,只需要复制那些少量存活的对象就可以完成垃圾收集;老年代中的对象存活率较高,就采用标记-清除和标记-整理算法来进行回收。

在这些区域的垃圾回收大概有如下几种情况:

大多数情况下,新的对象都分配在Eden区,当 Eden 区没有空间进行分配时,将进行一次 Minor GC,清理 Eden 区中的无用对象。清理后,Eden 和 From Survivor 中的存活对象如果小于To Survivor 的可用空间则进入To Survivor,否则直接进入老年代);Eden 和 From Survivor 中还存活且能够进入 To Survivor 的对象年龄增加 1 岁(虚拟机为每个对象定义了一个年龄计数器,每执行一次 Minor GC 年龄加 1),当存活对象的年龄到达一定程度(默认 15 岁)后进入老年代,可以通过 -XX:MaxTenuringThreshold 来设置年龄的值。

当进行了 Minor GC 后,Eden 还不足以为新对象分配空间(那这个新对象肯定很大),新对象直接进入老年代。

占 To Survivor 空间一半以上且年龄相等的对象,大于等于该年龄的对象直接进入老年代,比如 Survivor 空间是 10M,有几个年龄为 4 的对象占用总空间已经超过 5M,则年龄大于等于 4 的对象都直接进入老年代,不需要等到 MaxTenuringThreshold 指定的岁数。

在进行 Minor GC 之前,会判断老年代最大连续可用空间是否大于新生代所有对象总空间,如果大于,说明 Minor GC 是安全的,否则会判断是否允许担保失败,如果允许,判断老年代最大连续可用空间是否大于历次晋升到老年代的对象的平均大小,如果大于,则执行 Minor GC,否则执行 Full GC。

当在 java 代码里直接调用 System.gc() 时,会建议 JVM 进行 Full GC,但一般情况下都会触发 Full GC,一般不建议使用,尽量让虚拟机自己管理 GC 的策略。

永久代(方法区)中用于存放类信息,jdk1.6 及之前的版本永久代中还存储常量、静态变量等,当永久代的空间不足时,也会触发 Full GC,如果经过 Full GC 还无法满足永久代存放新数据的需求,就会抛出永久代的内存溢出异常。

大对象(需要大量连续内存的对象)例如很长的数组,会直接进入老年代,如果老年代没有足够的连续大空间来存放,则会进行 Full GC。

2.4.1分带收集算法中的Minor GC和Full GC。

Minor GC是新生代GC,指的是发生在新生代的垃圾收集动作。由于java对象大都是朝生夕死的,所以Minor GC非常平凡,一般回收速度也比较i快。

Major GC/Full GC 是老年代GC,指的是发生在老年代的GC,出现Major GC一般经常会伴有Minor GC,Major GC的速度比Minor GC慢的多。

何时发生?

  • (1)Minor GC发生:当jvm无法为新的对象分配空间的时候就会发生Minor gc,所以分配对象的频率越高,也就越容易发生Minor gc。
  • (2)Full GC:发生GC有两种情况,

①当老年代无法分配内存的时候,会导致MinorGC ②当发生Minor GC的时候可能触发Full GC,由于老年代要对年轻代进行担保,由于进行一次垃圾回收之前是无法确定有多少对象存活,因此老年代并不能清除自己要担保多少空间,因此采取采用动态估算的方法:也就是上一次回收发送时晋升到老年代的对象容量的平均值作为经验值,这样就会有一个问题,当发生一次Minor GC以后,存活的对象剧增(假设小对象),此时老年代并没有满,但是此时平均值增加了,会造成发生Full GC


3. 垃圾回收器

3.1主要的垃圾回收器

Serial:最早的单线程串行垃圾回收器。 Serial Old:Serial 垃圾回收器的老年版本,同样也是单线程的,可以作为 CMS 垃圾回收器的备选预案。 ParNew:是 Serial 的多线程版本。 Parallel 和 ParNew 收集器类似是多线程的,但 Parallel 是吞吐量优先的收集器,可 以牺牲等待时间换取系统的吞吐量。 Parallel Old 是 Parallel 老生代版本,Parallel 使用的是复制的内存回收算法, Parallel Old 使用的是标记-整理的内存回收算法。 CMS:一种以获得最短停顿时间为目标的收集器,非常适用 B/S 系统。 G1:一种兼顾吞吐量和停顿时间的 GC 实现,是 JDK 9 以后的默认 GC 选项。

这里要重点说一下GMS垃圾回收器 分代垃圾回收器

3.2GMS垃圾回收器

CMS 是英文 Concurrent Mark-Sweep 的简称,是以牺牲吞吐量为代价来获得最短回收停 顿时间的垃圾回收器。对于要求服务器响应速度的应用上,这种垃圾回收器非常适合。在 启动 JVM 的参数加上“-XX: UseConcMarkSweepGC”来指定使用 CMS 垃圾回收器。 CMS 使用的是标记-清除的算法实现的,所以在 gc 的时候回产生大量的内存碎片,当剩余 内存不能满足程序运行要求时,系统将会出现 Concurrent Mode Failure,临时 CMS 会 采用 Serial Old 回收器进行垃圾清除,此时的性能将会被降低

3.3分带垃圾回收器

  • 分代回收器有两个分区:老生代和新生代,新生代默认的空间占比总空间的 1/3,老生代 的默认占比是 2/3。
  • 新生代使用的是复制算法,新生代里有 3 个分区:Eden、To Survivor、From Survivor, 它们的默认占比是 8:1:1,它的执行流程如下:

把 Eden From Survivor 存活的对象放入 To Survivor 区; 清空 Eden 和 From Survivor 分区; From Survivor 和 To Survivor 分区交换,From Survivor 变 To Survivor,To Survivor 变 From Survivor。

  • 每次在 From Survivor 到 To Survivor 移动时都存活的对象,年龄就 1,当年龄到达 15(默认配置是15)时,升级为老生代。大对象也会直接进入老生代。
  • 老生代当空间占用到达某个值之后就会触发全局垃圾收回,一般使用标记整理的执行算 法。以上这些循环往复就构成了整个分代垃圾回收的整体执行流程。

新生代垃圾回收器和老生代垃圾回收器都有哪些?有什么区别?

  • 新生代回收器:Serial、ParNew、Parallel Scavenge
  • 老年代回收器:Serial Old、Parallel Old、CMS
  • 整堆回收器:G1

区别: 新生代垃圾回收器一般采用的是复制算法,复制算法的优点是效率高,缺点是内存利用率 低;老年代回收器一般采用的是标记-整理的算法进行垃圾回收。

0 人点赞