垃圾回收器
串行
-
单线程: 堆内存较小,适合个人电脑
-
jvm开关:
-
单个垃圾回收线程
吞吐量优先(运行用户代码时间 / (运行用户代码时间 + 垃圾收集时间))
-
多线程
-
堆内存较大,多核cpu
-
让单位时间内,STW的时间最短 0.2 0.2 = 0.4
-
jvm开关(1.8默认开启)
-
多个垃圾回收线程, 控制垃圾回收的线程数-XX:ParallelGCThreads=n
响应时间优先
-
多线程
-
标记+清除算法 可能会造成很多内存碎片,导致并发失败,垃圾回收器会退化成SerialOld导致时间增加
-
堆内存较大,多核cpu
-
尽可能让单次STW的时间最短 0.1 0.1 0.1 0.1 0.1 = 0.5
-
在某些时刻用户线程和垃圾回收线程同时运行
1.初始标记:单线程来进行初始标记,这个过程会非常非常的快所以采用单线程,通过初始标记来找到GCROOT能够关联到的对象。(STW)
2.并发标记:多线程来进行标记,防止初始标记不完整,对GCROOT关联进一步的追踪。可以和用户线程并发执行。
3.重新标记:单线程来进行标记,为了修正并发标记期间因用户程序继续动作而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段稍长一些,但远比并发标记的时间短。(STW)
4.并发清理:与用户线程并发进行,减少停顿时间。
- 因为是并发的,所以垃圾回收时还会有新垃圾产生(浮动垃圾),所以要预留一些空间给浮动垃圾用来存放
G1(目前主流)
使用场景
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GipKX4pW-1659364061703)(C:\Users\wjl\AppData\Roaming\Typora\typora-user-images\image-20220801183710866.png)]
region分区
- 虽然 G1 仍然遵循分代收集理论,但是 G1 不再坚持固定大小、固定数量的分代区域划分,而是将整个内存区域划分为若干个大小相等的独立小区域(Region),每个 Region 都能扮演 Eden、Survivor、Old 区。新生代和老年代的内存在物理上不再是连续的,而是逻辑上处于连续。示意图如下。
停顿时间
-
G1 还可以设置一个期望的停顿时间,然后在期望的停顿时间内,对**「一部分 Region」进行垃圾回收。在平时的工作中,我们经常为 JVM 设置合理的内存大小,优化部分参数,其实就是为了尽量减少 Minor GC 和 Full GC,从而减少系统的停顿时间,而 G1 垃圾回收器直接我们提供了最大停顿时间这个参数(「-XX:MaxGCPauseMillis」**)。
-
在系统运行过程中,G1 会收集每个 Region 的回收耗时、垃圾占比等各个可测量的信息,然后计算回收每个 Region 带来的收益大小(可回收的内存+回收耗时),通过维护一个优先级列表,然后在设置的最大停顿时间内,回收那些能带来最大收益的 Region。
-
虽然 G1 为我们提供了最大停顿时间这个参数,但是我们也不能认为,这个参数设置得越小越好。如果设置得太小,那么会因为每次 GC 可以停顿的时间太少,导致每次 GC 只能回收极少的 Region,如果此时内存的分配速度大于 Region 回收的速度,那么在系统初期,可能会因为还有空闲内存可以支撑一段时间,但是时间一长,就会导致空闲内存越来越少,最终触发 Full GC,从而导致系统停顿时间更长。
Young GC
-
在 G1 中,Eden、Survivor、老年代的大小是在动态变化的。在初始时,新生代占整个堆内存的 5%,可以通过参数**「G1NewSizePercent」**设置。
在 G1 中,虽然进行了 Region 分区,但是新生代依旧可以被分为 Eden 区和 Survivor 区,参数 SurvivorRatio 依旧表示 Eden/Survivor 的比值。
随着系统的运行,Eden 区的对象越来越多,当达到 Eden 区的最大大小时,就会触发 Minor GC。新生代的最大大小默认为整个堆内存的 60%,可以通过参数**「G1MaxNewSizePercent」**控制,默认值为 60。
-
G1 垃圾回收器在进行新生代的垃圾回收时,会采用复制算法来回收垃圾,不用考虑并发的场景,全程都是 STW,它会根据设置的停顿时间,尽可能的最大效率的回收新生代区域。
-
Yong GC的回收过程和其他垃圾回收器差不多,也是先标记,再复制。同时由于
Yong GC
的标记过程和后面Mixed GC中的并发标记(Concurrent Marking)的第一个阶段,初始标记(initial marking)所做的工作相同,因此,Concurrent Marking的初始标记阶段总是搭载着Yong GC
进行。
进入老年代
新生代的对象要进入老年代,需要达到以下两个条件中的其中之一即可。
- 「多次躲过新生代的回收」,对象年龄达到**「MaxTenuringThreshold」**。 在每次 Minor GC 时,新生代的对象如果存活,会被移动到 Survivor 区中,同时会将对象的年龄加 1,当对象的年龄达到 MaxTenuringThreshold 后,就被被移到老年代中。
- 「符合动态年龄判断规则」。如果某次 Minor GC 过后,发现 Survivor 区中相同年龄的对象达到了 Survivor 的 50%,那么该年龄及以上的对象,会被直接移动到老年代中。 例如某次 Minor GC 过后,Survivor 区中存在年龄分别为 1、2、3、4 的对象,而年龄为 3 的对象超过了 Survivor 区的 50%,那么年龄大于等于 3 的对象,就会被全部移动到老年代中。
混合GC
- 在 G1 中,「不存在单独回收老年代的行为,而是当要发生老年代的回收时,同时也会对新生代以及大对象进行回收,因此这个阶段称之为混合回收(Mixed GC)」。
- 一般不会将全部region回收,只会回收一部分回收价值较高的,用来节约时间
优缺点
与同样具有低延时的垃圾回收器 CMS 相比,G1 既有优点也有缺点。
首先,G1 中可以指定最大停顿时间、对内存进行 Region 分区、按照收益动态进行垃圾回收,这些特性带来的红利都是 CMS 所不具有的。另外,G1 垃圾回收器从局部看,采用的的是复制算法,将一个 Region 中存活的对象复制到另一个 Region 中;从整体上看,G1 回收器采用的是标记-整理算法。这两种算法最终都不会带来内存碎片,这有利于系统的长时间运行。而 CMS 则是采用的是标记-清除算法,会带来内存碎片,当连续内存不足以分配一个对象时,会产生 Full GC。
虽然 G1 的优点很多,但是它还不足以完全替代 CMS,它也存在在很明显的缺点。
「G1 的内存占用相对而言,比较大」。G1 堆内存采用 Region 分区设计,每个 Region 中都存在一个记忆集,而其他传统的垃圾回收器中,整个堆内存只需要维护一份记忆集即可,因此 G1 中记忆集所占用的内存相比传统的垃圾回收器而言,会大很多。「加上其他内存消耗,G1 所占用的内存空间可能达到堆内存的 20%,甚至更多」。(这个数据参考自周志明《深入理解 Java 虚拟机》第三版)。
「G1 对系统造成的负载较高」。G1 和 CMS 都是用到了写屏障来维护记忆集,不同的是,CMS 使用了写后屏障来维护记忆集,而 G1 在设计上由于更复杂,不仅使用了写前屏障还使用了写后屏障。G1 中写前屏障用来跟踪并发时的指针变化,从而实现 SATB(原始快照算法),使用写后屏障来维护记忆集中的卡表。由于 G1 对写屏障的复杂操作比 CMS 会消耗更多的资源,因此在 CMS 中,直接使用同步操作来实现写屏障,而在 G1 中不得不使用类似于队列的数据结构来实现写前屏障和写后屏障,进行异步处理。