用于确定JVM何时进入内存/ GC故障的有用度量


问题内容

我有一个scala数据处理应用程序,该应用程序有95%的时间可以处理内存中抛出的数据。剩下的5%(如果不加以检查)通常不会遇到
OutOfMemoryError
,而只是进入主要GC的周期,这会导致CPU峰值运行,阻止后台线程执行,并且即使完成也要花费10x-50x的时间有足够的内存。

我已经实现了可以将数据刷新到磁盘并将磁盘流视为内存迭代器的系统。它通常比内存慢一个数量级,但足以应付这5%的情况。我当前是由集合上下文的最大大小的启发式触发的,该上下文跟踪数据处理中涉及的各种集合的大小。这是可行的,但实际上只是一个预设的经验阈值。

我宁愿对JVM接近上述不良状态并在那时刷新到磁盘做出反应。我曾尝试观察记忆,但找不到正确的伊甸园,旧酒等组合来可靠地预测死亡螺旋。我还尝试过仅查看主要GC的频率,但是似乎也受制于从“太保守”到“太晚”的范围。

任何用于判断JVM健康状况和检测故障状态的资源将不胜感激。


问题答案:

一种可靠的方法是在GC事件上注册通知侦听器,并在所有Full
GC事件发生后检查内存运行状况。在发生完全GC事件后,直接使用的内存就是您实际的实时数据集。如果此时您的可用内存不足,则可能是时候开始向磁盘移动。

这样,您可以避免在不知道何时发生完整GC的情况下(例如在使用MEMORY_THRESHOLD_EXCEEDED通知类型时)尝试检查内存时经常发生的误报。

您可以使用以下代码注册通知侦听器并处理Full GC事件:

// ... standard imports ommitted
import com.sun.management.GarbageCollectionNotificationInfo;

public static void installGCMonitoring() {
    List<GarbageCollectorMXBean> gcBeans = ManagementFactory.getGarbageCollectorMXBeans();
    for (GarbageCollectorMXBean gcBean : gcBeans) {
        NotificationEmitter emitter = (NotificationEmitter) gcBean;
        NotificationListener listener = notificationListener();
        emitter.addNotificationListener(listener, null, null);
    }
}

private static NotificationListener notificationListener() {
    return new NotificationListener() {
        @Override
        public void handleNotification(Notification notification, Object handback) {
            if (notification.getType()
                    .equals(GarbageCollectionNotificationInfo.GARBAGE_COLLECTION_NOTIFICATION)) {
                GarbageCollectionNotificationInfo info = GarbageCollectionNotificationInfo
                        .from((CompositeData) notification.getUserData());
                String gctype = info.getGcAction();
                if (gctype.contains("major")) {
                    // We are only interested in full (major) GCs
                    Map<String, MemoryUsage> mem = info.getGcInfo().getMemoryUsageAfterGc();
                    for (Entry<String, MemoryUsage> entry : mem.entrySet()) {
                        String memoryPoolName = entry.getKey();
                        MemoryUsage memdetail = entry.getValue();
                        long memMax = memdetail.getMax();
                        long memUsed = memdetail.getUsed();
                        // Use the memMax/memUsed of the pool you are interested in (probably old gen)
                        // to determine memory health.
                    }
                }
            }
        }
    };
}

相信这篇文章是我们最初提出这个想法的。