本文详解为何 `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 模式下的固有行为。
当 jmap -histo:live 触发 GC 时,JVM 会强制进入 CMS 的 foreground 模式(即暂停所有应用线程的同步回收),该模式并非单次原子操作,而是由多个可独立计数的子阶段组成:
查看你的 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 的真实原因。
不应依赖单一 GarbageCollectorMXBean 的 getCollectionCount() 判断 Full GC,而应:
识别真正的 Full GC 收集器:CMS 的 ConcurrentMarkSweep 是并发收集器,其 getCollectionCount() 包含并发与 foreground 混合计数;真正执行 Full GC 的是 ParNew(年轻代)+ CMS(老年代)组合,但更可靠的方式是监听 GarbageCollectionNotification。
使用 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_TYP
E + "," +
"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
);
}
}
} 通过以上改进,你的 Full GC 监控将真正反映 JVM 行为本质,而非 GC 算法的实现细节陷阱。