changeset 59382:cc182bcb27df

8235921: jdk/jfr/event/oldobject/TestLargeRootSet.java times out with debug bits Reviewed-by: mgronlun
author egahlin
date Thu, 21 May 2020 01:36:46 +0200
parents 60c5c3174890
children e4ae92a9c67e
files src/hotspot/share/jfr/jni/jfrJniMethod.cpp src/hotspot/share/jfr/jni/jfrJniMethod.hpp src/hotspot/share/jfr/jni/jfrJniMethodRegistration.cpp src/hotspot/share/jfr/leakprofiler/chains/pathToGcRootsOperation.cpp src/hotspot/share/jfr/leakprofiler/chains/pathToGcRootsOperation.hpp src/hotspot/share/jfr/leakprofiler/checkpoint/eventEmitter.cpp src/hotspot/share/jfr/leakprofiler/checkpoint/eventEmitter.hpp src/hotspot/share/jfr/leakprofiler/leakProfiler.cpp src/hotspot/share/jfr/leakprofiler/leakProfiler.hpp src/hotspot/share/jfr/recorder/repository/jfrEmergencyDump.cpp src/jdk.jfr/share/classes/jdk/jfr/internal/JVM.java src/jdk.jfr/share/classes/jdk/jfr/internal/OldObjectSample.java src/jdk.jfr/share/classes/jdk/jfr/internal/test/WhiteBox.java test/jdk/jdk/jfr/event/oldobject/TestLargeRootSet.java
diffstat 14 files changed, 90 insertions(+), 131 deletions(-) [+]
line wrap: on
line diff
--- a/src/hotspot/share/jfr/jni/jfrJniMethod.cpp	Fri May 08 18:59:03 2020 +0200
+++ b/src/hotspot/share/jfr/jni/jfrJniMethod.cpp	Thu May 21 01:36:46 2020 +0200
@@ -321,8 +321,8 @@
   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))
--- a/src/hotspot/share/jfr/jni/jfrJniMethod.hpp	Fri May 08 18:59:03 2020 +0200
+++ b/src/hotspot/share/jfr/jni/jfrJniMethod.hpp	Thu May 21 01:36:46 2020 +0200
@@ -132,7 +132,7 @@
 
 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);
 
--- a/src/hotspot/share/jfr/jni/jfrJniMethodRegistration.cpp	Fri May 08 18:59:03 2020 +0200
+++ b/src/hotspot/share/jfr/jni/jfrJniMethodRegistration.cpp	Thu May 21 01:36:46 2020 +0200
@@ -81,7 +81,7 @@
       (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,
--- a/src/hotspot/share/jfr/leakprofiler/chains/pathToGcRootsOperation.cpp	Fri May 08 18:59:03 2020 +0200
+++ b/src/hotspot/share/jfr/leakprofiler/chains/pathToGcRootsOperation.cpp	Thu May 21 01:36:46 2020 +0200
@@ -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 @@
 
   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
--- a/src/hotspot/share/jfr/leakprofiler/chains/pathToGcRootsOperation.hpp	Fri May 08 18:59:03 2020 +0200
+++ b/src/hotspot/share/jfr/leakprofiler/chains/pathToGcRootsOperation.hpp	Thu May 21 01:36:46 2020 +0200
@@ -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 @@
   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();
 };
 
--- a/src/hotspot/share/jfr/leakprofiler/checkpoint/eventEmitter.cpp	Fri May 08 18:59:03 2020 +0200
+++ b/src/hotspot/share/jfr/leakprofiler/checkpoint/eventEmitter.cpp	Thu May 21 01:36:46 2020 +0200
@@ -52,7 +52,7 @@
   _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 @@
     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);
 }
 
--- a/src/hotspot/share/jfr/leakprofiler/checkpoint/eventEmitter.hpp	Fri May 08 18:59:03 2020 +0200
+++ b/src/hotspot/share/jfr/leakprofiler/checkpoint/eventEmitter.hpp	Thu May 21 01:36:46 2020 +0200
@@ -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 @@
   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
--- a/src/hotspot/share/jfr/leakprofiler/leakProfiler.cpp	Fri May 08 18:59:03 2020 +0200
+++ b/src/hotspot/share/jfr/leakprofiler/leakProfiler.cpp	Thu May 21 01:36:46 2020 +0200
@@ -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 @@
   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();
 }
 
--- a/src/hotspot/share/jfr/leakprofiler/leakProfiler.hpp	Fri May 08 18:59:03 2020 +0200
+++ b/src/hotspot/share/jfr/leakprofiler/leakProfiler.hpp	Thu May 21 01:36:46 2020 +0200
@@ -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 @@
   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
--- a/src/hotspot/share/jfr/recorder/repository/jfrEmergencyDump.cpp	Fri May 08 18:59:03 2020 +0200
+++ b/src/hotspot/share/jfr/recorder/repository/jfrEmergencyDump.cpp	Thu May 21 01:36:46 2020 +0200
@@ -530,7 +530,7 @@
     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");
--- a/src/jdk.jfr/share/classes/jdk/jfr/internal/JVM.java	Fri May 08 18:59:03 2020 +0200
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/JVM.java	Thu May 21 01:36:46 2020 +0200
@@ -521,8 +521,9 @@
      *
      * @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.
--- a/src/jdk.jfr/share/classes/jdk/jfr/internal/OldObjectSample.java	Fri May 08 18:59:03 2020 +0200
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/OldObjectSample.java	Thu May 21 01:36:46 2020 +0200
@@ -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 @@
         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<PlatformRecording> recordings, Boolean pathToGcRoots) {
@@ -74,10 +75,16 @@
         }
         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<String, String> s, Boolean pathToGcRoots) {
         if (pathToGcRoots != null) {
             s.put(OLD_OBJECT_CUTOFF, pathToGcRoots ? "infinity" : "0 ns");
--- a/src/jdk.jfr/share/classes/jdk/jfr/internal/test/WhiteBox.java	Fri May 08 18:59:03 2020 +0200
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/test/WhiteBox.java	Thu May 21 01:36:46 2020 +0200
@@ -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 @@
 public final class WhiteBox {
 
     private static boolean writeAllObjectSamples;
+    private static boolean skipBFS;
 
     /**
      * If OldObjectSample event is enabled, calling this method
@@ -45,4 +46,19 @@
         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;
+    }
 }
--- a/test/jdk/jdk/jfr/event/oldobject/TestLargeRootSet.java	Fri May 08 18:59:03 2020 +0200
+++ b/test/jdk/jdk/jfr/event/oldobject/TestLargeRootSet.java	Thu May 21 01:36:46 2020 +0200
@@ -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 @@
  * @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<StackObject[]> 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<RootThread> 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<RecordedEvent> 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<Object, Node> 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<RecordedEvent> 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++;
         }
     }
-
 }