以编程方式捕获 Full GC 计数

Programmatically capture Full GC count

我正在尝试捕获在我们的 Java 应用程序中发生的完整 GC。到目前为止,我有两种方法,都有缺陷:

  1. 每隔 x 秒轮询一次 GarbageCollectorMXBean 对象,然后尝试提取自上次轮询以来的 GC 时间和 GC 计数,并尝试检测 GC 发生的时间。不幸的是,我们不知道它是否是 Full GC。
  2. 使用javax.management.NotificationListener,订阅GarbageCollectorMXBean 通知。理论上,当 GC 发生时,应用程序将收到通知,其中包含文本 "end of major GC" 和 "end of minor GC" 以及原因。这种方法的缺陷是持续时间似乎非常不正确(有时显示 898 秒,而 GC 日志将显示 .2 秒)并且有一个神秘的案例 "No GC" 似乎表明实际上没有执行 GC(因为 gc 日志中没有条目)。

一种混合方法是可能的,当我收到 GC 通知时可以查询 GC MXBeans,然后检查是否已执行 GC。这样做的缺点是,No GC 原因可能仍会在 "major GC" 结束时触发并导致逻辑更加复杂。

我真正想要的是应用程序因 GC 操作而暂停的总时间。我认为捕获一个完整的 GC 足以表明应用程序已停止一段时间,因此如果我们检测到频繁的 GC,我们知道有大量使用或应用程序将很快 运行 内存不足。有没有更好的方案来检测GC时间和是否有full GC?

编辑:明确地说,我想在给定的 JVM 中捕获此信息。这个 JVM 由一个单独的团队控制,我们没有必要控制他们将设置的 JVM 参数,我们只能提出建议。这个想法类似于飞行记录器,而是实时向管理员提供信息。

如果你使用hotspot specific GC beans,那么你可以new/old区分,而不是concurrent/full。但这在大多数情况下就足够了。

在那种情况下,我会进行轮询,并使用来自相关世代的 #LastGCInfo 中的 before/after 信息:

例如启用ParallelOld时java.lang:type=GarbageCollector,name=PS Scavenge是年轻代,java.lang:type=GarbageCollector,name=PS MarkSweep是老年代。

使用 CMS,您可以在 java.lang:type=GarbageCollector,name=ConcurrentMarkSweep 中看到并发集合和完整集合(但希望永远不会有完整集合:)

这是特定于 GC 和 VM 的,因此它需要一些启发式方法来支持您关心的所有平台。

我认为只有使用 Flight Recorder 事件才能进行更细粒度的监控,但我没有这方面的经验。

如果您可以访问有问题的 JVM 的文件系统,您可以告诉 JVM 记录有关垃圾回收的信息。

对于 Oracle Java 存在 8 个标志(来自 http://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html#BABFAFAE


-XX:+PrintGC

允许在每次 GC 时打印消息。默认情况下,此选项处于禁用状态。

-XX:+PrintGCApplicationConcurrentTime

允许打印自上次暂停(例如 GC 暂停)以来经过的时间。默认情况下,此选项处于禁用状态。

-XX:+PrintGCApplicationStoppedTime

允许打印暂停(例如,GC 暂停)持续了多长时间。默认情况下,此选项处于禁用状态。

-XX:+PrintGCDateStamps

允许在每次 GC 打印日期戳。默认情况下,此选项处于禁用状态。

-XX:+PrintGCDetails

允许在每次 GC 时打印详细消息。默认情况下,此选项处于禁用状态。

-XX:+PrintGCTaskTimeStamps

为每个单独的 GC 工作线程任务启用时间戳打印。默认情况下,此选项处于禁用状态。

-XX:+PrintGCTimeStamps

在每次 GC 时启用时间戳打印。默认情况下,此选项处于禁用状态。

您可以监听 GC 通知。请参阅下面的代码。

我在这里交叉发布这个答案,因为它看起来很相关。基于 pointers given by @the8472 in this question 我制作了一个更完整的示例,用于从 JVM 内部记录 GC(因此 detecting/counting 它)。我希望这会节省一些时间:)

package fi.pelam.gclogutil;
import java.lang.management.*;
import java.util.Map;
import javax.management.openmbean.CompositeData;
import javax.management.*;

import com.sun.management.GarbageCollectionNotificationInfo;
import com.sun.management.GcInfo;

public class GcLogUtil {
    static public void startLoggingGc() {
        // http://www.programcreek.com/java-api-examples/index.php?class=javax.management.MBeanServerConnection&method=addNotificationListener
        // https://docs.oracle.com/javase/8/docs/jre/api/management/extension/com/sun/management/GarbageCollectionNotificationInfo.html#GARBAGE_COLLECTION_NOTIFICATION
        for (GarbageCollectorMXBean gcMbean : ManagementFactory.getGarbageCollectorMXBeans()) {
            try {
                ManagementFactory.getPlatformMBeanServer().
                        addNotificationListener(gcMbean.getObjectName(), listener, null,null);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    static private NotificationListener listener = new NotificationListener() {
        @Override
        public void handleNotification(Notification notification, Object handback) {
            if (notification.getType().equals(GarbageCollectionNotificationInfo.GARBAGE_COLLECTION_NOTIFICATION)) {
                // https://docs.oracle.com/javase/8/docs/jre/api/management/extension/com/sun/management/GarbageCollectionNotificationInfo.html
                CompositeData cd = (CompositeData) notification.getUserData();
                GarbageCollectionNotificationInfo gcNotificationInfo = GarbageCollectionNotificationInfo.from(cd);
                GcInfo gcInfo = gcNotificationInfo.getGcInfo();
                System.out.println("GarbageCollection: "+
                        gcNotificationInfo.getGcAction() + " " +
                        gcNotificationInfo.getGcName() +
                        " duration: " + gcInfo.getDuration() + "ms" +
                        " used: " + sumUsedMb(gcInfo.getMemoryUsageBeforeGc()) + "MB" +
                        " -> " + sumUsedMb(gcInfo.getMemoryUsageAfterGc()) + "MB");
            }
        }
    };

    static private long sumUsedMb(Map<String, MemoryUsage> memUsages) {
        long sum = 0;
        for (MemoryUsage memoryUsage : memUsages.values()) {
            sum += memoryUsage.getUsed();
        }
        return sum / (1024 * 1024);
    }
}

通过GarbageCollectorMXBean的名称,我们可以推断出是哪一个负责执行full gc。

GarbageCollectorMXBean fullGcMXBean = null;
        List<GarbageCollectorMXBean> gcMxBeanList = ManagementFactory.getGarbageCollectorMXBeans();
        for(GarbageCollectorMXBean gcMxBean: gcMxBeanList){
            if(gcMxBean.getName().endsWith("MarkSweep")){
                fullGcMXBean = gcMxBean;
                break;
            }
        }