8231081: TestMetadataRetention fails due to missing symbol id
authormgronlun
Thu, 24 Oct 2019 16:37:22 +0200
changeset 58786 7909763ad193
parent 58785 c6cbcc673cd3
child 58787 32d39d9525f9
8231081: TestMetadataRetention fails due to missing symbol id Reviewed-by: egahlin
src/hotspot/share/jfr/recorder/checkpoint/jfrCheckpointManager.cpp
src/hotspot/share/jfr/recorder/checkpoint/types/jfrType.cpp
src/hotspot/share/jfr/recorder/checkpoint/types/jfrType.hpp
src/hotspot/share/jfr/recorder/checkpoint/types/jfrTypeManager.cpp
src/hotspot/share/jfr/recorder/checkpoint/types/jfrTypeManager.hpp
src/hotspot/share/jfr/recorder/checkpoint/types/jfrTypeSet.cpp
src/hotspot/share/jfr/recorder/checkpoint/types/jfrTypeSet.hpp
src/hotspot/share/jfr/recorder/checkpoint/types/jfrTypeSetUtils.cpp
src/hotspot/share/jfr/recorder/checkpoint/types/jfrTypeSetUtils.hpp
test/jdk/jdk/jfr/jvm/TestClearStaleConstants.java
--- a/src/hotspot/share/jfr/recorder/checkpoint/jfrCheckpointManager.cpp	Thu Oct 24 16:28:51 2019 +0200
+++ b/src/hotspot/share/jfr/recorder/checkpoint/jfrCheckpointManager.cpp	Thu Oct 24 16:37:22 2019 +0200
@@ -24,15 +24,18 @@
 
 #include "precompiled.hpp"
 #include "classfile/javaClasses.inline.hpp"
-#include "jfr/recorder/jfrRecorder.hpp"
+#include "jfr/leakprofiler/checkpoint/objectSampleCheckpoint.hpp"
+#include "jfr/leakprofiler/leakProfiler.hpp"
 #include "jfr/recorder/checkpoint/jfrCheckpointManager.hpp"
 #include "jfr/recorder/checkpoint/jfrCheckpointWriter.hpp"
 #include "jfr/recorder/checkpoint/types/jfrTypeManager.hpp"
+#include "jfr/recorder/checkpoint/types/jfrTypeSet.hpp"
 #include "jfr/recorder/checkpoint/types/traceid/jfrTraceIdEpoch.hpp"
+#include "jfr/recorder/jfrRecorder.hpp"
+#include "jfr/recorder/repository/jfrChunkWriter.hpp"
 #include "jfr/recorder/service/jfrOptionSet.hpp"
 #include "jfr/recorder/storage/jfrMemorySpace.inline.hpp"
 #include "jfr/recorder/storage/jfrStorageUtils.inline.hpp"
-#include "jfr/recorder/repository/jfrChunkWriter.hpp"
 #include "jfr/utilities/jfrBigEndian.hpp"
 #include "jfr/utilities/jfrTypes.hpp"
 #include "logging/log.hpp"
@@ -81,7 +84,7 @@
   if (_lock != NULL) {
     delete _lock;
   }
-  JfrTypeManager::clear();
+  JfrTypeManager::destroy();
 }
 
 static const size_t unlimited_mspace_size = 0;
@@ -332,6 +335,7 @@
 
 typedef DiscardOp<DefaultDiscarder<JfrBuffer> > DiscardOperation;
 size_t JfrCheckpointManager::clear() {
+  JfrTypeSet::clear();
   DiscardOperation discarder(mutexed); // mutexed discard mode
   process_free_list(discarder, _free_list_mspace);
   process_free_list(discarder, _epoch_transition_mspace);
@@ -353,12 +357,34 @@
 }
 
 void JfrCheckpointManager::write_type_set() {
-  JfrTypeManager::write_type_set();
+  assert(!SafepointSynchronize::is_at_safepoint(), "invariant");
+  // can safepoint here
+  MutexLocker cld_lock(ClassLoaderDataGraph_lock);
+  MutexLocker module_lock(Module_lock);
+  if (!LeakProfiler::is_running()) {
+    JfrCheckpointWriter writer(true, true, Thread::current());
+    JfrTypeSet::serialize(&writer, NULL, false);
+    return;
+  }
+  Thread* const t = Thread::current();
+  JfrCheckpointWriter leakp_writer(false, true, t);
+  JfrCheckpointWriter writer(false, true, t);
+  JfrTypeSet::serialize(&writer, &leakp_writer, false);
+  ObjectSampleCheckpoint::on_type_set(leakp_writer);
 }
 
 void JfrCheckpointManager::write_type_set_for_unloaded_classes() {
   assert_locked_or_safepoint(ClassLoaderDataGraph_lock);
-  JfrTypeManager::write_type_set_for_unloaded_classes();
+  JfrCheckpointWriter writer(false, true, Thread::current());
+  const JfrCheckpointContext ctx = writer.context();
+  JfrTypeSet::serialize(&writer, NULL, true);
+  if (LeakProfiler::is_running()) {
+    ObjectSampleCheckpoint::on_type_set_unload(writer);
+  }
+  if (!JfrRecorder::is_recording()) {
+    // discard by rewind
+    writer.set_context(ctx);
+  }
 }
 
 void JfrCheckpointManager::create_thread_blob(JavaThread* jt) {
--- a/src/hotspot/share/jfr/recorder/checkpoint/types/jfrType.cpp	Thu Oct 24 16:28:51 2019 +0200
+++ b/src/hotspot/share/jfr/recorder/checkpoint/types/jfrType.cpp	Thu Oct 24 16:37:22 2019 +0200
@@ -36,7 +36,6 @@
 #include "jfr/recorder/jfrRecorder.hpp"
 #include "jfr/recorder/checkpoint/types/jfrThreadGroup.hpp"
 #include "jfr/recorder/checkpoint/types/jfrThreadState.hpp"
-#include "jfr/recorder/checkpoint/types/jfrTypeSet.hpp"
 #include "jfr/support/jfrThreadLocal.hpp"
 #include "jfr/writers/jfrJavaEventWriter.hpp"
 #include "memory/metaspaceGCThresholdUpdater.hpp"
@@ -271,30 +270,6 @@
   }
 }
 
-class TypeSetSerialization {
- private:
-  JfrCheckpointWriter* _leakp_writer;
-  bool _class_unload;
- public:
-  TypeSetSerialization(bool class_unload, JfrCheckpointWriter* leakp_writer = NULL) :
-    _leakp_writer(leakp_writer), _class_unload(class_unload) {}
-  void write(JfrCheckpointWriter& writer) {
-    JfrTypeSet::serialize(&writer, _leakp_writer, _class_unload);
-  }
-};
-
-void ClassUnloadTypeSet::serialize(JfrCheckpointWriter& writer) {
-  TypeSetSerialization type_set(true);
-  type_set.write(writer);
-};
-
-TypeSet::TypeSet(JfrCheckpointWriter* leakp_writer) : _leakp_writer(leakp_writer) {}
-
-void TypeSet::serialize(JfrCheckpointWriter& writer) {
-  TypeSetSerialization type_set(false, _leakp_writer);
-  type_set.write(writer);
-};
-
 void ThreadStateConstant::serialize(JfrCheckpointWriter& writer) {
   JfrThreadState::serialize(writer);
 }
--- a/src/hotspot/share/jfr/recorder/checkpoint/types/jfrType.hpp	Thu Oct 24 16:28:51 2019 +0200
+++ b/src/hotspot/share/jfr/recorder/checkpoint/types/jfrType.hpp	Thu Oct 24 16:37:22 2019 +0200
@@ -37,11 +37,6 @@
   void serialize(JfrCheckpointWriter& writer);
 };
 
-class ClassUnloadTypeSet : public JfrSerializer {
- public:
-  void serialize(JfrCheckpointWriter& writer);
-};
-
 class FlagValueOriginConstant : public JfrSerializer {
  public:
   void serialize(JfrCheckpointWriter& writer);
@@ -107,14 +102,6 @@
   void serialize(JfrCheckpointWriter& writer);
 };
 
-class TypeSet : public JfrSerializer {
- private:
-  JfrCheckpointWriter* _leakp_writer;
- public:
-  explicit TypeSet(JfrCheckpointWriter* leakp_writer = NULL);
-  void serialize(JfrCheckpointWriter& writer);
-};
-
 class ThreadStateConstant : public JfrSerializer {
  public:
   void serialize(JfrCheckpointWriter& writer);
--- a/src/hotspot/share/jfr/recorder/checkpoint/types/jfrTypeManager.cpp	Thu Oct 24 16:28:51 2019 +0200
+++ b/src/hotspot/share/jfr/recorder/checkpoint/types/jfrTypeManager.cpp	Thu Oct 24 16:37:22 2019 +0200
@@ -23,9 +23,6 @@
  */
 
 #include "precompiled.hpp"
-#include "jfr/jfr.hpp"
-#include "jfr/leakprofiler/leakProfiler.hpp"
-#include "jfr/leakprofiler/checkpoint/objectSampleCheckpoint.hpp"
 #include "jfr/metadata/jfrSerializer.hpp"
 #include "jfr/recorder/checkpoint/jfrCheckpointWriter.hpp"
 #include "jfr/recorder/checkpoint/types/jfrType.hpp"
@@ -35,9 +32,9 @@
 #include "memory/resourceArea.hpp"
 #include "runtime/handles.inline.hpp"
 #include "runtime/safepoint.hpp"
+#include "runtime/semaphore.hpp"
 #include "runtime/thread.inline.hpp"
 #include "utilities/exceptions.hpp"
-#include "runtime/semaphore.hpp"
 
 class JfrSerializerRegistration : public JfrCHeapObj {
  private:
@@ -120,7 +117,7 @@
 static List types;
 static List safepoint_types;
 
-void JfrTypeManager::clear() {
+void JfrTypeManager::destroy() {
   SerializerRegistrationGuard guard;
   Iterator iter(types);
   JfrSerializerRegistration* registration;
@@ -152,39 +149,6 @@
   }
 }
 
-void JfrTypeManager::write_type_set() {
-  assert(!SafepointSynchronize::is_at_safepoint(), "invariant");
-  // can safepoint here
-  MutexLocker cld_lock(ClassLoaderDataGraph_lock);
-  MutexLocker module_lock(Module_lock);
-  if (!LeakProfiler::is_running()) {
-    JfrCheckpointWriter writer(true, true, Thread::current());
-    TypeSet set;
-    set.serialize(writer);
-    return;
-  }
-  JfrCheckpointWriter leakp_writer(false, true, Thread::current());
-  JfrCheckpointWriter writer(false, true, Thread::current());
-  TypeSet set(&leakp_writer);
-  set.serialize(writer);
-  ObjectSampleCheckpoint::on_type_set(leakp_writer);
-}
-
-void JfrTypeManager::write_type_set_for_unloaded_classes() {
-  assert_locked_or_safepoint(ClassLoaderDataGraph_lock);
-  JfrCheckpointWriter writer(false, true, Thread::current());
-  const JfrCheckpointContext ctx = writer.context();
-  ClassUnloadTypeSet class_unload_set;
-  class_unload_set.serialize(writer);
-  if (LeakProfiler::is_running()) {
-    ObjectSampleCheckpoint::on_type_set_unload(writer);
-  }
-  if (!Jfr::is_recording()) {
-    // discard anything written
-    writer.set_context(ctx);
-  }
-}
-
 void JfrTypeManager::create_thread_blob(JavaThread* jt) {
   assert(jt != NULL, "invariant");
   ResourceMark rm(jt);
--- a/src/hotspot/share/jfr/recorder/checkpoint/types/jfrTypeManager.hpp	Thu Oct 24 16:28:51 2019 +0200
+++ b/src/hotspot/share/jfr/recorder/checkpoint/types/jfrTypeManager.hpp	Thu Oct 24 16:37:22 2019 +0200
@@ -32,11 +32,9 @@
 class JfrTypeManager : public AllStatic {
  public:
   static bool initialize();
-  static void clear();
+  static void destroy();
   static void write_types(JfrCheckpointWriter& writer);
   static void write_safepoint_types(JfrCheckpointWriter& writer);
-  static void write_type_set();
-  static void write_type_set_for_unloaded_classes();
   static void create_thread_blob(JavaThread* jt);
   static void write_thread_checkpoint(JavaThread* jt);
 };
--- a/src/hotspot/share/jfr/recorder/checkpoint/types/jfrTypeSet.cpp	Thu Oct 24 16:28:51 2019 +0200
+++ b/src/hotspot/share/jfr/recorder/checkpoint/types/jfrTypeSet.cpp	Thu Oct 24 16:37:22 2019 +0200
@@ -204,9 +204,14 @@
   return write_klass(writer, klass, true);
 }
 
+static bool is_implied(const Klass* klass) {
+  assert(klass != NULL, "invariant");
+  return klass->is_subclass_of(SystemDictionary::ClassLoader_klass()) || klass == SystemDictionary::Object_klass();
+}
+
 static void do_implied(Klass* klass) {
   assert(klass != NULL, "invariant");
-  if (klass->is_subclass_of(SystemDictionary::ClassLoader_klass()) || klass == SystemDictionary::Object_klass()) {
+  if (is_implied(klass)) {
     if (_leakp_writer != NULL) {
       SET_LEAKP(klass);
     }
@@ -259,6 +264,16 @@
 typedef CompositeFunctor<KlassPtr, KlassWriter, KlassArtifactRegistrator> KlassWriterRegistration;
 typedef JfrArtifactCallbackHost<KlassPtr, KlassWriterRegistration> KlassCallback;
 
+template <>
+class LeakPredicate<const Klass*> {
+public:
+  LeakPredicate(bool class_unload) {}
+  bool operator()(const Klass* klass) {
+    assert(klass != NULL, "invariant");
+    return IS_LEAKP(klass) || is_implied(klass);
+  }
+};
+
 typedef LeakPredicate<KlassPtr> LeakKlassPredicate;
 typedef JfrPredicatedTypeWriterImplHost<KlassPtr, LeakKlassPredicate, write__klass__leakp> LeakKlassWriterImpl;
 typedef JfrTypeWriterHost<LeakKlassWriterImpl, TYPE_CLASS> LeakKlassWriter;
@@ -809,6 +824,12 @@
   _artifacts->tally(sw);
 }
 
+static bool clear_artifacts = false;
+
+void JfrTypeSet::clear() {
+  clear_artifacts = true;
+}
+
 typedef Wrapper<KlassPtr, ClearArtifact> ClearKlassBits;
 typedef Wrapper<MethodPtr, ClearArtifact> ClearMethodFlag;
 typedef MethodIteratorHost<ClearMethodFlag, ClearKlassBits, false> ClearKlassAndMethods;
@@ -820,7 +841,7 @@
     assert(_writer != NULL, "invariant");
     ClearKlassAndMethods clear(_writer);
     _artifacts->iterate_klasses(clear);
-    _artifacts->clear();
+    JfrTypeSet::clear();
     ++checkpoint_id;
   }
   return total_count;
@@ -833,8 +854,9 @@
   if (_artifacts == NULL) {
     _artifacts = new JfrArtifactSet(class_unload);
   } else {
-    _artifacts->initialize(class_unload);
+    _artifacts->initialize(class_unload, clear_artifacts);
   }
+  clear_artifacts = false;
   assert(_artifacts != NULL, "invariant");
   assert(!_artifacts->has_klass_entries(), "invariant");
 }
--- a/src/hotspot/share/jfr/recorder/checkpoint/types/jfrTypeSet.hpp	Thu Oct 24 16:28:51 2019 +0200
+++ b/src/hotspot/share/jfr/recorder/checkpoint/types/jfrTypeSet.hpp	Thu Oct 24 16:37:22 2019 +0200
@@ -31,6 +31,7 @@
 
 class JfrTypeSet : AllStatic {
  public:
+  static void clear();
   static size_t serialize(JfrCheckpointWriter* writer, JfrCheckpointWriter* leakp_writer, bool class_unload);
 };
 
--- a/src/hotspot/share/jfr/recorder/checkpoint/types/jfrTypeSetUtils.cpp	Thu Oct 24 16:28:51 2019 +0200
+++ b/src/hotspot/share/jfr/recorder/checkpoint/types/jfrTypeSetUtils.cpp	Thu Oct 24 16:37:22 2019 +0200
@@ -35,6 +35,8 @@
   _cstring_table(new CStringTable(this)),
   _sym_list(NULL),
   _cstring_list(NULL),
+  _sym_query(NULL),
+  _cstring_query(NULL),
   _symbol_id_counter(1),
   _class_unload(false) {
   assert(_sym_table != NULL, "invariant");
@@ -66,9 +68,11 @@
   assert(!_cstring_table->has_entries(), "invariant");
 
   _sym_list = NULL;
-  _cstring_list = NULL;
   _symbol_id_counter = 1;
 
+  _sym_query = NULL;
+  _cstring_query = NULL;
+
   assert(bootstrap != NULL, "invariant");
   bootstrap->reset();
   _cstring_list = bootstrap;
@@ -88,10 +92,10 @@
 }
 
 bool JfrSymbolId::on_equals(uintptr_t hash, const SymbolEntry* entry) {
-  // query might be NULL
   assert(entry != NULL, "invariant");
   assert(entry->hash() == hash, "invariant");
-  return true;
+  assert(_sym_query != NULL, "invariant");
+  return _sym_query == entry->literal();
 }
 
 void JfrSymbolId::on_unlink(const SymbolEntry* entry) {
@@ -99,18 +103,36 @@
   const_cast<Symbol*>(entry->literal())->decrement_refcount();
 }
 
+static const char* resource_to_cstring(const char* resource_str) {
+  assert(resource_str != NULL, "invariant");
+  const size_t length = strlen(resource_str);
+  char* const c_string = JfrCHeapObj::new_array<char>(length + 1);
+  assert(c_string != NULL, "invariant");
+  strncpy(c_string, resource_str, length + 1);
+  return c_string;
+}
+
 void JfrSymbolId::on_link(const CStringEntry* entry) {
   assert(entry != NULL, "invariant");
   assert(entry->id() == 0, "invariant");
   entry->set_id(++_symbol_id_counter);
+  const_cast<CStringEntry*>(entry)->set_literal(resource_to_cstring(entry->literal()));
   entry->set_list_next(_cstring_list);
   _cstring_list = entry;
 }
 
+static bool string_compare(const char* query, const char* candidate) {
+  assert(query != NULL, "invariant");
+  assert(candidate != NULL, "invariant");
+  const size_t length = strlen(query);
+  return strncmp(query, candidate, length) == 0;
+}
+
 bool JfrSymbolId::on_equals(uintptr_t hash, const CStringEntry* entry) {
   assert(entry != NULL, "invariant");
   assert(entry->hash() == hash, "invariant");
-  return true;
+  assert(_cstring_query != NULL, "invariant");
+  return string_compare(_cstring_query, entry->literal());
 }
 
 void JfrSymbolId::on_unlink(const CStringEntry* entry) {
@@ -131,16 +153,10 @@
   return mark((uintptr_t)symbol->identity_hash(), symbol, leakp);
 }
 
-static unsigned int last_symbol_hash = 0;
-static traceid last_symbol_id = 0;
-
 traceid JfrSymbolId::mark(uintptr_t hash, const Symbol* data, bool leakp) {
   assert(data != NULL, "invariant");
   assert(_sym_table != NULL, "invariant");
-  if (hash == last_symbol_hash) {
-    assert(last_symbol_id != 0, "invariant");
-    return last_symbol_id;
-  }
+  _sym_query = data;
   const SymbolEntry& entry = _sym_table->lookup_put(hash, data);
   if (_class_unload) {
     entry.set_unloading();
@@ -148,21 +164,13 @@
   if (leakp) {
     entry.set_leakp();
   }
-  last_symbol_hash = hash;
-  last_symbol_id = entry.id();
-  return last_symbol_id;
+  return entry.id();
 }
 
-static unsigned int last_cstring_hash = 0;
-static traceid last_cstring_id = 0;
-
 traceid JfrSymbolId::mark(uintptr_t hash, const char* str, bool leakp) {
   assert(str != NULL, "invariant");
   assert(_cstring_table != NULL, "invariant");
-  if (hash == last_cstring_hash) {
-    assert(last_cstring_id != 0, "invariant");
-    return last_cstring_id;
-  }
+  _cstring_query = str;
   const CStringEntry& entry = _cstring_table->lookup_put(hash, str);
   if (_class_unload) {
     entry.set_unloading();
@@ -170,9 +178,7 @@
   if (leakp) {
     entry.set_leakp();
   }
-  last_cstring_hash = hash;
-  last_cstring_id = entry.id();
-  return last_cstring_id;
+  return entry.id();
 }
 
 /*
@@ -202,7 +208,7 @@
   sprintf(hash_buf, "/" UINTX_FORMAT, hash);
   const size_t hash_len = strlen(hash_buf);
   const size_t result_len = ik->name()->utf8_length();
-  anonymous_symbol = JfrCHeapObj::new_array<char>(result_len + hash_len + 1);
+  anonymous_symbol = NEW_RESOURCE_ARRAY(char, result_len + hash_len + 1);
   ik->name()->as_klass_external_name(anonymous_symbol, (int)result_len + 1);
   assert(strlen(anonymous_symbol) == result_len, "invariant");
   strcpy(anonymous_symbol + result_len, hash_buf);
@@ -215,21 +221,12 @@
   return k->is_instance_klass() && ((const InstanceKlass*)k)->is_unsafe_anonymous();
 }
 
-static unsigned int last_anonymous_hash = 0;
-static traceid last_anonymous_id = 0;
-
 traceid JfrSymbolId::mark_unsafe_anonymous_klass_name(const InstanceKlass* ik, bool leakp) {
   assert(ik != NULL, "invariant");
   assert(ik->is_unsafe_anonymous(), "invariant");
   const uintptr_t hash = unsafe_anonymous_klass_name_hash(ik);
-  if (hash == last_anonymous_hash) {
-    assert(last_anonymous_id != 0, "invariant");
-    return last_anonymous_id;
-  }
-  last_anonymous_hash = hash;
-  const CStringEntry* const entry = _cstring_table->lookup_only(hash);
-  last_anonymous_id = entry != NULL ? entry->id() : mark(hash, create_unsafe_anonymous_klass_symbol(ik, hash), leakp);
-  return last_anonymous_id;
+  const char* const anonymous_klass_symbol = create_unsafe_anonymous_klass_symbol(ik, hash);
+  return mark(hash, anonymous_klass_symbol, leakp);
 }
 
 traceid JfrSymbolId::mark(const Klass* k, bool leakp) {
@@ -249,23 +246,20 @@
   return symbol_id;
 }
 
-static void reset_symbol_caches() {
-  last_anonymous_hash = 0;
-  last_symbol_hash = 0;
-  last_cstring_hash = 0;
-}
-
 JfrArtifactSet::JfrArtifactSet(bool class_unload) : _symbol_id(new JfrSymbolId()),
-                                                     _klass_list(NULL),
-                                                     _total_count(0) {
+                                                    _klass_list(NULL),
+                                                    _total_count(0) {
   initialize(class_unload);
   assert(_klass_list != NULL, "invariant");
 }
 
 static const size_t initial_class_list_size = 200;
 
-void JfrArtifactSet::initialize(bool class_unload) {
+void JfrArtifactSet::initialize(bool class_unload, bool clear /* false */) {
   assert(_symbol_id != NULL, "invariant");
+  if (clear) {
+    _symbol_id->clear();
+  }
   _symbol_id->set_class_unload(class_unload);
   _total_count = 0;
   // resource allocation
@@ -273,13 +267,8 @@
 }
 
 JfrArtifactSet::~JfrArtifactSet() {
-  clear();
+  _symbol_id->clear();
   delete _symbol_id;
-}
-
-void JfrArtifactSet::clear() {
-  reset_symbol_caches();
-  _symbol_id->clear();
   // _klass_list will be cleared by a ResourceMark
 }
 
--- a/src/hotspot/share/jfr/recorder/checkpoint/types/jfrTypeSetUtils.hpp	Thu Oct 24 16:28:51 2019 +0200
+++ b/src/hotspot/share/jfr/recorder/checkpoint/types/jfrTypeSetUtils.hpp	Thu Oct 24 16:37:22 2019 +0200
@@ -222,6 +222,8 @@
   CStringTable* _cstring_table;
   const SymbolEntry* _sym_list;
   const CStringEntry* _cstring_list;
+  const Symbol* _sym_query;
+  const char* _cstring_query;
   traceid _symbol_id_counter;
   bool _class_unload;
 
@@ -300,9 +302,7 @@
   ~JfrArtifactSet();
 
   // caller needs ResourceMark
-  void initialize(bool class_unload);
-  void clear();
-
+  void initialize(bool class_unload, bool clear = false);
 
   traceid mark(uintptr_t hash, const Symbol* sym, bool leakp);
   traceid mark(const Klass* klass, bool leakp);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/jdk/jfr/jvm/TestClearStaleConstants.java	Thu Oct 24 16:37:22 2019 +0200
@@ -0,0 +1,115 @@
+/*
+ * 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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.
+ */
+
+package jdk.jfr.jvm;
+
+import java.time.Duration;
+import java.util.List;
+
+import jdk.jfr.consumer.RecordedClass;
+import jdk.jfr.consumer.RecordedClassLoader;
+import jdk.jfr.consumer.RecordedEvent;
+import jdk.jfr.internal.JVM;
+import jdk.jfr.Recording;
+import jdk.test.lib.Asserts;
+import jdk.test.lib.jfr.EventNames;
+import jdk.test.lib.jfr.Events;
+import jdk.test.lib.jfr.TestClassLoader;
+
+/**
+ * @test
+ * @bug 8231081
+ * @key jfr
+ * @requires vm.hasJFR
+ * @modules jdk.jfr/jdk.jfr.internal
+ * @library /test/lib /test/jdk
+ * @run main/othervm -Xlog:class+unload -Xlog:gc -Xmx16m jdk.jfr.jvm.TestClearStaleConstants
+ */
+
+/**
+ * System.gc() will trigger class unloading if -XX:+ExplicitGCInvokesConcurrent is NOT set.
+ * If this flag is set G1 will never unload classes on System.gc() and
+ * As far as the "jfr" key guarantees no VM flags are set from the outside
+ * it should be enough with System.gc().
+ */
+public final class TestClearStaleConstants {
+    static class MyClass {
+    }
+    private final static String TEST_CLASS_NAME = "jdk.jfr.jvm.TestClearStaleConstants$MyClass";
+    private final static String EVENT_NAME = EventNames.ClassDefine;
+
+    // to prevent the compiler to optimize away all unread writes
+    public static TestClassLoader firstClassLoader;
+    public static TestClassLoader secondClassLoader;
+
+    public static void main(String... args) throws Exception {
+        firstClassLoader = new TestClassLoader();
+        // define a  class using a class loader under a recording
+        Class<?> clz = recordClassDefinition(firstClassLoader);
+        JVM jvm = JVM.getJVM();
+        // we will now tag the defined and loaded clz as being in use (no recordings are running here)
+        jvm.getClassIdNonIntrinsic(clz);
+        // null out for unload to occur
+        firstClassLoader = null;
+        clz = null;
+        // provoke unload
+        System.gc();
+        // try to define another class _with the same name_ using a different class loader
+        secondClassLoader = new TestClassLoader();
+        // this will throw a NPE for 8231081 because it will reuse the same class name
+        // that symbol was  marked as already serialized by the unload, but since no recordings were running
+        // it was not written to any chunk. This creates a reference to a non-existing symbol, leading to an NPE (no symbol at the expected location).
+        recordClassDefinition(secondClassLoader);
+    }
+
+    private static Class<?> recordClassDefinition(TestClassLoader classLoader) throws Exception  {
+        try (Recording recording = new Recording())  {
+            recording.enable(EVENT_NAME);
+            recording.start();
+            Class<?> clz = classLoader.loadClass(TEST_CLASS_NAME);
+            recording.stop();
+            assertClassDefineEvent(recording);
+            return clz;
+        }
+    }
+
+    private static void assertClassDefineEvent(Recording recording) throws Exception {
+        boolean isAnyFound = false;
+        for (RecordedEvent event : Events.fromRecording(recording)) {
+            System.out.println(event);
+            RecordedClass definedClass = event.getValue("definedClass");
+            if (TEST_CLASS_NAME.equals(definedClass.getName())) {
+                RecordedClassLoader definingClassLoader = definedClass.getClassLoader();
+                String definingName = definingClassLoader.getType().getName();
+                String testName = TestClassLoader.class.getName();
+                String errorMsg = "Expected " + testName + ", got " + definingName;
+                Asserts.assertEquals(testName, definingName, errorMsg);
+                Asserts.assertFalse(isAnyFound, "Found more than 1 event");
+                isAnyFound = true;
+            }
+        }
+        Asserts.assertTrue(isAnyFound, "No events found");
+    }
+}