# HG changeset patch # User mgronlun # Date 1571927842 -7200 # Node ID 7909763ad19377a339523fa98695cf86f9f25321 # Parent c6cbcc673cd3a0d271e06bc05b6c224d4c393847 8231081: TestMetadataRetention fails due to missing symbol id Reviewed-by: egahlin diff -r c6cbcc673cd3 -r 7909763ad193 src/hotspot/share/jfr/recorder/checkpoint/jfrCheckpointManager.cpp --- 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 > 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) { diff -r c6cbcc673cd3 -r 7909763ad193 src/hotspot/share/jfr/recorder/checkpoint/types/jfrType.cpp --- 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); } diff -r c6cbcc673cd3 -r 7909763ad193 src/hotspot/share/jfr/recorder/checkpoint/types/jfrType.hpp --- 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); diff -r c6cbcc673cd3 -r 7909763ad193 src/hotspot/share/jfr/recorder/checkpoint/types/jfrTypeManager.cpp --- 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); diff -r c6cbcc673cd3 -r 7909763ad193 src/hotspot/share/jfr/recorder/checkpoint/types/jfrTypeManager.hpp --- 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); }; diff -r c6cbcc673cd3 -r 7909763ad193 src/hotspot/share/jfr/recorder/checkpoint/types/jfrTypeSet.cpp --- 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 KlassWriterRegistration; typedef JfrArtifactCallbackHost KlassCallback; +template <> +class LeakPredicate { +public: + LeakPredicate(bool class_unload) {} + bool operator()(const Klass* klass) { + assert(klass != NULL, "invariant"); + return IS_LEAKP(klass) || is_implied(klass); + } +}; + typedef LeakPredicate LeakKlassPredicate; typedef JfrPredicatedTypeWriterImplHost LeakKlassWriterImpl; typedef JfrTypeWriterHost LeakKlassWriter; @@ -809,6 +824,12 @@ _artifacts->tally(sw); } +static bool clear_artifacts = false; + +void JfrTypeSet::clear() { + clear_artifacts = true; +} + typedef Wrapper ClearKlassBits; typedef Wrapper ClearMethodFlag; typedef MethodIteratorHost 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"); } diff -r c6cbcc673cd3 -r 7909763ad193 src/hotspot/share/jfr/recorder/checkpoint/types/jfrTypeSet.hpp --- 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); }; diff -r c6cbcc673cd3 -r 7909763ad193 src/hotspot/share/jfr/recorder/checkpoint/types/jfrTypeSetUtils.cpp --- 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(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(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(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(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 } diff -r c6cbcc673cd3 -r 7909763ad193 src/hotspot/share/jfr/recorder/checkpoint/types/jfrTypeSetUtils.hpp --- 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); diff -r c6cbcc673cd3 -r 7909763ad193 test/jdk/jdk/jfr/jvm/TestClearStaleConstants.java --- /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"); + } +}