如何从JVM中检测长gc?

如何检测超过某个已配置应用程序超时的GC( 编辑或任何停顿),以便我可以记录警告(或动态延长超时)?

编辑我不是要求监视等替代方法或变通办法。 我正在写一个库,我无法控制环境或设置。 虽然我将清楚地记录库的用户必须设置适当的超时,但我仍然希望人们忽略其他人在几年后更改jvm堆设置并忘记增加超时。 如果我可以在库中记录可能超过配置的超时的暂停,则支持会更简单。 它不一定是完美的检测“足够好”会减少库用户浪费时间而不设置合理的超时。

编辑并清楚即使有一个大的GC,库也能正常工作,但是有充分的理由选择一个好的超时来检测崩溃,以便库试图连接到备用对等体。

您可以使用管理bean 通知并订阅GARBAGE_COLLECTION_NOTIFICATION事件,这些事件又为GcInfo对象提供您想要的统计信息。

javax.management包javadocs具有高级概述如何使用这些服务。

首先,我要说的不适用于实时系统,所以让我们立即解决这个问题:如果你想构建一个具有严格约束的实时系统,那么Java可能不是那样的去。

现在,如果您没有构建实时系统,那么我建议不要过分关注GC可能会降低程序速度,延迟程序,冻结程序等的可能性。

现代垃圾收集语言(如java)中的垃圾收集非常简化,它在一个单独的线程上工作,尽可能在尽可能小的块中完成尽可能多的工作,并且您有可能因为垃圾而目睹冻结 – 收集非常苗条。

另一方面,在任何现代非实时系统中,可能会发生许多不同的事情,可能会减慢或暂时冻结您的程序(最重要的是, 分页 ),GC的贡献可以忽略不计,最有可能在噪音中迷失了。

修订

在对您的问题进行修改之后,您的需求似乎是检测您的运行时环境是否在计算资源(CPU)的分配中遇到高度不规则。这是一个比检测GC导致的延迟更常见的问题。 (GC只是这种延迟的一个可能来源,甚至在第一批嫌疑人中也是如此。)因此,为了解决一般目的,请考虑以下方法:

创建一个单独的线程,在循环中执行以下操作:

 1. record the current time. 2. sleep for a specific number of milliseconds. (Say, 50.) 3. record the current time again. 

在平稳运行的系统中,第一次和第二次之间的差异应该非常接近睡眠量。 如果您的系统出现exception,那么这个时间会有很大差异。 在相当长的一段时间内持续存在这种疯狂的变化意味着你的系统运行不顺畅。

如果你真的很想抓住GC冻结你的程序,你可以确保在上面的步骤2和3之间执行一些内存分配。 据推测,如果GC冻结了你的java VM,那么在这个内存分配得到尊重之前需要一些时间。 相信我,它不会发生,但如果这会让你安心,那么继续前进并测试它。

您还可以通过将其与程序的主逻辑同步来进一步详细说明此技术,以确保主逻辑处于活动状态并正在运行。

基于上面@ the8472给出的指针,我制作了一个更完整的示例,用于从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 memUsages) { long sum = 0; for (MemoryUsage memoryUsage : memUsages.values()) { sum += memoryUsage.getUsed(); } return sum / (1024 * 1024); } } 

关于处理任何超时,您可以在未来的http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/Future.html中运行您的任务,然后创建一个不同的线程来监视Future运行,检查是否已完成,如果未通过您指定的超时完成,则在日志或其他内容中发出警告。

 ExecutorService svc = Executors.newFixedThreadPool( 1 ) ; Future submit = svc.submit(r); 

//睡觉超时

 if(!submit.isDone()) { log.warn("action is not done"); } 

你可以使用带有超时或没有超时的submit.get返回任务的答案。