/* * Copyright (c) 2013, 2022, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions.
*/
/** * Tests for event garbage_collection. * The test function is called from TestGCEvent*.java, with different worker threads. * Groups all events belonging to the same garbage collection (the same gcId). * The group of events belonging to the same GC is called a batch. * * This class contains the verifications done and the worker threads used to generate GCs. * The helper logic are in class GCHelper. * * Summary of verifications: * All gcIds in garbage_collection event are unique. * * All events in batch has the same gcId. * * Number of garbage_collection events == GarbageCollectionMXBean.getCollectionCount() * * garbage_collection.sum_pause_time approximately equals GarbageCollectionMXBean.getCollectionTime() * * Batch contains expected events depending on garbage_collection.name * * garbage_collection_start.timestamp == garbage_collection.startTime. * * garbage_collection.timestamp >= timestamp for all other events in batch. * * The start_garbage_collection and garbage_collection events must be synchronized. * This means that there may be multiple start_garbage_collection before a garbage_collection, * but garbage_collection.gcId must be equal to latest start_garbage_collection.gcId. * * start_garbage_collection must be the first event in the batch, * that means no event with same gcId before garbage_collection_start event. * * garbage_collection.name matches what is expected by the collectors specified in initial_configuration. * * Duration for event "vm/gc/phases/pause" >= 1. Duration for phase level events >= 0. * *
*/ publicclass GCEventAll { private String youngCollector = null; private String oldCollector = null;
/** * Trigger GC events by generating garbage and calling System.gc() concurrently.
*/ publicstaticvoid doTest() throws Throwable { // Trigger GC events by generating garbage and calling System.gc() concurrently. Thread[] workerThreads = newThread[] { newThread(GCEventAll.GarbageRunner.create(10)), newThread(GCEventAll.SystemGcWaitRunner.create(10, 2, 1000))};
GCEventAll test = new GCEventAll();
test.doSingleTest(workerThreads);
}
/** * Runs the test once with given worker threads. * @param workerThreads Threads that generates GCs. * @param gcIds Set of all used gcIds * @throws Exception
*/ privatevoid doSingleTest(Thread[] workerThreads) throws Throwable {
Recording recording = new Recording();
enableAllGcEvents(recording);
// Start with a full GC to minimize risk of getting extra GC between // getBeanCollectionCount() and recording.start().
doSystemGc();
GCHelper.CollectionSummary startBeanCount = GCHelper.CollectionSummary.createFromMxBeans();
recording.start();
for (Thread t : workerThreads) {
t.start();
} for (Thread t : workerThreads) {
t.join();
}
// End with a full GC to minimize risk of getting extra GC between // recording.stop and getBeanCollectionCount().
doSystemGc(); // Add an extra System.gc() to make sure we get at least one full garbage_collection batch at // the end of the test. This extra System.gc() is only necessary when using "+ExplicitGCInvokesConcurrent".
doSystemGc();
/** * Does all verifications of the received events. * * @param events All flight recorder events. * @param beanCounts Number of collections and sum pause time reported by GarbageCollectionMXBeans. * @param gcIds All used gcIds. Must be unique. * @throws Exception
*/ privatevoid verify(List<RecordedEvent> events, GCHelper.CollectionSummary beanCounts) throws Throwable {
List<GCHelper.GcBatch> gcBatches = null;
GCHelper.CollectionSummary eventCounts = null;
// For some GC configurations, the JFR recording may have stopped before we received the last gc event. try {
gcBatches = GCHelper.GcBatch.createFromEvents(events);
eventCounts = GCHelper.CollectionSummary.createFromEvents(gcBatches);
privateint getLastGcId(List<RecordedEvent> events) { int lastGcId = -1; for (RecordedEvent event : events) { if (GCHelper.isGcEvent(event)) { int gcId = GCHelper.getGcId(event); if (gcId > lastGcId) {
lastGcId = gcId;
}
}
}
Asserts.assertTrue(lastGcId != -1, "No gcId found"); return lastGcId;
}
/** * Verifies collection count reported by flight recorder events against the values * reported by GarbageCollectionMXBean. * Number of collections should match exactly. * Sum pause time are allowed some margin of error because of rounding errors in measurements.
*/ privatevoid verifyCollectionCount(GCHelper.CollectionSummary eventCounts, GCHelper.CollectionSummary beanCounts) {
verifyCollectionCount(youngCollector, eventCounts.collectionCountYoung, beanCounts.collectionCountYoung);
verifyCollectionCount(oldCollector, eventCounts.collectionCountOld, beanCounts.collectionCountOld);
}
privatevoid verifyCollectionCount(String collector, long eventCounts, long beanCounts) { if (GCHelper.gcG1Old.equals(oldCollector)) { // MXBean does not report old collections for G1Old, so we have nothing to compare with. return;
} // JFR events and GarbageCollectorMXBean events are not updated at the same time. // This means that number of collections may diff. // We allow a diff of +- 1 collection count. long minCount = Math.max(0, beanCounts - 1); long maxCount = beanCounts + 1;
Asserts.assertGreaterThanOrEqual(eventCounts, minCount, "Too few event counts for collector " + collector);
Asserts.assertLessThanOrEqual(eventCounts, maxCount, "Too many event counts for collector " + collector);
}
/** * Verifies that all events belonging to a single GC are ok. * A GcBatch contains all flight recorder events that belong to a single GC.
*/ privatevoid verifySingleGcBatch(List<GCHelper.GcBatch> batches) { for (GCHelper.GcBatch batch : batches) { //System.out.println("batch:\r\n" + batch.getLog()); try {
RecordedEvent endEvent = batch.getEndEvent();
Asserts.assertNotNull(endEvent, "No end event in batch.");
Asserts.assertNotNull(batch.getName(), "No method name in end event."); long longestPause = Events.assertField(endEvent, "longestPause").atLeast(0L).getValue();
Events.assertField(endEvent, "sumOfPauses").atLeast(longestPause).getValue();
Instant batchStartTime = endEvent.getStartTime();
Instant batchEndTime = endEvent.getEndTime(); for (RecordedEvent event : batch.getEvents()) {
String name = event.getEventType().getName(); if (name.contains("AllocationRequiringGC")) { // Unlike other events, these are sent *before* a GC.
Asserts.assertLessThanOrEqual(event.getStartTime(), batchStartTime, "Timestamp in event after start event, should be sent before GC start");
} else {
Asserts.assertGreaterThanOrEqual(event.getStartTime(), batchStartTime, "startTime in event before batch start event, should be sent after GC start");
} // GCCPUTime is generated after GC is completed. if (!EventNames.GCCPUTime.equals(name)) {
Asserts.assertLessThanOrEqual(event.getEndTime(), batchEndTime, "endTime in event after batch end event, should be sent before GC end");
}
}
// Verify that all required events has been received.
String[] requiredEvents = GCHelper.requiredEvents.get(batch.getName());
Asserts.assertNotNull(requiredEvents, "No required events specified for " + batch.getName()); for (String requiredEvent : requiredEvents) { boolean b = batch.containsEvent(requiredEvent);
Asserts.assertTrue(b, String.format("%s does not contain event %s", batch, requiredEvent));
}
// Verify that we have exactly one heap_summary "Before GC" and one "After GC". int countBeforeGc = 0; int countAfterGc = 0; for (RecordedEvent event : batch.getEvents()) { if (GCHelper.event_heap_summary.equals(event.getEventType().getName())) {
String when = Events.assertField(event, "when").notEmpty().getValue(); if ("Before GC".equals(when)) {
countBeforeGc++;
} elseif ("After GC".equals(when)) {
countAfterGc++;
} else {
Asserts.fail("Unknown value for heap_summary.when: '" + when + "'");
}
}
}
Asserts.assertEquals(1, countBeforeGc, "Unexpected number of heap_summary.before_gc");
Asserts.assertEquals(1, countAfterGc, "Unexpected number of heap_summary.after_gc");
} catch (Throwable e) {
GCHelper.log("verifySingleGcBatch failed for gcEvent:");
GCHelper.log(batch.getLog()); throw e;
}
}
}
// Duration for event "vm/gc/phases/pause" must be >= 1. Other phase event durations must be >= 0.
Duration minDuration = Duration.ofNanos(GCHelper.event_phases_pause.equals(event.getEventType().getName()) ? 1 : 0);
Duration duration = event.getDuration();
Asserts.assertGreaterThanOrEqual(duration, minDuration, "Wrong duration. Event:" + event);
}
}
}
}
/** * Verifies that the collector name in initial configuration matches the name in garbage configuration event. * If the names are not equal, then we check if this is an expected collector override. * For example, if old collector in initial config is "G1Old" we allow both event "G1Old" and "SerialOld".
*/ privatevoid verifyCollectorNames(List<GCHelper.GcBatch> batches) { for (GCHelper.GcBatch batch : batches) {
String name = batch.getName();
Asserts.assertNotNull(name, "garbage_collection.name was null"); boolean isYoung = batch.isYoungCollection();
String expectedName = isYoung ? youngCollector : oldCollector; if (!expectedName.equals(name)) { // Collector names not equal. Check if the collector has been overridden by an expected collector.
String overrideKey = expectedName + "." + name; boolean isOverride = GCHelper.collectorOverrides.contains(overrideKey);
Asserts.assertTrue(isOverride, String.format("Unexpected event name(%s) for collectors(%s, %s)", name, youngCollector, oldCollector));
}
}
}
/** * Verifies field "cause" in garbage_collection event. * Only check that at cause is not null and that at least 1 cause is "System.gc()" * We might want to check more cause reasons later.
*/ privatevoid verifyCollectionCause(List<GCHelper.GcBatch> batches) { int systemGcCount = 0; for (GCHelper.GcBatch batch : batches) {
RecordedEvent endEvent = batch.getEndEvent();
String cause = Events.assertField(endEvent, "cause").notEmpty().getValue(); // A System.GC() can be consolidated into a GCLocker GC if (cause.equals("System.gc()") || cause.equals("GCLocker Initiated GC")) {
systemGcCount++;
}
Asserts.assertNotNull(batch.getName(), "garbage_collection.name was null");
} final String msg = "No event with cause=System.gc(), collectors(%s, %s)";
Asserts.assertTrue(systemGcCount > 0, String.format(msg, youngCollector, oldCollector));
}
publicvoid run() { for (int i = 0; i < totalCollections; i++) {
GCEventAll.doSystemGc();
}
}
}
/** * Thread that creates garbage until a certain number of GCs has been run.
*/ publicstaticclass GarbageRunner implements Runnable { privatefinalint totalCollections; publicbyte[] dummyBuffer = null;
public GarbageRunner(int totalCollections) { this.totalCollections = totalCollections;
}
publicvoid run() { long currCollections = GCHelper.CollectionSummary.createFromMxBeans().sum(); long endCollections = totalCollections + currCollections;
Random r = new Random(0); while (true) { for (int i = 0; i < 1000; i++) {
dummyBuffer = newbyte[r.nextInt(10000)];
} if (GCHelper.CollectionSummary.createFromMxBeans().sum() >= endCollections) { break;
}
}
}
}
/** * Thread that runs System.gc() and then wait for a number of GCs or a maximum time.
*/ publicstaticclass SystemGcWaitRunner implements Runnable { privatefinalint totalCollections; privatefinalint minWaitCollections; privatefinallong maxWaitMillis;
public SystemGcWaitRunner(int totalCollections, int minWaitCollections, long maxWaitMillis) { this.totalCollections = totalCollections; this.minWaitCollections = minWaitCollections; this.maxWaitMillis = maxWaitMillis;
}
publicstatic SystemGcWaitRunner create(int deltaCollections, int minWaitCollections, long maxWaitMillis) { returnnew SystemGcWaitRunner(deltaCollections, minWaitCollections, maxWaitMillis);
}
publicvoid run() { long currCount = GCHelper.CollectionSummary.createFromMxBeans().sum(); long endCount = totalCollections + currCount; long nextSystemGcCount = currCount + minWaitCollections; long now = System.currentTimeMillis(); long nextSystemGcMillis = now + maxWaitMillis;
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.