diff -r b5ae6d3daf87 jmh-core/src/main/java/org/openjdk/jmh/profile/GCProfiler.java --- a/jmh-core/src/main/java/org/openjdk/jmh/profile/GCProfiler.java Fri Apr 03 15:13:42 2015 +0300 +++ b/jmh-core/src/main/java/org/openjdk/jmh/profile/GCProfiler.java Fri Apr 03 15:14:40 2015 +0300 @@ -24,20 +24,59 @@ */ package org.openjdk.jmh.profile; +import com.sun.management.GarbageCollectionNotificationInfo; import org.openjdk.jmh.infra.BenchmarkParams; import org.openjdk.jmh.infra.IterationParams; import org.openjdk.jmh.results.AggregationPolicy; import org.openjdk.jmh.results.Result; +import org.openjdk.jmh.util.HashMultiset; +import org.openjdk.jmh.util.Multiset; +import javax.management.ListenerNotFoundException; +import javax.management.Notification; +import javax.management.NotificationEmitter; +import javax.management.NotificationListener; +import javax.management.openmbean.CompositeData; import java.lang.management.GarbageCollectorMXBean; import java.lang.management.ManagementFactory; -import java.util.Arrays; +import java.lang.management.MemoryUsage; +import java.util.ArrayList; import java.util.Collection; +import java.util.HashSet; import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.TimeUnit; public class GCProfiler implements InternalProfiler { + private long beforeTime; private long beforeGCCount; private long beforeGCTime; + private final NotificationListener listener; + private Set observedSpaces = new HashSet(); + private Multiset churn = new HashMultiset(); + + public GCProfiler() { + listener = new NotificationListener() { + @Override + public void handleNotification(Notification n, Object o) { + if (n.getType().equals(GarbageCollectionNotificationInfo.GARBAGE_COLLECTION_NOTIFICATION)) { + GarbageCollectionNotificationInfo info = GarbageCollectionNotificationInfo.from((CompositeData) n.getUserData()); + + Map mapBefore = info.getGcInfo().getMemoryUsageBeforeGc(); + Map mapAfter = info.getGcInfo().getMemoryUsageAfterGc(); + for (Map.Entry entry : mapAfter.entrySet()) { + String name = entry.getKey(); + MemoryUsage after = entry.getValue(); + MemoryUsage before = mapBefore.get(name); + long c = before.getUsed() - after.getUsed(); + churn.add(name, c); + observedSpaces.add(name); + } + } + } + }; + } @Override public String getDescription() { @@ -56,6 +95,8 @@ @Override public void beforeIteration(BenchmarkParams benchmarkParams, IterationParams iterationParams) { + installHooks(); + long gcTime = 0; long gcCount = 0; for (GarbageCollectorMXBean bean : ManagementFactory.getGarbageCollectorMXBeans()) { @@ -64,10 +105,14 @@ } this.beforeGCCount = gcCount; this.beforeGCTime = gcTime; + this.beforeTime = System.nanoTime(); } @Override public Collection afterIteration(BenchmarkParams benchmarkParams, IterationParams iterationParams) { + uninstallHooks(); + long afterTime = System.nanoTime(); + long gcTime = 0; long gcCount = 0; for (GarbageCollectorMXBean bean : ManagementFactory.getGarbageCollectorMXBeans()) { @@ -75,12 +120,42 @@ gcTime += bean.getCollectionTime(); } - return Arrays.asList( - new ProfilerResult(Defaults.PREFIX + "gc.count.profiled", gcCount - beforeGCCount, "counts", AggregationPolicy.SUM), - new ProfilerResult(Defaults.PREFIX + "gc.count.total", gcCount, "counts", AggregationPolicy.MAX), - new ProfilerResult(Defaults.PREFIX + "gc.time.profiled", gcTime - beforeGCTime, "ms", AggregationPolicy.SUM), - new ProfilerResult(Defaults.PREFIX + "gc.time.total", gcTime, "ms", AggregationPolicy.MAX) - ); + List results = new ArrayList(); + results.add(new ProfilerResult(Defaults.PREFIX + "gc.count.profiled", gcCount - beforeGCCount, "counts", AggregationPolicy.SUM)); + results.add(new ProfilerResult(Defaults.PREFIX + "gc.count.total", gcCount, "counts", AggregationPolicy.MAX)); + results.add(new ProfilerResult(Defaults.PREFIX + "gc.time.profiled", gcTime - beforeGCTime, "ms", AggregationPolicy.SUM)); + results.add(new ProfilerResult(Defaults.PREFIX + "gc.time.total", gcTime, "ms", AggregationPolicy.MAX)); + + for (String space : observedSpaces) { + double churnMb = churn.count(space) * 1.0 / 1024 / 1024; + double churnRate = churnMb * TimeUnit.SECONDS.toNanos(1) / (afterTime - beforeTime); + results.add(new ProfilerResult(Defaults.PREFIX + "gc.churn.{" + space + "}", churnRate, "MB/sec", AggregationPolicy.AVG)); + } + + return results; + } + + private boolean hooksInstalled; + + public synchronized void installHooks() { + if (hooksInstalled) return; + hooksInstalled = true; + churn = new HashMultiset(); + for (GarbageCollectorMXBean bean : ManagementFactory.getGarbageCollectorMXBeans()) { + ((NotificationEmitter) bean).addNotificationListener(listener, null, null); + } + } + + public synchronized void uninstallHooks() { + if (!hooksInstalled) return; + hooksInstalled = false; + for (GarbageCollectorMXBean bean : ManagementFactory.getGarbageCollectorMXBeans()) { + try { + ((NotificationEmitter) bean).removeNotificationListener(listener); + } catch (ListenerNotFoundException e) { + // Do nothing + } + } } }