FULL PRODUCT VERSION :
java version "1.7.0"
Java(TM) SE Runtime Environment (build 1.7.0-b147)
Java HotSpot(TM) Client VM (build 21.0-b17, mixed mode, sharing)
(I verified that this is still a problem in: http://hg.openjdk.java.net/jdk7u/jdk7u/)
FULL OS VERSION :
This is a problem on ALL OS's.
A DESCRIPTION OF THE PROBLEM :
Consider the following in hotspot/src/share/vm/services:
When a garbage collection is complete, GCMemoryManager::gc_end gets called (in memoryManager.cpp). This method completes the stats within the _current_gc_stat object. As the method proceeds, it makes a call to GCNotifier::pushNotification (in gcNotifier.cpp) BEFORE it moves _current_gc_stat into _last_gc_stat.
The problem with this is that GCNotifier::pushNotification makes a call to GCMemoryManager::get_last_gc_stat, which always has the LAST set of stats (and bogus data if it's the first call).
Any java app that subscribes for GC notifications consistently gets stats that is 1 collection old (with the 1st set of stats bogus).
THE PROBLEM WAS REPRODUCIBLE WITH -Xint FLAG: Did not try
THE PROBLEM WAS REPRODUCIBLE WITH -server FLAG: Did not try
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Run the provided source code:
java -XX:+PrintGC -XX:+PrintGCTimeStamps -XX:+TraceGen0Time -XX:+TraceGen1Time GCTest
EXPECTED VERSUS ACTUAL BEHAVIOR :
Notice in the following output that the id and secs consistently apply to the PREVIOUS collection, with the very 1st collection pointing to bogus data:
13:24:38.797
13:24:39.000
0.266: [GC 2457K->203K(15872K), 0.0011547 secs]
13:24:39.000 [ name:Copy, action:end of minor GC, cause:Allocation Failure, id:3932536, secs:0.000000 ]
13:24:40.000
13:24:41.000
2.266: [GC 4615K->269K(15872K), 0.0010946 secs]
13:24:41.000 [ name:Copy, action:end of minor GC, cause:Allocation Failure, id:1, secs:0.001194 ]
13:24:42.000
13:24:43.000
4.266: [GC 4444K->271K(15872K), 0.0009936 secs]
13:24:43.000 [ name:Copy, action:end of minor GC, cause:Allocation Failure, id:2, secs:0.001124 ]
13:24:44.000
13:24:45.000
6.266: [GC 4419K->272K(15872K), 0.0006629 secs]
13:24:45.000 [ name:Copy, action:end of minor GC, cause:Allocation Failure, id:3, secs:0.001024 ]
13:24:46.000
13:24:47.000
8.266: [GC 4422K->274K(15872K), 0.0004875 secs]
13:24:47.000 [ name:Copy, action:end of minor GC, cause:Allocation Failure, id:4, secs:0.000692 ]
13:24:48.000
[Accumulated GC generation 0 time 0.0045320 secs, 5 GC's, avg GC time 0.0009064]
[Accumulated GC generation 1 time 0.0000000 secs, 0 GC's, avg GC time 0.0000000]
REPRODUCIBILITY :
This bug can be reproduced always.
---------- BEGIN SOURCE ----------
import java.lang.management.GarbageCollectorMXBean;
import java.lang.management.ManagementFactory;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import javax.management.Notification;
import javax.management.NotificationBroadcaster;
import javax.management.NotificationListener;
import javax.management.openmbean.CompositeData;
import sun.misc.Perf;
public class GcTest
{
private static final long FREQ = Perf.getPerf().highResFrequency();
private static void handleGcNotification(Notification notification)
{
if (notification == null)
return;
Object data = notification.getUserData();
if (!(data instanceof CompositeData))
return;
long curTimeMs = System.currentTimeMillis();
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS");
StringBuilder sb = new StringBuilder();
sb.append(sdf.format(new Date(curTimeMs)));
CompositeData cd = (CompositeData) data;
sb.append(" [ name:");
sb.append(String.valueOf(cd.get("gcName")));
sb.append(", action:");
sb.append(String.valueOf(cd.get("gcAction")));
sb.append(", cause:");
sb.append(String.valueOf(cd.get("gcCause")));
Object gcInfo = cd.get("gcInfo");
if (gcInfo instanceof CompositeData)
{
cd = (CompositeData) gcInfo;
sb.append(", id:");
sb.append(String.valueOf(cd.get("id")));
// NOTE: duration is in ticks, NOT millis. Bug: 7087969
Object duration = cd.get("duration");
double ticks = (duration instanceof Number) ? ((Number) duration).doubleValue() : -1;
double secs = ticks > -1 ? ticks / FREQ : -1;
sb.append(", secs:");
sb.append(String.format("%.6f", Double.valueOf(secs)));
}
sb.append(" ]");
System.out.println(sb);
}
public static void main(String[] args)
{
NotificationListener nl = new NotificationListener()
{
@Override
public void handleNotification(Notification notification, Object handback)
{
handleGcNotification(notification);
}
};
List<GarbageCollectorMXBean> list = ManagementFactory.getGarbageCollectorMXBeans();
for (GarbageCollectorMXBean bean : list)
{
if (bean instanceof NotificationBroadcaster)
((NotificationBroadcaster) bean).addNotificationListener(nl, null, null);
}
int runtimeSecs = 10;
int bytesSize = 1024 * 1024 * 2;
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS");
long startMs = System.currentTimeMillis();
long lastSec = 0;
for (;;)
{
long curTimeMs = System.currentTimeMillis();
if (curTimeMs - startMs >= runtimeSecs * 1000)
break;
if (lastSec != curTimeMs / 1000)
{
System.out.println(sdf.format(new Date(curTimeMs)));
lastSec = curTimeMs / 1000;
byte[] bytes = new byte[bytesSize];
}
}
}
}
---------- END SOURCE ----------