8163511: Allocation of compile task fails with assert: "Leaking compilation tasks?"
authorthartmann
Thu, 07 Mar 2019 08:38:16 +0100
changeset 54015 cd701366fcf8
parent 54014 083d7a34bbfd
child 54016 a7cccbdf73f3
8163511: Allocation of compile task fails with assert: "Leaking compilation tasks?" Summary: Use weak handles for compile tasks to allow unloading of referenced methods. Reviewed-by: kvn, coleenp, eosterlund
src/hotspot/share/ci/ciEnv.cpp
src/hotspot/share/compiler/compileBroker.cpp
src/hotspot/share/compiler/compileTask.cpp
src/hotspot/share/compiler/compileTask.hpp
src/hotspot/share/runtime/compilationPolicy.cpp
src/hotspot/share/runtime/thread.cpp
src/hotspot/share/runtime/thread.hpp
src/hotspot/share/runtime/tieredThresholdPolicy.cpp
test/hotspot/jtreg/compiler/classUnloading/methodUnloading/TestOverloadCompileQueues.java
--- a/src/hotspot/share/ci/ciEnv.cpp	Thu Mar 07 00:23:45 2019 +0100
+++ b/src/hotspot/share/ci/ciEnv.cpp	Thu Mar 07 08:38:16 2019 +0100
@@ -119,6 +119,9 @@
   _system_dictionary_modification_counter = system_dictionary_modification_counter;
   _num_inlined_bytecodes = 0;
   assert(task == NULL || thread->task() == task, "sanity");
+  if (task != NULL) {
+    task->mark_started(os::elapsed_counter());
+  }
   _task = task;
   _log = NULL;
 
--- a/src/hotspot/share/compiler/compileBroker.cpp	Thu Mar 07 00:23:45 2019 +0100
+++ b/src/hotspot/share/compiler/compileBroker.cpp	Thu Mar 07 08:38:16 2019 +0100
@@ -258,14 +258,14 @@
   }
 #endif
   CompileLog*     log  = thread->log();
-  if (log != NULL)  task->log_task_start(log);
+  if (log != NULL && !task->is_unloaded())  task->log_task_start(log);
 }
 
 CompileTaskWrapper::~CompileTaskWrapper() {
   CompilerThread* thread = CompilerThread::current();
   CompileTask* task = thread->task();
   CompileLog*  log  = thread->log();
-  if (log != NULL)  task->log_task_done(log);
+  if (log != NULL && !task->is_unloaded())  task->log_task_done(log);
   thread->set_task(NULL);
   task->set_code_handle(NULL);
   thread->set_env(NULL);
@@ -444,6 +444,9 @@
   {
     NoSafepointVerifier nsv;
     task = CompilationPolicy::policy()->select_task(this);
+    if (task != NULL) {
+      task = task->select_for_compilation();
+    }
   }
 
   if (task != NULL) {
@@ -455,7 +458,6 @@
     remove(task);
     purge_stale_tasks(); // may temporarily release MCQ lock
   }
-
   return task;
 }
 
@@ -483,7 +485,7 @@
 }
 
 void CompileQueue::remove(CompileTask* task) {
-   assert(MethodCompileQueue_lock->owned_by_self(), "must own lock");
+  assert(MethodCompileQueue_lock->owned_by_self(), "must own lock");
   if (task->prev() != NULL) {
     task->prev()->set_next(task->next());
   } else {
@@ -534,7 +536,7 @@
 
   char buf[2000];
   int buflen = sizeof(buf);
-  Threads::print_threads_compiling(st, buf, buflen);
+  Threads::print_threads_compiling(st, buf, buflen, /* short_form = */ true);
 
   st->cr();
   if (_c1_compile_queue != NULL) {
--- a/src/hotspot/share/compiler/compileTask.cpp	Thu Mar 07 00:23:45 2019 +0100
+++ b/src/hotspot/share/compiler/compileTask.cpp	Thu Mar 07 08:38:16 2019 +0100
@@ -63,28 +63,31 @@
 /**
 * Add a task to the free list.
 */
-
 void CompileTask::free(CompileTask* task) {
- MutexLocker locker(CompileTaskAlloc_lock);
- if (!task->is_free()) {
-   task->set_code(NULL);
-   assert(!task->lock()->is_locked(), "Should not be locked when freed");
-   JNIHandles::destroy_global(task->_method_holder);
-   JNIHandles::destroy_global(task->_hot_method_holder);
+  MutexLocker locker(CompileTaskAlloc_lock);
+  if (!task->is_free()) {
+    task->set_code(NULL);
+    assert(!task->lock()->is_locked(), "Should not be locked when freed");
+    if ((task->_method_holder != NULL && JNIHandles::is_weak_global_handle(task->_method_holder)) ||
+        (task->_hot_method_holder != NULL && JNIHandles::is_weak_global_handle(task->_hot_method_holder))) {
+      JNIHandles::destroy_weak_global(task->_method_holder);
+      JNIHandles::destroy_weak_global(task->_hot_method_holder);
+    } else {
+      JNIHandles::destroy_global(task->_method_holder);
+      JNIHandles::destroy_global(task->_hot_method_holder);
+    }
+    if (task->_failure_reason_on_C_heap && task->_failure_reason != NULL) {
+      os::free((void*) task->_failure_reason);
+    }
+    task->_failure_reason = NULL;
+    task->_failure_reason_on_C_heap = false;
 
-   if (task->_failure_reason_on_C_heap && task->_failure_reason != NULL) {
-     os::free((void*) task->_failure_reason);
-   }
-   task->_failure_reason = NULL;
-   task->_failure_reason_on_C_heap = false;
-
-   task->set_is_free(true);
-   task->set_next(_task_free_list);
-   _task_free_list = task;
- }
+    task->set_is_free(true);
+    task->set_next(_task_free_list);
+    _task_free_list = task;
+  }
 }
 
-
 void CompileTask::initialize(int compile_id,
                              const methodHandle& method,
                              int osr_bci,
@@ -98,7 +101,7 @@
   Thread* thread = Thread::current();
   _compile_id = compile_id;
   _method = method();
-  _method_holder = JNIHandles::make_global(Handle(thread, method->method_holder()->klass_holder()));
+  _method_holder = JNIHandles::make_weak_global(Handle(thread, method->method_holder()->klass_holder()));
   _osr_bci = osr_bci;
   _is_blocking = is_blocking;
   JVMCI_ONLY(_has_waiter = CompileBroker::compiler(comp_level)->is_jvmci();)
@@ -113,20 +116,20 @@
   _hot_method = NULL;
   _hot_method_holder = NULL;
   _hot_count = hot_count;
-  _time_queued = 0;  // tidy
+  _time_queued = os::elapsed_counter();
+  _time_started = 0;
   _compile_reason = compile_reason;
   _failure_reason = NULL;
   _failure_reason_on_C_heap = false;
 
   if (LogCompilation) {
-    _time_queued = os::elapsed_counter();
     if (hot_method.not_null()) {
       if (hot_method == method) {
         _hot_method = _method;
       } else {
         _hot_method = hot_method();
         // only add loader or mirror if different from _method_holder
-        _hot_method_holder = JNIHandles::make_global(Handle(thread, hot_method->method_holder()->klass_holder()));
+        _hot_method_holder = JNIHandles::make_weak_global(Handle(thread, hot_method->method_holder()->klass_holder()));
       }
     }
   }
@@ -141,6 +144,24 @@
   return CompileBroker::compiler(_comp_level);
 }
 
+// Replace weak handles by strong handles to avoid unloading during compilation.
+CompileTask* CompileTask::select_for_compilation() {
+  if (is_unloaded()) {
+    // Guard against concurrent class unloading
+    return NULL;
+  }
+  Thread* thread = Thread::current();
+  assert(_method->method_holder()->is_loader_alive(), "should be alive");
+  Handle method_holder(thread, _method->method_holder()->klass_holder());
+  JNIHandles::destroy_weak_global(_method_holder);
+  JNIHandles::destroy_weak_global(_hot_method_holder);
+  _method_holder = JNIHandles::make_global(method_holder);
+  if (_hot_method != NULL) {
+    _hot_method_holder = JNIHandles::make_global(Handle(thread, _hot_method->method_holder()->klass_holder()));
+  }
+  return this;
+}
+
 // ------------------------------------------------------------------
 // CompileTask::code/set_code
 //
@@ -162,14 +183,22 @@
 
 void CompileTask::mark_on_stack() {
   // Mark these methods as something redefine classes cannot remove.
+  assert(!is_unloaded(), "unloaded method on the stack");
   _method->set_on_stack(true);
   if (_hot_method != NULL) {
     _hot_method->set_on_stack(true);
   }
 }
 
+bool CompileTask::is_unloaded() const {
+  return _method_holder != NULL && JNIHandles::is_weak_global_handle(_method_holder) && JNIHandles::is_global_weak_cleared(_method_holder);
+}
+
 // RedefineClasses support
 void CompileTask::metadata_do(void f(Metadata*)) {
+  if (is_unloaded()) {
+    return;
+  }
   f(method());
   if (hot_method() != NULL && hot_method() != method()) {
     f(hot_method());
@@ -206,10 +235,20 @@
 // ------------------------------------------------------------------
 // CompileTask::print_impl
 void CompileTask::print_impl(outputStream* st, Method* method, int compile_id, int comp_level,
-                                         bool is_osr_method, int osr_bci, bool is_blocking,
-                                         const char* msg, bool short_form, bool cr) {
+                             bool is_osr_method, int osr_bci, bool is_blocking,
+                             const char* msg, bool short_form, bool cr,
+                             jlong time_queued, jlong time_started) {
   if (!short_form) {
-    st->print("%7d ", (int) st->time_stamp().milliseconds());  // print timestamp
+    // Print current time
+    st->print("%7d ", (int)st->time_stamp().milliseconds());
+    if (Verbose && time_queued != 0) {
+      // Print time in queue and time being processed by compiler thread
+      jlong now = os::elapsed_counter();
+      st->print("%d ", (int)TimeHelper::counter_to_millis(now-time_queued));
+      if (time_started != 0) {
+        st->print("%d ", (int)TimeHelper::counter_to_millis(now-time_started));
+      }
+    }
   }
   // print compiler name if requested
   if (CIPrintCompilerName) {
@@ -284,7 +323,7 @@
 // CompileTask::print_compilation
 void CompileTask::print(outputStream* st, const char* msg, bool short_form, bool cr) {
   bool is_osr_method = osr_bci() != InvocationEntryBci;
-  print_impl(st, method(), compile_id(), comp_level(), is_osr_method, osr_bci(), is_blocking(), msg, short_form, cr);
+  print_impl(st, is_unloaded() ? NULL : method(), compile_id(), comp_level(), is_osr_method, osr_bci(), is_blocking(), msg, short_form, cr, _time_queued, _time_started);
 }
 
 // ------------------------------------------------------------------
--- a/src/hotspot/share/compiler/compileTask.hpp	Thu Mar 07 00:23:45 2019 +0100
+++ b/src/hotspot/share/compiler/compileTask.hpp	Thu Mar 07 08:38:16 2019 +0100
@@ -98,7 +98,8 @@
   CompileTask* _next, *_prev;
   bool         _is_free;
   // Fields used for logging why the compilation was initiated:
-  jlong        _time_queued;  // in units of os::elapsed_counter()
+  jlong        _time_queued;  // time when task was enqueued
+  jlong        _time_started; // time when compilation started
   Method*      _hot_method;   // which method actually triggered this task
   jobject      _hot_method_holder;
   int          _hot_count;    // information about its invocation counter
@@ -156,11 +157,13 @@
 
   void         mark_complete()                   { _is_complete = true; }
   void         mark_success()                    { _is_success = true; }
+  void         mark_started(jlong time)          { _time_started = time; }
 
   int          comp_level()                      { return _comp_level;}
   void         set_comp_level(int comp_level)    { _comp_level = comp_level;}
 
   AbstractCompiler* compiler();
+  CompileTask*      select_for_compilation();
 
   int          num_inlined_bytecodes() const     { return _num_inlined_bytecodes; }
   void         set_num_inlined_bytecodes(int n)  { _num_inlined_bytecodes = n; }
@@ -171,6 +174,7 @@
   void         set_prev(CompileTask* prev)       { _prev = prev; }
   bool         is_free() const                   { return _is_free; }
   void         set_is_free(bool val)             { _is_free = val; }
+  bool         is_unloaded() const;
 
   // RedefineClasses support
   void         metadata_do(void f(Metadata*));
@@ -179,7 +183,8 @@
 private:
   static void  print_impl(outputStream* st, Method* method, int compile_id, int comp_level,
                                       bool is_osr_method = false, int osr_bci = -1, bool is_blocking = false,
-                                      const char* msg = NULL, bool short_form = false, bool cr = true);
+                                      const char* msg = NULL, bool short_form = false, bool cr = true,
+                                      jlong time_queued = 0, jlong time_started = 0);
 
 public:
   void         print(outputStream* st = tty, const char* msg = NULL, bool short_form = false, bool cr = true);
--- a/src/hotspot/share/runtime/compilationPolicy.cpp	Thu Mar 07 00:23:45 2019 +0100
+++ b/src/hotspot/share/runtime/compilationPolicy.cpp	Thu Mar 07 08:38:16 2019 +0100
@@ -176,6 +176,14 @@
 }
 
 CompileTask* CompilationPolicy::select_task_helper(CompileQueue* compile_queue) {
+  // Remove unloaded methods from the queue
+  for (CompileTask* task = compile_queue->first(); task != NULL; ) {
+    CompileTask* next = task->next();
+    if (task->is_unloaded()) {
+      compile_queue->remove_and_mark_stale(task);
+    }
+    task = next;
+  }
 #if INCLUDE_JVMCI
   if (UseJVMCICompiler && !BackgroundCompilation) {
     /*
--- a/src/hotspot/share/runtime/thread.cpp	Thu Mar 07 00:23:45 2019 +0100
+++ b/src/hotspot/share/runtime/thread.cpp	Thu Mar 07 08:38:16 2019 +0100
@@ -4773,7 +4773,7 @@
   print_threads_compiling(st, buf, buflen);
 }
 
-void Threads::print_threads_compiling(outputStream* st, char* buf, int buflen) {
+void Threads::print_threads_compiling(outputStream* st, char* buf, int buflen, bool short_form) {
   ALL_JAVA_THREADS(thread) {
     if (thread->is_Compiler_thread()) {
       CompilerThread* ct = (CompilerThread*) thread;
@@ -4786,7 +4786,7 @@
       if (task != NULL) {
         thread->print_name_on_error(st, buf, buflen);
         st->print("  ");
-        task->print(st, NULL, true, true);
+        task->print(st, NULL, short_form, true);
       }
     }
   }
--- a/src/hotspot/share/runtime/thread.hpp	Thu Mar 07 00:23:45 2019 +0100
+++ b/src/hotspot/share/runtime/thread.hpp	Thu Mar 07 08:38:16 2019 +0100
@@ -2298,7 +2298,7 @@
   static void print_on_error(outputStream* st, Thread* current, char* buf, int buflen);
   static void print_on_error(Thread* this_thread, outputStream* st, Thread* current, char* buf,
                              int buflen, bool* found_current);
-  static void print_threads_compiling(outputStream* st, char* buf, int buflen);
+  static void print_threads_compiling(outputStream* st, char* buf, int buflen, bool short_form = false);
 
   // Get Java threads that are waiting to enter a monitor.
   static GrowableArray<JavaThread*>* get_pending_threads(ThreadsList * t_list,
--- a/src/hotspot/share/runtime/tieredThresholdPolicy.cpp	Thu Mar 07 00:23:45 2019 +0100
+++ b/src/hotspot/share/runtime/tieredThresholdPolicy.cpp	Thu Mar 07 08:38:16 2019 +0100
@@ -295,28 +295,24 @@
   for (CompileTask* task = compile_queue->first(); task != NULL;) {
     CompileTask* next_task = task->next();
     Method* method = task->method();
+    // If a method was unloaded or has been stale for some time, remove it from the queue.
+    // Blocking tasks and tasks submitted from whitebox API don't become stale
+    if (task->is_unloaded() || (task->can_become_stale() && is_stale(t, TieredCompileTaskTimeout, method) && !is_old(method))) {
+      if (!task->is_unloaded()) {
+        if (PrintTieredEvents) {
+          print_event(REMOVE_FROM_QUEUE, method, method, task->osr_bci(), (CompLevel) task->comp_level());
+        }
+        method->clear_queued_for_compilation();
+      }
+      compile_queue->remove_and_mark_stale(task);
+      task = next_task;
+      continue;
+    }
     update_rate(t, method);
-    if (max_task == NULL) {
+    if (max_task == NULL || compare_methods(method, max_method)) {
+      // Select a method with the highest rate
       max_task = task;
       max_method = method;
-    } else {
-      // If a method has been stale for some time, remove it from the queue.
-      // Blocking tasks and tasks submitted from whitebox API don't become stale
-      if (task->can_become_stale() && is_stale(t, TieredCompileTaskTimeout, method) && !is_old(method)) {
-        if (PrintTieredEvents) {
-          print_event(REMOVE_FROM_QUEUE, method, method, task->osr_bci(), (CompLevel)task->comp_level());
-        }
-        compile_queue->remove_and_mark_stale(task);
-        method->clear_queued_for_compilation();
-        task = next_task;
-        continue;
-      }
-
-      // Select a method with a higher rate
-      if (compare_methods(method, max_method)) {
-        max_task = task;
-        max_method = method;
-      }
     }
 
     if (task->is_blocking()) {
@@ -501,7 +497,7 @@
   }
 }
 
-// Check if this method has been stale from a given number of milliseconds.
+// Check if this method has been stale for a given number of milliseconds.
 // See select_task().
 bool TieredThresholdPolicy::is_stale(jlong t, jlong timeout, Method* m) {
   jlong delta_s = t - SafepointTracing::end_of_last_safepoint_epoch_ms();
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/hotspot/jtreg/compiler/classUnloading/methodUnloading/TestOverloadCompileQueues.java	Thu Mar 07 08:38:16 2019 +0100
@@ -0,0 +1,86 @@
+/*
+ * Copyright (c) 2019, 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.
+ */
+
+/*
+ * @test TestOverloadCompileQueues
+ * @bug 8163511
+ * @summary Test overloading the C1 and C2 compile queues with tasks.
+ * @run main/othervm -XX:-TieredCompilation -XX:CompileThreshold=2 -XX:CICompilerCount=1
+ *                   compiler.classUnloading.methodUnloading.TestOverloadCompileQueues
+ * @run main/othervm -XX:TieredCompileTaskTimeout=1000 -XX:CompileThresholdScaling=0.001 -XX:CICompilerCount=2
+ *                   compiler.classUnloading.methodUnloading.TestOverloadCompileQueues
+ */
+
+package compiler.classUnloading.methodUnloading;
+
+import java.lang.reflect.Method;
+import java.net.URL;
+import java.net.URLClassLoader;
+
+public class TestOverloadCompileQueues {
+    public static final int ITERS = 500; // Increase for longer stress testing
+
+    // Some methods to fill up the compile queue
+    public static void test0() { }
+    public static void test1() { }
+    public static void test2() { }
+    public static void test3() { }
+    public static void test4() { }
+    public static void test5() { }
+    public static void test6() { }
+    public static void test7() { }
+    public static void test8() { }
+    public static void test9() { }
+    public static void test10() { }
+    public static void test11() { }
+    public static void test12() { }
+    public static void test13() { }
+    public static void test14() { }
+    public static void test15() { }
+    public static void test16() { }
+    public static void test17() { }
+    public static void test18() { }
+    public static void test19() { }
+
+    public static void main(String[] args) throws Throwable {
+        Class<?> thisClass = TestOverloadCompileQueues.class;
+        ClassLoader defaultLoader = thisClass.getClassLoader();
+        URL classesDir = thisClass.getProtectionDomain().getCodeSource().getLocation();
+
+        for (int i = 0; i < ITERS; ++i) {
+            // Load test class with own class loader
+            URLClassLoader myLoader = URLClassLoader.newInstance(new URL[] {classesDir}, defaultLoader.getParent());
+            Class<?> testClass = Class.forName(thisClass.getCanonicalName(), true, myLoader);
+
+            // Execute all test methods to trigger compilation and fill up compile queue
+            for (int j = 1; j < 20; ++j) {
+                Method method = testClass.getDeclaredMethod("test" + j);
+                method.invoke(null);
+                method.invoke(null);
+            }
+
+            // Unload dead classes from ealier iterations
+            System.gc();
+        }
+    }
+}