Java G1:监视生产中的内存泄漏

时间:2016-03-10 21:45:07

标签: java garbage-collection g1gc

多年来,我们一直使用+UseParallelOldGC运行具有适度堆大小的Java服务。现在,我们开始使用更大的堆和G1收集器推出新服务。这很顺利。

对于使用+UseParallelOldGC的服务,我们通过查看收集后的旧代大小并在阈值上发出警报来监控内存泄漏。这种方法效果很好,事实上两周前就已经保存了我们的培根。

具体而言,对于+UseParallelOldGC,我们会执行以下操作:

  • ManagementFactory.getMemoryPoolMXBeans()
  • 搜索名称以MemoryPoolMXBean
  • 结尾的"Old Gen"结果
  • getCollectionUsage().getUsed()(如果有)与getMax()
  • 进行比较

不幸的是,似乎G1不再具有getCollectionUsage()的概念。

但是,从根本上说,我们希望在混合循环中选择的最后一个混合集合之后监视G1堆大小,或类似的东西。

例如,在VM之外我会对awk脚本感到满意,该脚本仅发现最后一个'(mixed)'后面跟着'(young)'并查看最终堆大小是什么(例如,{{ 1}})

有没有办法在Java VM中执行此操作?

1 个答案:

答案 0 :(得分: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中的单个混合集合通常仅影响几个旧的gen区域 - 许多足以释放足够的内存但不会太多以保持GC暂停低。因此,在G1中的混合收集之后,您无法确定所有垃圾都已消失。因此,您需要找到更复杂的策略来检测内存泄漏(可能基于集合频率,从多个集合中收集统计信息等)。