我试图捕获Java应用程序中出现的完整GC。到目前为止,我有两种方法,都有缺陷:
当我收到GC通知时可以查询GC MXBeans,然后检查GC是否已执行,可以采用混合方法。这样做的缺点是No GC原因可能仍会在主要GC"结束时触发。并使逻辑变得更加复杂。
我真正关注的是由于GC操作导致应用程序暂停的总时间。我认为捕获一个完整的GC将足以表明应用程序已停止一段时间,因此如果我们检测到频繁的GC,我们知道有大量使用或应用程序将很快耗尽内存。有没有更好的方法来检测GC时间以及是否有完整的GC?
编辑:要清楚,我想在给定的JVM中捕获此信息。这个JVM由一个单独的团队控制,我们没有必要控制他们将设置的JVM参数,我们只能提出建议。这个想法类似于飞行记录器,而是向管理员实时提供信息。
答案 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;
}
}