基础篇:java GC 总结,建议收藏

2022-08-30 15:36:19 浏览数 (2)

  • 垃圾标记算法
  • 垃圾回收算法
  • major gc、minor gc、full gc、mixed gc 又是什么,怎么触发的
  • 垃圾回收器的介绍
  • Safe Point 和 Safe Region
  • 什么是 TLAB 和 PLAB ?
  • CMS、G1 新生代的 GC 如何避免全堆扫描
  • CMS 和 G1 为了防止并发时的漏标分别用了什么手段
  • 什么是 logging write barrier
  • CMS 常见问题
  • GC 事件和日志分析
  • JVM 常用参数汇总 关注公众号,一起交流:潜行前行

1 垃圾标记算法

引用计算法

  • 引用计数法是最简单有效的垃圾标记方法,它会把对象被引用的次数记录下来,当被引用时,计数加一。当其他变量不再指向目标对象时,则引用减一。对象引用数为零时 ,则可以进行内存回收释放
  • 无法解决循环引用问题

根可达性分析

  • 从 GC Root 开始进行对象搜索,可以被搜索到的对象即为可达对象,不可达对象便可以作为垃圾被回收掉。目前 Java 中主流的虚拟机均采用此算法
  • 在Java语言里,可作为GC Roots的对象包括下面几种:
    • 虚拟机栈(栈帧中的本地变量表)中的引用的对象
    • 方法区中的类静态属性引用的对象
    • 方法区中的常量引用的对象
    • 本地方法栈中JNI(即一般说的Native方法)的引用的对象

2 垃圾回收算法

  • 复制:将一块内存区域进行对半分,当一半的内存使用完时,便将其中存活的对象复制到另一半内存区域中,原先的区域进行回收。不存在内存碎片问题,实现简单运行高效,但是有个缺点,就是对内存的利用率只有 50%
  • 标记清除:算法分为 “标记” 和 “清理”两个阶段
    • 标记阶段:标记出所有需要回收的对象
    • 清除阶段:标记完成后,统一清除回收被标记的对象
    • 由于对象之前在内存中的分布是无规律的,标记清除算法会产生大量不连续的内存碎片,造成连续的大内存空间缺失,阻碍大内存对象的分配,严重时会触发垃圾回收,甚至出现 OutOfMemeryError。而且如果大部分的对象是朝生夕死的,标记的对象就会更多,效率更低
  • 标记整理:步骤与 标记清除步 骤一致,但它的第二步是整理标记之外的所有对象,将所有对象向前移动,之后直接回收掉存活对象之外的内存区域。标记整理 不会存在内存碎片,但是效率是偏低的

按年代划分-分代算法

  • 新生代回收(Minor GC/Young GC):指只是进行新生代的回收
  • 老年代回收(Major GC/Old GC):指只是进行老年代的回收。目前只有 CMS 垃圾回收器会有这个单独的回收老年代的行为
  • 整堆回收(Full GC):收集整个堆,包括年轻代、老年代,如果有永久代的话还包括永久代

3 major gc、minor gc、mixed gc又是什么,怎么触发的?

  • major gc:个人理解应该是指 old gc。不过有些人认为和 full gc 等价
    • 执行 System.gc()、jmap -dump 等命令会触发 full gc
    • 有永久代的话,永久代满了也会触发 full gc
    • 大对象直接在老年代申请分配,如果此时老年代空间不足则会触发 full gc
    • 新生代对象 gc 年龄到达阈值需要晋升,老年代如果放不下的话会触发 full gc
  • minor gc:指的也是年轻代的 young gc
    • 年轻代的 eden 快要被占满的时候会触发 young gc
    • eden 快满的触发因素有两个,一个是为对象分配内存不够,一个是为 TLAB 分配内存不够
  • mixed gc:这个是 G1 收集器特有的,指的是收集整个年轻代和部分老年代的 GC
    • 在 young gc 之后,当老年代的堆占有率达到参数 (-XX:InitiatingHeapOccupancyPercent) 设定的值时则触发 mixed GC

4 垃圾回收器的介绍

Serial New 和 Serial Old

  • jvm 诞生初期所采用的垃圾回收器,单线程,独占式,适合单 CPU
  • 单线程进行垃圾回收时,必须暂停所有的工作线程,直到它回收结束。这个暂停称之为 Stop The World ,但是 STW 会带来差的性能影响

Parallel Scavenge 和 Parallel Old

  • 为了提高 jvm 的回收效率,jvm 使用了多线程的垃圾回收器,关注吞吐量的垃圾回收器,可以更高效的利用CPU 时间,从而尽快完成程序的运算任务
  • Parallel Scavenge 收集器提供了两个参数用于精确控制吞吐量,可以分别是控制最大垃圾收集停顿时间
    • -XX:MaxGCPauseMillis参数以及直接设置吞吐量大小的
    • -XX:GCTimeRatio参数。其也经常被称为“吞吐量优先”收集器

ParNew 和 CMS

  • ParNew 与 Parallel Scavenge 差不多。区别是 Parallel Scavenge 是一个可控制的吞吐量并行垃圾回收器,ParNew 没有参数来控制吞吐量和停顿时间
  • ParNew 可以和 CMS 搭配使用,而 Parallel Scavenge 不可用,其根本原因是设计上就是没想过兼容 CMS
  • CMS(Concurrent Mark Sweep) 是一款针对老年代的垃圾回收器,追求最短的回收停顿时间(STW)为目标,采用的是 标记-清除 算法
CMS 的回收流程
  • 初始标记:只标记与 GC Root 有直接关联的对象,这类的对象比较少,标记快。需要 STW
  • 并发标记:并发标记与初始化标记的对象有关联的所有对象,这类的对象比较多所以采用的并发,与用户线程一起跑
  • **并发预清理(Concurrent Preclean)**:并发标记阶段是与应用线程并发执行的,有些引用关系已经发生改变,通过卡片标记(Card Marking),如果引用关系发生改变,JVM会将发生改变的区域标记位“脏区”(Dirty Card),然后在本阶段,这些脏区会被找出来,刷新引用关系,清除“脏区”标记
  • **并发可取消的预清理(Concurrent Abortable Preclean)**:和并发预清理阶段工作差不多,用来减少Final Remark 阶段的暂停时间。该阶段会不断循环处理:标记老年代的可达对象、扫描处理Dirty Card区域中的对象引用关系。循环中断条件
    • 达到循环次数
    • 达到循环执行时间阈值
    • 新生代内存使用率达到阈值
  • 最终标记 (Final Remark):修正并发标记时候标记产生异动的对象标记,这块的时间比初始标记稍长一些,但是比起并发标记要快很多。需要 STW
    • 遍历新生代对象,重新标记
    • 根据GC Roots,重新标记
    • 遍历老年代的Dirty Card,重新标记
  • 并发清除(Concurrent Sweep):与用户线程一起运行,进行对象回收清除
  • 缺点
    • 浮动垃圾:在CMS进行并发清除阶段,GC线程是并发的,所以在清除的时候用户线程会产出新的垃圾。因此在进行回收时需要预留一部分的空间来存放这些新产生垃圾(JDK 1.6 设置的阈值为92%)。但是如果用户线程产出的垃圾比较快,预留内存放不下的时候就会出现 Concurrent Mode Failure,这时虚拟机将临时启用 Serial Old 来替代 CMS。
    • 内存碎片:因为采用的是 标记-清除 算法,会产生内存碎片

G1

  • G1 垃圾回收器的设计思想与上面的垃圾回收器的都不一样,前面垃圾回收器采用的都是 分代划分 的方式进行设计的,而 G1 则是将堆看作是一个整体的区域,这个区域被划分成了一个个大小一致的独立区域(Region),而每个区域都可以根据需要成为 Eden、Survivor 以及老年代区域
G1的回收流程

image.png

  • 初始标记(Initial Marking):标记与 GC Roots 能关联到的对象,修改 TAMS (Top at Mark Start) 给堆对象拍个快照,这个过程是需要暂停用户线程的,但是耗时非常的短。需要 STW

image.png

  • Region 记录着两个 top-at-mark-start (TAMS) 指针,分别为 prevTAMSnextTAMS。在 nextTAMS~top的之间对象是新分配的,被视为隐式 marked(存活对象)。对象是否存活使用 bitmap 位图标志,prevBitmap 记录第 n-1 轮 concurrent marking 所得的对象存活状态,nextBitmap 记录第 n 轮 concurrent marking 的结果
  • top 是该 Region 的当前分配指针,[bottom, top) 是当前该 Region 已用的部分,[top, end) 是尚未使用的可分配空间
  • [bottom, prevTAMS):这部分里的对象存活信息可以通过 prevBitmap 来得知
  • [prevTAMS, nextTAMS):这部分里的对象在第 n-1 轮 concurrent marking 是隐式存活的
  • [nextTAMS, top):这部分里的对象在第 n 轮 concurrent marking 是隐式存活的

  • 并发标记(Concurrent Marking):进行扫描标记所有可回收的对象。当扫描完成后,并发会有引用变化的对象,而这些对象会漏标这些漏标的对象会被 SATB 算法所解决
    • SATB(snapshot-at-the-beginning):旧对象区域 [bottom, nextTAMS) 按 nextTAMS 生成时的存活快照为准,即对象在 nextTAMS 生成之后变成垃圾也不会被回收
    • 如果在并发标记时,引用发生改变的对象将被放入 satb_mark_queue 队列(写屏障实现),之后在最终标记阶段,以队列对象为根重新标记可能漏标的对象 (按快照的存活关系处理)
    • 新分配对象区域 [nextTAMS, top) 可能存在浮动垃圾,将在下次被收集
  • 最终标记 (Final Marking):暂停所有的用户线程,对之前漏标的对象进行一个标记。需要 STW
  • 筛选回收( Live Data Counting and Evacuation):更新Region的统计数据,对各个 Region 的回收价值进行一个排序,根据用户所设置的停顿时间制定一个回收计划,自由选择任意个 Region 进行回收。将需要回收的Region 复制到空的 Region 区域中,再清除掉原来的整个Region区域。这块还涉及到对象的移动所以需要暂停所有的用户线程,多条回收器线程并行完成。需要 STW
    • 为什么需要 Stop The World呢?因为在筛选回收阶段首先会对各个Region的回收价值和成本进行排序,根据用户所期望的GC停顿时间(可以用JVM参数 -XX:MaxGCPauseMillis 指定)来制定回收计划,可以自由选择任意多个Region构成回收集,然后把决定回收的那一部分Region的存活对象复制到空的Region中,再清理掉整个旧Region的全部空间
    • 其实也可以做到与用户程序一起并发执行,但是停顿用户线程将大幅提高收集效率
G1 与 CMS 的区别
  • G1 从整体来看是基于 “标记—整理” 算法实现的收集器,从局部(两个 Region 之间)上来看是基于“复制”算法实现的,这意味着 G1 运作期间不会产生内存空间碎片,收集后能提供规整的可用内存
  • G1 SATB 利用 write barrier 将所有即将被删除的引用关系的旧引用记录下来,最后以这些旧引用为根 Stop The World 地重新扫描一遍即可避免漏标问题。因此 G1 Final Marking 阶段 Stop The World 与 CMS 的 remark 有一个本质上的区别,那就是这个暂停只需要扫描以 write barrier 所追踪到对象为根的对象, 而 CMS 的 remark 需要重新扫描整个根集合(产生新的根对象指向引用,需要扫描整个根集合),因而 CMS remark 有可能会非常慢
G1 中的三种垃圾回收模式
  • YoungGC 触发条件:young eden 区不够用
  • Mixed GC 触发条件
    • 在 YoungGC 之后,会触发 Concurrent Marking 并发阶段,接着进行 mixed GC,mixed GC 主要工作就是回收并发标记过程中筛选出来的 Region 。和 young GC 流程基本一致
  • Full GC 触发条件
    • mixed GC 赶不上内存分配的速度,只能通过 full GC 来释放内存,这种情况解决方案后面再说
    • metaSpace 不足,对于大量使用反射,动态代理的类,由于动态代理的每个类都会生成一个新的类,同时class信息会存放在元空间,因此如果元空间不足, G1 会靠 full GC 来扩容元空间,这种情况解决方案就是扩大初始元空间大小
    • humongous 分配失败, G1 分配大对象时,会靠 concurrent marking 或 full GC 回收空间,因此如果大对象分配失败,则可能会引发 full GC
G1调优参数
  • 开启参数:-XX: UseG1GC
  • 最大GC暂停时间: -XX:MaxGCPauseMillis
  • 不要设置年轻代大小:不要使用-Xmn,因为 G1 是通过需要扩展或缩小年轻代大小,如果设置了年轻代大小,则会导致 G1 无法使用暂停时间目标

5 Safe Point 和 Safe Region

  • jvm 准备进行 GC 阶段,并不是随时都能开始的,需要用户线程进入一个安全的状态,才能开始 GC 操作。这个状态 被称为 safe point,在代码上特定的位置点有下面几种
    • 方法返回之前
    • 调用某个方法之后
    • 抛出异常位置
    • 循环的末尾
  • 用户线程执行到安全点时,会轮询 GC 中断标志,一旦出现则在安全点主动挂起线程
  • safe point 解决了用户线程停顿,让 jvm 进入GC。但如果用户线程本身就处于 sleep 和 wait 状态呢,线程不执行,也达到了不了 safe point 位置。Safe Region 可以解决类似问题,Safe Region 是指在一段代码片段中,引用关系不会发生变化。在这个区域内的任意地方开始 GC 都是安全的

OopMap(Ordinary Object Pointer,普通对象指针)

  • 如何确定 GC ROOT 的对象呢,检查完所有执行上下文和全局的引用位置?实际上 jvm 使用了 OopMap 记录栈上本地变量到堆上对象的引用关系,避免从全局性引用和执行上下文中逐个查找 GC ROOT,加快枚举根节点的速度,帮助HotSpot实现准确式GC
  • JIT编译过后的方法也会在一些特定的位置记录下OopMap。特定的位置如下
    • 循环的末尾
    • 方法临返回前 / 调用方法的call指令后
    • 可能抛异常的位置

6 什么是 TLAB 和 PLAB ?

TLAB

堆内存是所有线程共享的,jvm 在并发的环境进行内存分配存在同步竞争,为了加快对象的分配创建,jvm 为每个线程分配了一个私有缓存区域(在Eden空间内),这就是 Thread Local Allocation Buffer。使用TLAB可以避免一系列的非线程安全问题,同时还能够提升内存分配的吞吐量。如果私有 TLAB 使用完,则使用全局的

PLAB

PLAB 即 Promotion Local Allocation Buffers,用在年轻代对象晋升到老年代时。在多线程并行执行 YGC 时,可能有很多对象需要晋升到老年代,为了加快内存分配,于是有了 PLAB

7 CMS、G1 新生代的 GC 如何避免全堆扫描?

常见的 GC 利用了记忆集,记录分代 GC中 老年代对象指向新年代对象的引用关系,以此避免扫描老年代对象区域

  • CMS 使用 CardTable(卡表)的数据结构来标记老年代的某一块内存区域中的对象是否持有新生代对象的引用。point out 结构
    • Card Table: 卡表的数量取决于老年代的大小和每张卡对应的内存大小,每张卡在卡表中对应一个比特位,当老年代中的某个对象持有了新生代对象的引用时,JVM就把这个对象对应的Card所在的位置标记为dirty(bit位设置为1),这样在Minor GC时就不用扫描整个老年代,而是扫描Card为Dirty对应的那些内存区域
  • G1 为了避免 young GC 时,扫描整个老年代,G1 引入了 Card Table 和 Remember Set 的概念
    • RSet:全称 Remembered Sets, 用来记录外部指向本 Region 的所有引用,每个 Region 维护一个 RSet。point in 结构,双向指向
    • 下图展示的是 RSet 与 Card Table 的关系。每个 Region 被分成了多个 Card Table,其中绿色部分的 Card 表示该 Card 中有对象引用了其他 Card 中的对象,这种引用关系用蓝色实线表示。RSet 其实是一个 HashTable,Key 是 Region 的起始地址,Value 是 Card Table (字节数组),字节数组下标表示 Card 的空间地址,当该地址空间被引用的时候会被标记为 dirty_card

image.png

为什么 G1 不维护年轻代到老年代的记忆集?

  • G1 分 young GC 和 mixed GC,full GC。young gc 会选所有年轻代的区域进行回收;midex gc 会选所有年轻代的区域和一些收集收益高的老年代区域进行回收,而full GC 则是全堆回收。三种 GC,年轻代的区域都在回收范围内,所以不需要额外记录年轻代到老年代的跨代引用

8 CMS、G1 为了防止并发时的漏标分别用了什么手段?

三色标志法

  • 黑色:从GCRoots开始,已扫描过它全部引用的对象,标记为黑色
  • 灰色:扫描过对象本身,还没完全扫描过它全部引用的对象,标记为灰色
  • 白色:还没扫描过的对象,标记为白色
  • 并发执行漏标的两个充分必要条件
    • 赋值器插入了一条或多条从黑色对象到白色对象的新引用
    • 赋值器删除了全部从灰色对象到该白色对象的直接或间接引用

漏标 CMS 解决方案-增量更新(Incremental Update)

增量更新要破坏的是第一个条件,当黑色对象插入新的指向白色对象的引用时,用写屏障将新插入的引用记录下来,等并发扫描结束之后,再以这些记录过的黑色对象为根,重新扫描一次

漏标 G1 解决方案-原始快照(Snapshot At TheBeginning,SATB)

SATB 要破坏的是第二个条件,当灰色对象要删除指向白色对象的引用时,用写屏障将这个要删除的引用记录下来,在并发扫描结束之后,再将这些记录过的引用关系中的灰色对象为根,重新扫描一次

9 什么是 logging write barrier

  • write barrier 的操作逻辑是复杂的,是为了减少对应用 mutator 线程性能的影响,G1将一部分原本要在 write barrier 里做的逻辑分离出来交给异步线程并发执行:mutator 线程在写屏障里把分离的逻辑信息以 log 形式放到一个队列里,然异步线程再从队列里取出 log 批量执行
  • 以SATB write barrier为例,每个Java线程有一个独立的、定长的 SATBMarkQueue,mutator在 barrier 里把old_value压入该队列中。一个队列满了之后,它就会被加到全局的 SATB 队列集合 SATBMarkQueueSet 里等待处理。后台异步线程会扫描,如果超过一定阈值就会处理,开始处理

10 CMS 常见问题

最终标记阶段停顿时间过长问题

  • CMS的GC停顿时间约80%都在最终标记阶段(Final Remark),若该阶段停顿时间过长,常见原因是新生代对老年代的无效引用,在 并发可取消预清理 阶段中,执行阈值时间内未完成循环,来不及触发 young GC,清理这些无效引用
  • 通过添加参数:-XX: CMSScavengeBeforeRemark。在执行 Final Remark 操作之前先触发 young GC,从而减少新生代对老年代的无效引用,降低最终标记阶段的停顿

Promotion Failure

  • 该问题是在进行 young gc 时,Survivor Space放不下,对象只能放入老年代,而此时老年代也放不下,则会产生 Promotion Failure

concurrent mode failure

CMS 垃圾收集器特有的错误,CMS 的垃圾清理和引用线程是并行进行的,如果在并行清理的过程中老年代的空间不足以容纳应用产生的垃圾(也就是老年代正在清理,从年轻代晋升了新的对象,或者直接分配大对象年轻代放不下导致直接在老年代生成,这时候老年代也放不下),则会抛出 concurrent mode failure

  • 垃圾产生速度超过清理速度
    • 晋升阈值过小,设置 -XX:MaxTenuringThreshold=n
    • 降低触发CMS GC的阈值,开启根据阈值触发CMS GC开关:-XX: UseCMSInitiatingOccupancyOnly,和参数 -XX:CMSInitiatingOccupancyFraction=n 的值(默认为 92%),让CMS GC尽早执行,以保证有足够的空间
    • 增加CMS线程数,即参数-XX:ConcGCThreads
    • Survivor 空间过小,加大;Eden 区过小,加大。整体内存下导致晋升速率提高,老年区空间不足
    • 存在大对象分配
  • CMS GC 发生 concurrent mode failure 时的 full GC 为什么是单线程的?
    • CMS GC 不兼容并发回收 young 区

内存碎片问题

  • 开启空间碎片整理,并将空间碎片整理周期设置在合理范围。开启空间碎片整理 -XX: UseCMSCompactAtFullCollection,让CMS在进行一定次数 Full GC 进行碎片压缩 -XX:CMSFullGCsBeforeCompaction=n

11 GC 事件和日志分析

GC 指标

  • 延迟、GC 暂停时间(stop the world)
  • 吞吐量(应用服务在非 GC 功能上运行的耗时百分比)
  • GC 频率
  • CPU 耗时

GC事件分类

  • Young GC, 新生代内存的垃圾收集事件称为Young GC(又称Minor GC),当JVM无法为新对象分配在新生代内存空间时总会触发 Young GC
  • Old GC,只清理老年代空间的GC事件,只有CMS的并发收集是这个模式
  • Mixed GC,清理整个新生代以及部分老年代的GC,只有G1有这个模式
  • Full GC,清理整个堆的GC事件,包括新生代、老年代、元空间等

GC日志分析

  • 开启 GC 日志分析 -verbose:gc -XX: PrintGCDetails -XX: PrintGCDateStamps -XX: PrintGCTimeStamps

需要对 GC 进行完整的监控,监控各年代占用大小、YGC 触发频率、Full GC 触发频率,对象分配速率等等

GCLocker Initiated GC

  • 如果线程执行在 JNI 临界区时,刚好需要进行 GC,此时 GC Locker 将会阻止 GC 的发生,同时阻止其他线程进入 JNI 临界区,直到最后一个线程退出临界区时触发一次 GC

动态扩容引起的空间震荡

  • 服务刚刚启动时 GC 次数较多,最大空间剩余很多但是依然发生 GC。在 JVM 的参数中 -Xms-Xmx 设置的不一致,在初始化时只会初始 -Xms 大小的空间存储信息,每当空间不够用时再向操作系统申请,这样的话必然要进行一次 GC
  • 尽量将成对出现的空间大小配置参数设置成固定的,如 -Xms-Xmx-XX:MaxNewSize-XX:NewSize-XX:MetaSpaceSize-XX:MaxMetaSpaceSize

12 JVM 常用参数汇总

  • 通用配置参数

参数

说明

实例

-Xms

初始堆大小,默认物理内存的1/64

-Xms512M

-Xmx

最大堆大小,默认物理内存的1/4

-Xms2G

-Xmn

新生代内存大小,官方推荐为整个堆的3/8

-Xmn512M

-XX:NewRatio=n

设置新生代和年老代的比值。如: 3,表示年轻代与年老代比值为1:3,年轻代占整个年轻代年老代和的1/4

-XX:NewRatio=3

-XX:SurvivorRatio=n

年轻代中Eden区与两个Survivor区的比值。注意Survivor区有两个。如: 8,表示Eden:Survivor=8:1:1,一个Survivor区占整个年轻代的1/10

-XX:SurvivorRatio=8

-Xss

线程堆栈大小,jdk1.5及之后默认1M,之前默认256k

-Xss512k

-XX:PermSize=n

永久代初始值,默认为物理内存的1/64

-XX:PermSize=128M

-XX:MaxPermSize=n

永久代最大值,默认为物理内存的1/4

-XX:MaxPermSize=256M

-verbose:class

在控制台打印类加载信息

-verbose:gc

在控制台打印垃圾回收日志

-XX: PrintGC

打印GC日志,内容简单

-XX: PrintGCDetails

打印GC日志,内容详细

-XX: PrintGCDateStamps

在GC日志中添加时间戳

-Xloggc:filename

指定gc日志路径

-Xloggc:/data/jvm/gc.log

-XX: DisableExplicitGC

关闭System.gc()

-XX: UseBiasedLocking

自旋锁机制的性能改善

-XX:PretenureSizeThreshold

对象超过多大是直接在旧生代分配,默认值 0 ,单位字节

-XX:TLABWasteTargetPercent

TLAB 占eden区的百分比 默认值 1%

-XX: CollectGen0First

fullGC 时是否先 youngGC 默认值 false

-XX: PrintHeapAtGC

打印 GC 前后的详细堆栈信息

-XX:ParallelGCThreads=n

设置并行收集器时使用的CPU数。此值最好配置与处理器数目相等,同样适用于CMS

-XX:ParallelGCThreads=4

  • 年轻代

参数

说明

-XX: UseSerialGC

年轻代设置串行收集器Serial

-XX: UseParallelGC

年轻代设置并行收集器Parallel Scavenge

-XX:UseParNewGC

启用ParNew收集器

-XX:MaxTenuringThreshold

几次 youngGC 后会被分到老年代,默认是15次

-XX:MaxGCPauseMillis=n

年轻代垃圾回收的最长时间,如果无法满足此时间,JVM会自动调整年轻代大小,以满足此值

  • 老年代

参数

说明

-XX: UseParallelOldGC

设置老年代为并行收集器ParallelOld收集器

-XX: UseConcMarkSweepGC

设置老年代并发收集器CMS,且默认使用parNew作为新生代的垃圾回收

-XX UseCMSCompactAtFullCollection

fullGC过后,开启对老年代的内存压缩,我们知道CMS使用的标记清除算法,会产生内存碎片,所以需要内存压缩

-XX:CMSFullGCsBeforeCompaction=n

经过几次FullGC后进行内存压缩,默认是 0

-XX:ParallelCMSThreads=n

CMS 过程并发线程数

-XX: CMSParallelInitialMarkEnabled

为了减少 CMS 初始标志暂停的时间,开启并行标志

-XX: CMSParallelRemarkEnabled

为了减少 CMS 第二次暂停的时间,开启并行remark

-XX: CMSScavengeBeforeRemark

如果 CMS remark 暂停时间过长的话,可以开启该选项,强制remark之前开始一次minor gc,减少remark的暂停时间,但是在remark之后也将立即开始又一次minor gc

  • G1 特有参数

参数

说明

-XX: UseG1GC

使用 G1 (Garbage First) 垃圾收集器

-XX:InitiatingHeapOccupancyPercent

老年代占用空间达到整堆内存阈值(默认45%),则执行新生代和老年代的混合收集(MixedGC),比如我们之前说的堆默认有2048个region,如果有接近1000个region都是老年代的region,则可能 就要触发MixedGC了

-XX:MaxGCPauseMillis

目标暂停时间(默认200ms) 也就是垃圾回收的时候允许停顿的时间

-XX:G1MixedGCCountTarget

在一次回收过程中指定做几次筛选回收(默认8次),在最后一个筛选回收阶段可以回收一 会,然后暂停回收,恢复系统运行,一会再开始回收,这样可以让系统不至于单次停顿时间过长

-XX:G1HeapWastePercent

(默认5%) 在混合回收时,一旦空闲出来的Region数量达到了堆内存的5%,此时就会立 即停止混合回收,意味着本次混合回收就结束了

-XX:ConcGCThreads=n

并发垃圾收集器使用的线程数量. 默认值随JVM运行的平台不同而不同

欢迎指正文中错误

参考文章

  • JVM(四)分代垃圾回收机制和垃圾回收算法 [1]
  • JVM(五) GC 底层细节[2]
  • ZGC设计与实现[3]
  • ?炸了!一口气问了我18个JVM问题
  • concurrent mode failure[4]
  • 老大难的Java GC原理和调优,看这篇就够了[5]
  • ?Java中9种常见的CMS GC问题分析与解决
  • 从实际案例聊聊Java应用的GC优化[6]
  • JDK 11 ZGC简介[7]
  • JVM - 解读GC中的 Safe Point & Safe Region[8]

参考资料

[1]

JVM(四)分代垃圾回收机制和垃圾回收算法 : https://www.cnblogs.com/mouren/p/14361439.html

[2]

JVM(五) GC 底层细节: https://www.cnblogs.com/mouren/p/14387192.html

[3]

ZGC设计与实现: 书籍

[4]

concurrent mode failure: https://blog.csdn.net/muzhixi/article/details/105274542

[5]

老大难的Java GC原理和调优,看这篇就够了: https://www.cnblogs.com/caison/p/11641791.html

[6]

从实际案例聊聊Java应用的GC优化: https://tech.meituan.com/2017/12/29/jvm-optimize.html

[7]

JDK 11 ZGC简介: https://www.cnblogs.com/ctgulong/p/9742434.html

[8]

JVM - 解读GC中的 Safe Point & Safe Region: https://blog.csdn.net/yangshangwei/article/details/107119177

0 人点赞