G1 GC笔记

php转java重构搭脚手架的时候,因为是从新开始的项目,所以打算直接升jdk版本+spring boot3.0+。查了下jdk17自带的zgc是保障低延迟的,并且为了保障低延迟,需要的额外空间开销比较大,出于以下角度考虑,还是选择了G1

  • 我们的堆往往没那么大,所以内存比较宝贵,尽可能减少额外的内存额外开销
  • 我们对吞吐量的要求明显高于低延迟,单接口百ms级别都是可以接受的,此时zgc的毫秒级别延迟在整个接口百ms的基准下影响不大,为了这点延迟去降低实际可用内存得不偿失
  • G1 GC近十来年了,ZGC还较为新,使用G1 GC相关的问题以及调优可以参考的更多

其实CMS更适合,不过用新不用旧,并且G1在大多数情况下它的GC逻辑也是接近CMS的

如何学习

个人一开始尝试去理解一些复杂概念,但是这些复杂概念只是通过论述很难彻底理解,所以转为了解这些结构的功能而非实现,了解GC的过程而非细节,以便于实际工作调优或者排查问题。

毕竟除非需要去修改GC的源码,应该是不需要对里面使用到的一些数据结构和方法进行详细的了解(但是大部分博客文章中往往反而会讲,就会讲的你很晕)。

这篇笔记记录给自己不定时翻出来看看回忆下,不会带一些需要大量篇幅才可以解释清楚的细节。

G1 GC的GC模式

内存分布

G1 中,将内存划分为多个大小相同的region,其中可以简单的认为分为新生代region和老年代region

与CMS相同:

  • 新生代在N次GC后未被回收,进入老年代
  • 大对象直接进老年代

GC收集模式

G1 GC的GC模式包含以下几种收集模式(内容来源于R大):

  1. young GC(或者叫minor GC):只收集young gen里的所有region,也就是eden和survivor。控制young GC开销的手段是动态改变young region的个数;
  2. mixed GC:收集young gen里的所有region,外加若干选定的old gen region。控制mixed GC开销的手段是选多少个、哪几个old gen region。
  3. 其实没有3了。G1 GC的控制范围内没有full GC。如果mixed GC无法跟上mutator分配的速度,导致没有足够的空region来完成mixed GC,那么就会使用serial old GC( mark-compact)来对整堆收集一次。

所以G1 GC的GC,一般都在几个过程里:

  1. 要么在young gc,只回收新生代,可以类比和CMS一起用的young gc,用的是也是标记复制
  2. 要么在mixed GC,通过一次young GC来先做标记,然后再并发标记,并发标记后根据一个回收的耗时评估+你配置的预期停顿时间,来决定回收那些老年代old region。
  3. 要么在full gc,新生代和老年代都无法分配内存了,直接进入最原始的STW然后遍历整个堆做收集

G1 的GC细节(不那么细的)

G1 性能方面的优化

解决问题:新生代GC时,老年代对新生代的引用问题

概念1:卡表card table

可以先看下https://www.cnblogs.com/binyue/p/17281785.html

在新生代GC时,可能存在老年代对新生代的引用,此时想要回收新生代的对象,还需要扫描整个老年代吗?那岂不是等于full gc(扫描整个堆)了?卡表使用额外的空间减少扫描的消耗。

划重点:卡表是一个数据结构,用于记录老年代对象对新生代的引用,此时回收新生代时,只需要扫描那些卡表中有的老年代对象即可(不需要care卡表的实现细节

概念2:Remember Set

Remember Set是G1中用于解决回收时老年代引用新生代的问题的,因为G1的回收是针对Region的,所以要保证每个Region可以单独被回收,那么每个Region就需要记录自己的卡表

划重点Remember Set是每个region维护的一个数据结构,里面维护了自己的卡表,在新生代回收时用于减小老年代扫描的规模

G1 回收正确性

解决问题:解决并发标记的正确性

因为标记过程与用户线程执行是并发的,所以会存在你标记后但是被修改了的情况,那么就会漏标或者多标

概念1:黑白灰标记

这里有一个模型来简化问题,即黑白灰标记,使用黑白灰来标记对象

  • 黑色 - 该对象已经标记,且该对象的引用字段也都已经处理完或已经加到任务队列中
  • 灰色 - 该对象已经标记,但是对象的引用字段还没有处理完
  • 白色 - 该对象还没有标记

概念2:satb以及浮动垃圾

satb是一个解决并发标记问题的方案。在一开始,认为所有对象都是存活的。然后在标记过程中,使用一个数据结构,通过代码中在改变引用的前后加上指令,实现一个切面一样的效果,以此来在这个对象的引用改变的时候,记录那些变更了引用的对象到satb里去。

在实际清理前,只需要扫描satb里的对象就可以了。

satb会产生浮动垃圾(该回收的没标记,下次才能回收),但是不会漏标(不会因为并发标记的错误而导致实际内存泄露,即保证了正确性)。

概念3:新产生的内存,TopAtMarkStart,TAMS指针

在并发标记执行后,实际回收执行前。新产生的内存也应该是存活的,所以需要避免回收这部分,在region内定义一个指针,记录并发标记时的地址,大于该指针的内存全是并发标记后新产生的

G1 回收保证吞吐

G1进行并发标记、最终标记、清理这三个阶段后,实际并没有回收内存。这里的清理阶段只是清理一些标记状态,并且把完全没有活对象的region整体回收到可分配的region列表里。

实际清理后,有一个完全可以独立执行的evacuation阶段,这个阶段根据标记的结果,分析每个老年代的region可回收信息,然后经过一堆统计学算法和你配置的信息的评估,决定回收哪些region。然后活对象拷贝到空region里去,回收原本的region的空间。

参考:

[资料] [HotSpot VM] 请教G1算法的原理

深入分析G1垃圾收集器实现原理

G1 GC核心原理介绍

垃圾回收之CardTable和Remembered Set

版权声明:除特殊说明,博客文章均为intotw原创,依据CC BY-SA 4.0许可证进行授权,转载请附上出处链接及本声明。
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇