以编程方式捕获完整GC计数

时间:2015-01-10 00:19:52

标签: java garbage-collection jvm

我试图捕获Java应用程序中出现的完整GC。到目前为止,我有两种方法,都有缺陷:

  1. 每隔x秒轮询一次GarbageCollectorMXBean对象,然后尝试拉动自上次轮询以来的GC时间和GC计数,并尝试检测GC发生的时间。不幸的是,我们不知道它是否是Full GC。
  2. 使用javax.management.NotificationListener,订阅GarbageCollectorMXBean通知。理论上,当GC发生时,将通知应用程序,文本"主要GC结束"和#34;小GC的结束"以及一个原因。这种方法的缺陷是持续时间似乎非常不正确(有时候GC日志会显示.2秒时显示898秒)并且有一个神秘的案例" No GC"这似乎表明没有实际执行GC(因为gc日志中没有条目)。
  3. 当我收到GC通知时可以查询GC MXBeans,然后检查GC是否已执行,可以采用混合方法。这样做的缺点是No GC原因可能仍会在主要GC"结束时触发。并使逻辑变得更加复杂。

    我真正关注的是由于GC操作导致应用程序暂停的总时间。我认为捕获一个完整的GC将足以表明应用程序已停止一段时间,因此如果我们检测到频繁的GC,我们知道有大量使用或应用程序将很快耗尽内存。有没有更好的方法来检测GC时间以及是否有完整的GC?

    编辑:要清楚,我想在给定的JVM中捕获此信息。这个JVM由一个单独的团队控制,我们没有必要控制他们将设置的JVM参数,我们只能提出建议。这个想法类似于飞行记录器,而是向管理员实时提供信息。

4 个答案:

答案 0 :(得分:2)

如果您使用特定于热点的GC bean,那么您可以区分新/旧,而不是并发/完全。但这在大多数情况下都足够了。

在这种情况下,我会进行民意调查,并使用相关代人#LastGCInfo中的前/后信息:

例如,当启用ParallelOld时,java.lang:type=GarbageCollector,name=PS Scavenge是年轻代,而java.lang:type=GarbageCollector,name=PS MarkSweep是旧代。

使用CMS,您可以在java.lang:type=GarbageCollector,name=ConcurrentMarkSweep中看到并发集合和完整集合(但希望永远不会有完整集合)。

这是GC和VM特定的,因此需要一些启发式方法来支持您关注的所有平台。

我认为只有飞行记录器事件才能实现更细粒度的监控,但我没有这方面的经验。

答案 1 :(得分:1)

如果您可以访问相关JVM的文件系统,您可以告诉JVM记录有关垃圾收集的信息。

对于Oracle Java 8,存在几个标记(来自http://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html#BABFAFAE


<强> -XX:+ PrintGC

允许在每个GC上打印消息。默认情况下,此选项已禁用。

<强> -XX:+ PrintGCApplicationConcurrentTime

启用打印自上次暂停后经过的时间(例如,GC暂停)。默认情况下,此选项已禁用。

<强> -XX:+ PrintGCApplicationStoppedTime

允许打印暂停(例如,GC暂停)持续多长时间。默认情况下,此选项已禁用。

<强> -XX:+ PrintGCDateStamps

允许在每个GC上打印日期戳。默认情况下,此选项已禁用。

<强> -XX:+ PrintGCDetails

允许在每个GC上打印详细消息。默认情况下,此选项已禁用。

<强> -XX:+ PrintGCTaskTimeStamps

允许为每个GC工作线程任务打印时间戳。默认情况下,此选项已禁用。

<强> -XX:+ PrintGCTimeStamps

允许在每个GC上打印时间戳。默认情况下,此选项已禁用。

答案 2 :(得分:1)

您可以收听GC通知。请参阅下面的代码。

我在这里交代这个答案,因为它看似相关。基于pointers given by @the8472 in this question,我制作了一个更完整的示例,用于从JVM内部记录GC(从而检测/计算它)。我希望这会节省一些时间:)

package fi.pelam.gclogutil;
import java.lang.management.*;
import java.util.Map;
import javax.management.openmbean.CompositeData;
import javax.management.*;

import com.sun.management.GarbageCollectionNotificationInfo;
import com.sun.management.GcInfo;

public class GcLogUtil {
    static public void startLoggingGc() {
        // http://www.programcreek.com/java-api-examples/index.php?class=javax.management.MBeanServerConnection&method=addNotificationListener
        // https://docs.oracle.com/javase/8/docs/jre/api/management/extension/com/sun/management/GarbageCollectionNotificationInfo.html#GARBAGE_COLLECTION_NOTIFICATION
        for (GarbageCollectorMXBean gcMbean : ManagementFactory.getGarbageCollectorMXBeans()) {
            try {
                ManagementFactory.getPlatformMBeanServer().
                        addNotificationListener(gcMbean.getObjectName(), listener, null,null);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    static private NotificationListener listener = new NotificationListener() {
        @Override
        public void handleNotification(Notification notification, Object handback) {
            if (notification.getType().equals(GarbageCollectionNotificationInfo.GARBAGE_COLLECTION_NOTIFICATION)) {
                // https://docs.oracle.com/javase/8/docs/jre/api/management/extension/com/sun/management/GarbageCollectionNotificationInfo.html
                CompositeData cd = (CompositeData) notification.getUserData();
                GarbageCollectionNotificationInfo gcNotificationInfo = GarbageCollectionNotificationInfo.from(cd);
                GcInfo gcInfo = gcNotificationInfo.getGcInfo();
                System.out.println("GarbageCollection: "+
                        gcNotificationInfo.getGcAction() + " " +
                        gcNotificationInfo.getGcName() +
                        " duration: " + gcInfo.getDuration() + "ms" +
                        " used: " + sumUsedMb(gcInfo.getMemoryUsageBeforeGc()) + "MB" +
                        " -> " + sumUsedMb(gcInfo.getMemoryUsageAfterGc()) + "MB");
            }
        }
    };

    static private long sumUsedMb(Map<String, MemoryUsage> memUsages) {
        long sum = 0;
        for (MemoryUsage memoryUsage : memUsages.values()) {
            sum += memoryUsage.getUsed();
        }
        return sum / (1024 * 1024);
    }
}

答案 3 :(得分:1)

通过GarbageCollectorMXBean的名称,我们可以推断出哪个人负责执行完整的gc。

GarbageCollectorMXBean fullGcMXBean = null;
        List<GarbageCollectorMXBean> gcMxBeanList = ManagementFactory.getGarbageCollectorMXBeans();
        for(GarbageCollectorMXBean gcMxBean: gcMxBeanList){
            if(gcMxBean.getName().endsWith("MarkSweep")){
                fullGcMXBean = gcMxBean;
                break;
            }
        }