0

0

监控 JVM Full GC 次数的正确实践:避免 CMS 垃圾收集器计数误判

霞舞

霞舞

发布时间:2025-12-27 16:29:11

|

530人浏览过

|

来源于php中文网

原创

监控 JVM Full GC 次数的正确实践:避免 CMS 垃圾收集器计数误判

本文详解为何 `managementfactory.getgarbagecollectormxbeans()` 在 cms 垃圾收集器下会将一次 `jmap -histo:live` 触发的 full gc 误报为多次,揭示 cms 的 foreground 模式中 initial mark、remark 和 sweep 阶段均独立增加 `collectioncount` 的机制,并提供健壮、跨 gc 算法的监控方案。

在使用 ManagementFactory.getGarbageCollectorMXBeans() 监控 Full GC 次数时,你观察到:执行一次 jmap -histo:live(触发 Heap Inspection Initiated GC)后,日志显示仅发生一次 CMS Full GC,但程序却报告 add FullGC count:2 —— 这并非代码逻辑错误,而是 CMS 垃圾收集器在 foreground 模式下的固有行为

? 根本原因:CMS 的 “伪 Full GC” 阶段拆分

当 jmap -histo:live 触发 GC 时,JVM 会强制进入 CMS 的 foreground 模式(即暂停所有应用线程的同步回收),该模式并非单次原子操作,而是由多个可独立计数的子阶段组成:

  • Initial Mark(初始标记)→ collectionCount++
  • Remark(重新标记)→ collectionCount++
  • Sweep(清除)→ collectionCount++

查看你的 GC 日志可验证这一点:

# 第一次 jmap:包含 Remark + Sweep(共 2 次计数增量)
[Full GC (Heap Inspection Initiated GC) ... [CMS: ...] ... [weak refs processing] ... [class unloading] ... [scrub symbol/string table] ... ]
→ 实际触发了 Remark(含 class unloading/scrub)和 Sweep 两个独立阶段

# 第二次 jmap:仅 Sweep(1 次增量)
[Full GC (Heap Inspection Initiated GC) ... [CMS: 83931K->85173K(...) ...]

因此,bean.getCollectionCount() 返回的是 CMS 子阶段总执行次数,而非用户语义上的“一次完整的 Full GC”。这就是你看到 sum of fullgc:1, add FullGC count:2 的真实原因。

Facet
Facet

Facet.ai是一款AI图像生成和编辑工具,具备实时图像生成和编辑功能

下载

✅ 正确做法:区分 GC 类型 + 聚合统计

不应依赖单一 GarbageCollectorMXBean 的 getCollectionCount() 判断 Full GC,而应:

  1. 识别真正的 Full GC 收集器:CMS 的 ConcurrentMarkSweep 是并发收集器,其 getCollectionCount() 包含并发与 foreground 混合计数;真正执行 Full GC 的是 ParNew(年轻代)+ CMS(老年代)组合,但更可靠的方式是监听 GarbageCollectionNotification。

  2. 使用 JMX 通知机制(推荐)
    它能精确捕获每次 GC 的类型(endOfMajorGC / endOfMinorGC)、持续时间与内存变化,且不受 GC 算法内部阶段拆分影响:

import com.sun.management.GarbageCollectionNotificationInfo;
import javax.management.Notification;
import javax.management.NotificationListener;
import javax.management.ObjectName;
import java.lang.management.ManagementFactory;
import java.util.List;

public class GCMonitor {
    private static long fullGCCount = 0;

    public static void startMonitoring() throws Exception {
        List beans = ManagementFactory.getGarbageCollectorMXBeans();
        for (GarbageCollectorMXBean bean : beans) {
            ObjectName objName = ManagementFactory.newPlatformMXBeanObjectName(
                ManagementFactory.GARBAGE_COLLECTOR_MXBEAN_DOMAIN_TYPE + "," +
                "name=" + ObjectName.quote(bean.getName())
            );
            ManagementFactory.getPlatformMBeanServer().addNotificationListener(
                objName,
                (notification, handback) -> {
                    if (notification.getType().equals(GarbageCollectionNotificationInfo.GARBAGE_COLLECTION_NOTIFICATION)) {
                        GarbageCollectionNotificationInfo info =
                            GarbageCollectionNotificationInfo.from((CompositeData) notification.getUserData());
                        // 关键判断:Major GC(即 Full GC)通常作用于老年代(如 CMS Old Gen、G1 Old Generation)
                        if (info.getGcCause().contains("System.gc") ||
                            info.getGcCause().contains("Heap Inspection") ||
                            info.getGcName().toLowerCase().contains("old")) {
                            fullGCCount++;
                            System.out.printf("✅ Detected Full GC #%d: %s (%s) → %dms%n",
                                fullGCCount, info.getGcName(), info.getGcCause(), info.getGcInfo().getDuration());
                        }
                    }
                },
                null, null
            );
        }
    }
}
  1. 兼容性提醒(重要!)
    • CMS 已在 JDK 9 中被标记为 deprecated,JDK 14 起彻底移除。现代应用应迁移到 G1 或 ZGC。
    • G1/ZGC 的 getCollectionCount() 行为更符合直觉:一次 jmap -histo:live 仅触发 1 次 collectionCount 增量(对应一次 Mixed GC 或 Full GC)。
    • 若必须支持旧版 CMS,请始终以 GarbageCollectionNotification 为准,放弃轮询 getCollectionCount()。

? 总结

  • ❌ 错误认知:getCollectionCount() = Full GC 次数(尤其在 CMS foreground 模式下不成立)
  • ✅ 正确认知:它是 GC 子阶段执行总次数,CMS 下一次 jmap 可能触发多次计数
  • ✅ 最佳实践:使用 GarbageCollectionNotification 监听,结合 gcCause(如 "Heap Inspection")和 gcName(如 "CMS Old Gen")精准识别 Full GC
  • ⚠️ 长期建议:升级至 G1/ZGC,简化监控逻辑并获得更好性能与可观测性

通过以上改进,你的 Full GC 监控将真正反映 JVM 行为本质,而非 GC 算法的实现细节陷阱。

相关专题

更多
counta和count的区别
counta和count的区别

Count函数用于计算指定范围内数字的个数,而CountA函数用于计算指定范围内非空单元格的个数。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

198

2023.11.20

线程和进程的区别
线程和进程的区别

线程和进程的区别:线程是进程的一部分,用于实现并发和并行操作,而线程共享进程的资源,通信更方便快捷,切换开销较小。本专题为大家提供线程和进程区别相关的各种文章、以及下载和课程。

482

2023.08.10

页面置换算法
页面置换算法

页面置换算法是操作系统中用来决定在内存中哪些页面应该被换出以便为新的页面提供空间的算法。本专题为大家提供页面置换算法的相关文章,大家可以免费体验。

404

2023.08.14

Golang 性能分析与pprof调优实战
Golang 性能分析与pprof调优实战

本专题系统讲解 Golang 应用的性能分析与调优方法,重点覆盖 pprof 的使用方式,包括 CPU、内存、阻塞与 goroutine 分析,火焰图解读,常见性能瓶颈定位思路,以及在真实项目中进行针对性优化的实践技巧。通过案例讲解,帮助开发者掌握 用数据驱动的方式持续提升 Go 程序性能与稳定性。

9

2026.01.22

html编辑相关教程合集
html编辑相关教程合集

本专题整合了html编辑相关教程合集,阅读专题下面的文章了解更多详细内容。

56

2026.01.21

三角洲入口地址合集
三角洲入口地址合集

本专题整合了三角洲入口地址合集,阅读专题下面的文章了解更多详细内容。

28

2026.01.21

AO3中文版入口地址大全
AO3中文版入口地址大全

本专题整合了AO3中文版入口地址大全,阅读专题下面的的文章了解更多详细内容。

385

2026.01.21

妖精漫画入口地址合集
妖精漫画入口地址合集

本专题整合了妖精漫画入口地址合集,阅读专题下面的文章了解更多详细内容。

116

2026.01.21

java版本选择建议
java版本选择建议

本专题整合了java版本相关合集,阅读专题下面的文章了解更多详细内容。

3

2026.01.21

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Kotlin 教程
Kotlin 教程

共23课时 | 2.7万人学习

C# 教程
C# 教程

共94课时 | 7.3万人学习

Java 教程
Java 教程

共578课时 | 49.1万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号