Java G1: 生产环境下内存泄漏的监控

16
多年来,我们一直使用+UseParallelOldGC运行具有适度堆大小的Java服务。现在,我们开始推出一个使用更大堆和G1收集器的新服务。这个进展顺利。
对于使用+UseParallelOldGC的服务,我们通过查看老一代集合后的大小并在阈值上发出警报来监视内存泄漏。这非常有效,事实上,仅两周前就挽救了我们的困境。
具体而言,对于+UseParallelOldGC,我们执行以下操作:
  • ManagementFactory.getMemoryPoolMXBeans()
  • 查找名称以“Old Gen”结尾的MemoryPoolMXBean结果
  • getCollectionUsage().getUsed()(如果可用)与getMax()进行比较
不幸的是,似乎G1不再有getCollectionUsage()的概念。
但基本上,我们想监视它选择在混合循环中执行的最后一次混合集合后的G1堆大小,或类似的东西。
例如,在虚拟机之外,我可以使用一个awk脚本,仅查找最后一个后跟'(young)''(mixed)',并查看最终堆大小(例如,'1540.0M' 'Heap: 3694.5M(9216.0M)->1540.0M(9216.0M)'
有没有办法在Java VM内部做到这一点?

应提供类似信息:http://stackoverflow.com/a/32509813/1362755 - the8472
1个回答

2

是的,JVM提供了足够的工具来检索G1垃圾回收相关信息。例如,你可以使用类似于下面这个的类打印所有有关垃圾回收的细节(只需调用MemoryUtil.startGCMonitor()):

public class MemoryUtil {

    private static final Set<String> heapRegions;

    static {
        heapRegions = ManagementFactory.getMemoryPoolMXBeans().stream()
                .filter(b -> b.getType() == MemoryType.HEAP)
                .map(MemoryPoolMXBean::getName)
                .collect(Collectors.toSet());
    }

    private static NotificationListener gcHandler = (notification, handback) -> {
        if (notification.getType().equals(GarbageCollectionNotificationInfo.GARBAGE_COLLECTION_NOTIFICATION)) {
            GarbageCollectionNotificationInfo gcInfo = GarbageCollectionNotificationInfo.from((CompositeData) notification.getUserData());
            Map<String, MemoryUsage> memBefore = gcInfo.getGcInfo().getMemoryUsageBeforeGc();
            Map<String, MemoryUsage> memAfter = gcInfo.getGcInfo().getMemoryUsageAfterGc();
            StringBuilder sb = new StringBuilder(250);
            sb.append("[").append(gcInfo.getGcAction()).append(" / ").append(gcInfo.getGcCause())
                    .append(" / ").append(gcInfo.getGcName()).append(" / (");
            appendMemUsage(sb, memBefore);
            sb.append(") -> (");
            appendMemUsage(sb, memAfter);
            sb.append("), ").append(gcInfo.getGcInfo().getDuration()).append(" ms]");
            System.out.println(sb.toString());
        }
    };

    public static void startGCMonitor() {
        for(GarbageCollectorMXBean mBean: ManagementFactory.getGarbageCollectorMXBeans()) {
            ((NotificationEmitter) mBean).addNotificationListener(gcHandler, null, null);
        }
    }

    public static void stopGCMonitor() {
        for(GarbageCollectorMXBean mBean: ManagementFactory.getGarbageCollectorMXBeans()) {
            try {
                ((NotificationEmitter) mBean).removeNotificationListener(gcHandler);
            } catch(ListenerNotFoundException e) {
                // Do nothing
            }
        }
    }

    private static void appendMemUsage(StringBuilder sb, Map<String, MemoryUsage> memUsage) {
        memUsage.entrySet().forEach((entry) -> {
            if (heapRegions.contains(entry.getKey())) {
                sb.append(entry.getKey()).append(" used=").append(entry.getValue().getUsed() >> 10).append("K; ");
            }
        });
    }
}

在这段代码中,gcInfo.getGcAction()提供了足够的信息来区分小型集合和大型/混合集合。但是,使用您的方法(带有阈值)来处理G1时有一个重要的注意事项。G1中的单个混合集合通常只影响几个老年代区域——足以释放足够的内存,但不会太多以保持GC暂停时间低。因此,在G1中进行混合集合之后,您不能确定所有垃圾是否已经清除。因此,您需要找到更复杂的策略来检测内存泄漏(可能基于收集频率、从几个集合中收集统计信息等)。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接