JavaG1:在生产中监视内存泄漏

JavaG1:在生产中监视内存泄漏,java,garbage-collection,g1gc,Java,Garbage Collection,G1gc,多年来,我们一直使用+UseParallelOldGC以适度的堆大小运行Java服务。现在,我们开始使用更大的堆和G1收集器推出新服务。事情进展得很顺利 对于使用+UseParallelOldGC的服务,我们通过查看收集后的旧代大小并在阈值上发出警报来监控内存泄漏。这很有效,事实上就在两周前救了我们的命 具体而言,对于+UseParallelOldGC,我们执行以下操作: ManagementFactory.getMemoryPoolMXBeans() 搜索名称以“Old Gen”结尾的Mem

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

对于使用
+UseParallelOldGC
的服务,我们通过查看收集后的旧代大小并在阈值上发出警报来监控内存泄漏。这很有效,事实上就在两周前救了我们的命

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

  • ManagementFactory.getMemoryPoolMXBeans()
  • 搜索名称以“Old Gen”结尾的
    MemoryPoolMXBean
    结果
  • 比较
    getCollectionUsage().getUsed()
    (如果可用)与
    getMax()
不幸的是,G1似乎不再有
getCollectionUsage()
的概念

不过,从根本上说,我们希望在G1选择在混合循环中执行的最后一个混合集合或类似操作之后监视G1堆的大小

例如,在VM之外,我很高兴看到一个awk脚本只找到了最后一个
(混合)
,后面跟着一个
”(young)
,看看最终的堆大小是多少(例如,
'1540.0M'堆:3694.5M(9216.0M)->1540.0M(9216.0M)


在Java虚拟机中有什么方法可以做到这一点吗?

是的,JVM为您提供了足够的工具来检索G1的此类信息。例如,您可以使用类似这样的类来打印有关垃圾收集的所有详细信息(只需调用
MemoryUtil.startGCMonitor()
):

公共类MemoryUtil{
私有静态最终集;
静止的{
heapRegions=ManagementFactory.getMemoryPoolMXBeans().stream()
.filter(b->b.getType()==MemoryType.HEAP)
.map(MemoryPoolMXBean::getName)
.collect(收集器.toSet());
}
私有静态NotificationListener gcHandler=(通知,传回)->{
if(notification.getType().equals(GarbageCollectionNotificationInfo.GARBAGE\u COLLECTION\u notification)){
GarbageCollectionNotificationInfo gcInfo=GarbageCollectionNotificationInfo.from((CompositeData)notification.getUserData());
Map memBefore=gcInfo.getGcInfo().getMemoryUsageBeforeGc();
Map memAfter=gcInfo.getGcInfo().getMemoryUsageAfterGc();
StringBuilder sb=新StringBuilder(250);
sb.append(“[”).append(gcInfo.getGcAction()).append(“/”).append(gcInfo.getGcCause())
.append(“/”).append(gcInfo.getGcName()).append(“/”);
(某人、某人之前);
某人加上(“)->(”);
(某人、某人之后);
sb.append(“),”).append(gcInfo.getGcInfo().getDuration()).append(“ms]”;
System.out.println(sb.toString());
}
};
公共静态无效startGCMonitor(){
对于(GarbageCollectorMXBean mBean:ManagementFactory.getGarbageCollectorMXBeans()){
((NotificationEmitter)mBean).addNotificationListener(gcHandler,null,null);
}
}
公共静态void stopGCMonitor(){
对于(GarbageCollectorMXBean mBean:ManagementFactory.getGarbageCollectorMXBeans()){
试一试{
((NotificationEmitter)mBean);
}捕获(ListenerNotFounde异常){
//无所事事
}
}
}
私有静态无效appendMemUsage(StringBuilder sb、Map memUsage){
memUsage.entrySet().forEach((条目)->{
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中混合收集之后,您无法确保所有垃圾都已清除。因此,您需要找到更复杂的策略来检测内存泄漏(可能基于收集频率、从多个收集中收集统计信息等)。

这应该提供类似的信息:
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; ");
            }
        });
    }
}