diff --git a/src/hotspot/share/jfr/jni/jfrJniMethod.cpp b/src/hotspot/share/jfr/jni/jfrJniMethod.cpp index eaa58e97acf..d591dfdf368 100644 --- a/src/hotspot/share/jfr/jni/jfrJniMethod.cpp +++ b/src/hotspot/share/jfr/jni/jfrJniMethod.cpp @@ -321,8 +321,8 @@ JVM_ENTRY_NO_ENV(void, jfr_set_force_instrumentation(JNIEnv* env, jobject jvm, j JfrEventClassTransformer::set_force_instrumentation(force_instrumentation == JNI_TRUE); JVM_END -JVM_ENTRY_NO_ENV(void, jfr_emit_old_object_samples(JNIEnv* env, jobject jvm, jlong cutoff_ticks, jboolean emit_all)) - LeakProfiler::emit_events(cutoff_ticks, emit_all == JNI_TRUE); +JVM_ENTRY_NO_ENV(void, jfr_emit_old_object_samples(JNIEnv* env, jobject jvm, jlong cutoff_ticks, jboolean emit_all, jboolean skip_bfs)) + LeakProfiler::emit_events(cutoff_ticks, emit_all == JNI_TRUE, skip_bfs == JNI_TRUE); JVM_END JVM_ENTRY_NO_ENV(void, jfr_exclude_thread(JNIEnv* env, jobject jvm, jobject t)) diff --git a/src/hotspot/share/jfr/jni/jfrJniMethod.hpp b/src/hotspot/share/jfr/jni/jfrJniMethod.hpp index 8c0a170e22f..e97562e237f 100644 --- a/src/hotspot/share/jfr/jni/jfrJniMethod.hpp +++ b/src/hotspot/share/jfr/jni/jfrJniMethod.hpp @@ -132,7 +132,7 @@ jlong JNICALL jfr_get_unloaded_event_classes_count(JNIEnv* env, jobject jvm); jboolean JNICALL jfr_set_cutoff(JNIEnv* env, jobject jvm, jlong event_type_id, jlong cutoff_ticks); -void JNICALL jfr_emit_old_object_samples(JNIEnv* env, jobject jvm, jlong cutoff_ticks, jboolean); +void JNICALL jfr_emit_old_object_samples(JNIEnv* env, jobject jvm, jlong cutoff_ticks, jboolean, jboolean); jboolean JNICALL jfr_should_rotate_disk(JNIEnv* env, jobject jvm); diff --git a/src/hotspot/share/jfr/jni/jfrJniMethodRegistration.cpp b/src/hotspot/share/jfr/jni/jfrJniMethodRegistration.cpp index 8c1a8b7b372..8322612e0b1 100644 --- a/src/hotspot/share/jfr/jni/jfrJniMethodRegistration.cpp +++ b/src/hotspot/share/jfr/jni/jfrJniMethodRegistration.cpp @@ -81,7 +81,7 @@ JfrJniMethodRegistration::JfrJniMethodRegistration(JNIEnv* env) { (char*)"setForceInstrumentation", (char*)"(Z)V", (void*)jfr_set_force_instrumentation, (char*)"getUnloadedEventClassCount", (char*)"()J", (void*)jfr_get_unloaded_event_classes_count, (char*)"setCutoff", (char*)"(JJ)Z", (void*)jfr_set_cutoff, - (char*)"emitOldObjectSamples", (char*)"(JZ)V", (void*)jfr_emit_old_object_samples, + (char*)"emitOldObjectSamples", (char*)"(JZZ)V", (void*)jfr_emit_old_object_samples, (char*)"shouldRotateDisk", (char*)"()Z", (void*)jfr_should_rotate_disk, (char*)"exclude", (char*)"(Ljava/lang/Thread;)V", (void*)jfr_exclude_thread, (char*)"include", (char*)"(Ljava/lang/Thread;)V", (void*)jfr_include_thread, diff --git a/src/hotspot/share/jfr/leakprofiler/chains/pathToGcRootsOperation.cpp b/src/hotspot/share/jfr/leakprofiler/chains/pathToGcRootsOperation.cpp index a76044d9c73..98f6c907e8c 100644 --- a/src/hotspot/share/jfr/leakprofiler/chains/pathToGcRootsOperation.cpp +++ b/src/hotspot/share/jfr/leakprofiler/chains/pathToGcRootsOperation.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2020, 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 @@ -47,8 +47,8 @@ #include "runtime/safepoint.hpp" #include "utilities/globalDefinitions.hpp" -PathToGcRootsOperation::PathToGcRootsOperation(ObjectSampler* sampler, EdgeStore* edge_store, int64_t cutoff, bool emit_all) : - _sampler(sampler),_edge_store(edge_store), _cutoff_ticks(cutoff), _emit_all(emit_all) {} +PathToGcRootsOperation::PathToGcRootsOperation(ObjectSampler* sampler, EdgeStore* edge_store, int64_t cutoff, bool emit_all, bool skip_bfs) : + _sampler(sampler),_edge_store(edge_store), _cutoff_ticks(cutoff), _emit_all(emit_all), _skip_bfs(skip_bfs) {} /* The EdgeQueue is backed by directly managed virtual memory. * We will attempt to dimension an initial reservation @@ -113,7 +113,7 @@ void PathToGcRootsOperation::doit() { GranularTimer::start(_cutoff_ticks, 1000000); roots.process(); - if (edge_queue.is_full()) { + if (edge_queue.is_full() || _skip_bfs) { // Pathological case where roots don't fit in queue // Do a depth-first search, but mark roots first // to avoid walking sideways over roots diff --git a/src/hotspot/share/jfr/leakprofiler/chains/pathToGcRootsOperation.hpp b/src/hotspot/share/jfr/leakprofiler/chains/pathToGcRootsOperation.hpp index ca42d2b97ac..b43ce7e1c0c 100644 --- a/src/hotspot/share/jfr/leakprofiler/chains/pathToGcRootsOperation.hpp +++ b/src/hotspot/share/jfr/leakprofiler/chains/pathToGcRootsOperation.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2020, 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 @@ -37,9 +37,10 @@ class PathToGcRootsOperation : public OldObjectVMOperation { EdgeStore* const _edge_store; const int64_t _cutoff_ticks; const bool _emit_all; + const bool _skip_bfs; public: - PathToGcRootsOperation(ObjectSampler* sampler, EdgeStore* edge_store, int64_t cutoff, bool emit_all); + PathToGcRootsOperation(ObjectSampler* sampler, EdgeStore* edge_store, int64_t cutoff, bool emit_all, bool skip_bfs); virtual void doit(); }; diff --git a/src/hotspot/share/jfr/leakprofiler/checkpoint/eventEmitter.cpp b/src/hotspot/share/jfr/leakprofiler/checkpoint/eventEmitter.cpp index 54bfa67b8fb..23cccb03c98 100644 --- a/src/hotspot/share/jfr/leakprofiler/checkpoint/eventEmitter.cpp +++ b/src/hotspot/share/jfr/leakprofiler/checkpoint/eventEmitter.cpp @@ -52,7 +52,7 @@ EventEmitter::~EventEmitter() { _jfr_thread_local->clear_cached_stack_trace(); } -void EventEmitter::emit(ObjectSampler* sampler, int64_t cutoff_ticks, bool emit_all) { +void EventEmitter::emit(ObjectSampler* sampler, int64_t cutoff_ticks, bool emit_all, bool skip_bfs) { assert(sampler != NULL, "invariant"); ResourceMark rm; EdgeStore edge_store; @@ -68,7 +68,7 @@ void EventEmitter::emit(ObjectSampler* sampler, int64_t cutoff_ticks, bool emit_ return; } // events emitted with reference chains require a safepoint operation - PathToGcRootsOperation op(sampler, &edge_store, cutoff_ticks, emit_all); + PathToGcRootsOperation op(sampler, &edge_store, cutoff_ticks, emit_all, skip_bfs); VMThread::execute(&op); } diff --git a/src/hotspot/share/jfr/leakprofiler/checkpoint/eventEmitter.hpp b/src/hotspot/share/jfr/leakprofiler/checkpoint/eventEmitter.hpp index c72d505683e..880292ea0e3 100644 --- a/src/hotspot/share/jfr/leakprofiler/checkpoint/eventEmitter.hpp +++ b/src/hotspot/share/jfr/leakprofiler/checkpoint/eventEmitter.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2020, 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 @@ -52,7 +52,7 @@ class EventEmitter : public CHeapObj { void write_event(const ObjectSample* sample, EdgeStore* edge_store); size_t write_events(ObjectSampler* sampler, EdgeStore* store, bool emit_all); - static void emit(ObjectSampler* sampler, int64_t cutoff_ticks, bool emit_all); + static void emit(ObjectSampler* sampler, int64_t cutoff_ticks, bool emit_all, bool skip_bfs); }; #endif // SHARE_JFR_LEAKPROFILER_CHECKPOINT_EVENTEMITTER_HPP diff --git a/src/hotspot/share/jfr/leakprofiler/leakProfiler.cpp b/src/hotspot/share/jfr/leakprofiler/leakProfiler.cpp index 593b5e524f4..e6f064ee0e0 100644 --- a/src/hotspot/share/jfr/leakprofiler/leakProfiler.cpp +++ b/src/hotspot/share/jfr/leakprofiler/leakProfiler.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2020, 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 @@ -78,14 +78,14 @@ bool LeakProfiler::stop() { return true; } -void LeakProfiler::emit_events(int64_t cutoff_ticks, bool emit_all) { +void LeakProfiler::emit_events(int64_t cutoff_ticks, bool emit_all, bool skip_bfs) { if (!is_running()) { return; } // exclusive access to object sampler instance ObjectSampler* const sampler = ObjectSampler::acquire(); assert(sampler != NULL, "invariant"); - EventEmitter::emit(sampler, cutoff_ticks, emit_all); + EventEmitter::emit(sampler, cutoff_ticks, emit_all, skip_bfs); ObjectSampler::release(); } diff --git a/src/hotspot/share/jfr/leakprofiler/leakProfiler.hpp b/src/hotspot/share/jfr/leakprofiler/leakProfiler.hpp index 38fa3a500f3..36e07bbd3a3 100644 --- a/src/hotspot/share/jfr/leakprofiler/leakProfiler.hpp +++ b/src/hotspot/share/jfr/leakprofiler/leakProfiler.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2020, 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 @@ -37,7 +37,7 @@ class LeakProfiler : public AllStatic { static bool stop(); static bool is_running(); - static void emit_events(int64_t cutoff_ticks, bool emit_all); + static void emit_events(int64_t cutoff_ticks, bool emit_all, bool skip_bfs); static void sample(HeapWord* object, size_t size, JavaThread* thread); // Called by GC diff --git a/src/hotspot/share/jfr/recorder/repository/jfrEmergencyDump.cpp b/src/hotspot/share/jfr/recorder/repository/jfrEmergencyDump.cpp index ddf63dc592d..fbf5a6187be 100644 --- a/src/hotspot/share/jfr/recorder/repository/jfrEmergencyDump.cpp +++ b/src/hotspot/share/jfr/recorder/repository/jfrEmergencyDump.cpp @@ -530,7 +530,7 @@ static void post_events(bool exception_handler) { e.commit(); } else { // OOM - LeakProfiler::emit_events(max_jlong, false); + LeakProfiler::emit_events(max_jlong, false, false); } EventDumpReason event; event.set_reason(exception_handler ? "Crash" : "Out of Memory"); diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/JVM.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/JVM.java index 307346fbdc0..0363c65fca2 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/JVM.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/JVM.java @@ -521,8 +521,9 @@ public final class JVM { * * @param cutoff the cutoff in ticks * @param emitAll emit all samples in old object queue + * @param skipBFS don't use BFS when searching for path to GC root */ - public native void emitOldObjectSamples(long cutoff, boolean emitAll); + public native void emitOldObjectSamples(long cutoff, boolean emitAll, boolean skipBFS); /** * Test if a chunk rotation is warranted. diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/OldObjectSample.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/OldObjectSample.java index 75bfb9b6681..a3461b50324 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/OldObjectSample.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/OldObjectSample.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2020, 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 @@ -54,10 +54,11 @@ public final class OldObjectSample { if (isEnabled(recording)) { long nanos = CutoffSetting.parseValueSafe(recording.getSettings().get(OLD_OBJECT_CUTOFF)); long ticks = Utils.nanosToTicks(nanos); - JVM.getJVM().emitOldObjectSamples(ticks, WhiteBox.getWriteAllObjectSamples()); + emit(ticks); } } + // Emit if old object is enabled for at least one recording, and use the largest // cutoff for an enabled recording public static void emit(List recordings, Boolean pathToGcRoots) { @@ -74,10 +75,16 @@ public final class OldObjectSample { } if (enabled) { long ticks = Utils.nanosToTicks(cutoffNanos); - JVM.getJVM().emitOldObjectSamples(ticks, WhiteBox.getWriteAllObjectSamples()); + emit(ticks); } } + private static void emit(long ticks) { + boolean emitAll = WhiteBox.getWriteAllObjectSamples(); + boolean skipBFS = WhiteBox.getSkipBFS(); + JVM.getJVM().emitOldObjectSamples(ticks, emitAll, skipBFS); + } + public static void updateSettingPathToGcRoots(Map s, Boolean pathToGcRoots) { if (pathToGcRoots != null) { s.put(OLD_OBJECT_CUTOFF, pathToGcRoots ? "infinity" : "0 ns"); diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/test/WhiteBox.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/test/WhiteBox.java index 4cd3c6f6667..40cd177f912 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/test/WhiteBox.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/test/WhiteBox.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2020, 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 @@ -28,6 +28,7 @@ package jdk.jfr.internal.test; public final class WhiteBox { private static boolean writeAllObjectSamples; + private static boolean skipBFS; /** * If OldObjectSample event is enabled, calling this method @@ -45,4 +46,19 @@ public final class WhiteBox { return writeAllObjectSamples; } + /** + * If OldObjectSample event is enabled, calling this method + * ensures that BFS is not used when searching for path to GC root. + * Purpose of this method is to trigger code paths that are + * hard to provoke reliably in testing. + * + * @param skipBFS if only DFS should be used + */ + public static void setSkipBFS(boolean skip) { + skipBFS = skip; + } + + public static boolean getSkipBFS() { + return skipBFS; + } } diff --git a/test/jdk/jdk/jfr/event/oldobject/TestLargeRootSet.java b/test/jdk/jdk/jfr/event/oldobject/TestLargeRootSet.java index 14fd2c095a5..3e289f37dce 100644 --- a/test/jdk/jdk/jfr/event/oldobject/TestLargeRootSet.java +++ b/test/jdk/jdk/jfr/event/oldobject/TestLargeRootSet.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2020, 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 @@ -25,11 +25,12 @@ package jdk.jfr.event.oldobject; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import java.util.Random; import java.util.Vector; -import java.util.concurrent.BrokenBarrierException; -import java.util.concurrent.CyclicBarrier; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicInteger; import jdk.jfr.Recording; import jdk.jfr.consumer.RecordedClass; @@ -52,117 +53,50 @@ import jdk.test.lib.jfr.Events; * @run main/othervm -XX:TLABSize=2k jdk.jfr.event.oldobject.TestLargeRootSet */ public class TestLargeRootSet { - - private static final int THREAD_COUNT = 50; - private static final Random RANDOM = new Random(4711); - public static Vector temporaries = new Vector<>(OldObjects.MIN_SIZE); - - private static class RootThread extends Thread { - private final CyclicBarrier barrier; - private int maxDepth = OldObjects.MIN_SIZE / THREAD_COUNT; - - RootThread(CyclicBarrier cb) { - this.barrier = cb; - } - - public void run() { - buildRootObjects(); - } - - private void buildRootObjects() { - if (maxDepth-- > 0) { - // Allocate array to trigger sampling code path for interpreter - // / c1 - StackObject[] stackObject = new StackObject[RANDOM.nextInt(7)]; - temporaries.add(stackObject); // make sure object escapes - buildRootObjects(); - } else { - temporaries.clear(); - try { - barrier.await(); // wait for gc - barrier.await(); // wait for recording to be stopped - } catch (InterruptedException e) { - System.err.println("Thread was unexpected interrupted: " + e.getMessage()); - } catch (BrokenBarrierException e) { - System.err.println("Unexpected barrier exception: " + e.getMessage()); - } - return; - } - } + static class Node { + Node left; + Node right; + Object value; } - private static class StackObject { + static class Leak { + // Leaking object has to be of some size, + // otherwise Node object wins most of the + // slots in the object queue. + // In a normal application, objects would + // be of various size and allocated over a + // longer period of time. This would create + // randomness not present in the test. + public long value1; + public Object value2; + float value3; + int value4; + double value5; } public static void main(String[] args) throws Exception { WhiteBox.setWriteAllObjectSamples(true); - int attempt = 1; - while (true) { - System.out.println(); - System.out.println(); - System.out.println("ATTEMPT: " + attempt); - System.out.println("===================================="); - List threads = new ArrayList<>(); - try (Recording r = new Recording()) { - r.enable(EventNames.OldObjectSample).withStackTrace().with("cutoff", "infinity"); - r.start(); - CyclicBarrier cb = new CyclicBarrier(THREAD_COUNT + 1); - for (int i = 0; i < THREAD_COUNT; i++) { - RootThread t = new RootThread(cb); - t.start(); - if (i % 10 == 0) { - // Give threads some breathing room before starting next - // batch - Thread.sleep(100); - } - threads.add(t); - } - cb.await(); - System.gc(); - r.stop(); - cb.await(); - List events = Events.fromRecording(r); - Events.hasEvents(events); - int sample = 0; - for (RecordedEvent e : events) { - RecordedObject ro = e.getValue("object"); - RecordedClass rc = ro.getValue("type"); - System.out.println("Sample: " + sample); - System.out.println(" - allocationTime: " + e.getInstant("allocationTime")); - System.out.println(" - type: " + rc.getName()); - RecordedObject root = e.getValue("root"); - if (root != null) { - System.out.println(" - root:"); - System.out.println(" - description: " + root.getValue("description")); - System.out.println(" - system: " + root.getValue("system")); - System.out.println(" - type: " + root.getValue("type")); - } else { - System.out.println(" - root: N/A"); - } - RecordedStackTrace stack = e.getStackTrace(); - if (stack != null) { - System.out.println(" - stack:"); - int frameCount = 0; - for (RecordedFrame frame : stack.getFrames()) { - RecordedMethod m = frame.getMethod(); - System.out.println(" " + m.getType().getName() + "." + m.getName() + "(...)"); - frameCount++; - if (frameCount == 10) { - break; - } - } - } else { - System.out.println(" - stack: N/A"); - } - System.out.println(); - if (rc.getName().equals(StackObject[].class.getName())) { - return; // ok - } - sample++; + WhiteBox.setSkipBFS(true); + HashMap leaks = new HashMap<>(); + try (Recording r = new Recording()) { + r.enable(EventNames.OldObjectSample).withStackTrace().with("cutoff", "infinity"); + r.start(); + for (int i = 0; i < 1_000_000; i++) { + Node node = new Node(); + node.left = new Node(); + node.right = new Node(); + node.right.value = new Leak(); + leaks.put(i, node); + } + r.stop(); + List events = Events.fromRecording(r); + Events.hasEvents(events); + for (RecordedEvent e : events) { + RecordedClass type = e.getValue("object.type"); + if (type.getName().equals(Leak.class.getName())) { + return; } } - attempt++; } } - }