簡體   English   中英

Java G1:監視生產中的內存泄漏

[英]Java G1: Monitoring for memory leaks in production

多年來,我們一直使用+UseParallelOldGC運行具有適度堆大小的Java服務。 現在,我們開始使用更大的堆和G1收集器推出新服務。 這很順利。

對於使用+UseParallelOldGC服務,我們通過查看收集后的舊生成大小並在閾值上發出警報來監視內存泄漏。 這很有效,事實上兩周前我們的培根就已經存了。

具體來說,對於+UseParallelOldGC ,我們執行以下操作:

  • ManagementFactory.getMemoryPoolMXBeans()
  • 搜索名稱以"Old Gen"結尾的MemoryPoolMXBean結果
  • 使用getMax()比較getCollectionUsage().getUsed() (如果可用getMax()

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

但是,從根本上說,我們希望在混合循環中選擇的最后一個混合集合之后監視G1堆大小,或類似的東西。

例如,在VM之外我會很高興一個awk腳本只是發現最后一個'(mixed)'后面跟着'(young)'並看看最終的堆大小是什么(例如, '1540.0M' 'Heap: 3694.5M(9216.0M)->1540.0M(9216.0M)'

在Java VM中有沒有辦法做到這一點?

是的,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中的混合收集之后,您無法確定所有垃圾都已消失。 因此,您需要找到更復雜的策略來檢測內存泄漏(可能基於集合頻率,從多個集合中收集統計信息等)

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM