From 046f5554e4638e51a84f5b0542a5fd247e1d2719 Mon Sep 17 00:00:00 2001 From: jbachorik Date: Tue, 19 Apr 2022 14:18:06 +0000 Subject: [PATCH] Backport 72a976ef05fc2c62657920a560a0abc60b27c852 --- make/data/hotspot-symbols/symbols-unix | 1 + .../share/classfile/systemDictionary.cpp | 3 +- src/hotspot/share/include/jvm.h | 6 + src/hotspot/share/jfr/jni/jfrJavaSupport.cpp | 51 +-- src/hotspot/share/jfr/jni/jfrJavaSupport.hpp | 5 +- src/hotspot/share/jfr/metadata/metadata.xml | 7 + .../periodic/jfrFinalizerStatisticsEvent.cpp | 131 ++++++++ .../periodic/jfrFinalizerStatisticsEvent.hpp | 38 +++ .../share/jfr/periodic/jfrPeriodic.cpp | 14 +- .../recorder/checkpoint/types/jfrTypeSet.cpp | 91 +++-- .../checkpoint/types/jfrTypeSetUtils.cpp | 258 ++------------ .../checkpoint/types/jfrTypeSetUtils.hpp | 112 +------ .../share/jfr/recorder/service/jfrPostBox.cpp | 8 +- .../service/jfrRecorderThreadLoop.cpp | 21 +- .../share/jfr/support/jfrKlassUnloading.cpp | 16 + .../share/jfr/support/jfrSymbolTable.cpp | 316 ++++++++++++++++++ .../share/jfr/support/jfrSymbolTable.hpp | 148 ++++++++ src/hotspot/share/logging/logTag.hpp | 1 + src/hotspot/share/oops/instanceKlass.cpp | 5 +- src/hotspot/share/prims/jvm.cpp | 10 + src/hotspot/share/runtime/serviceThread.cpp | 7 + .../share/services/classLoadingService.cpp | 124 ++++--- .../share/services/classLoadingService.hpp | 67 +--- .../share/services/finalizerService.cpp | 313 +++++++++++++++++ .../share/services/finalizerService.hpp | 68 ++++ src/hotspot/share/services/management.cpp | 19 ++ src/hotspot/share/services/management.hpp | 16 +- src/hotspot/share/utilities/macros.hpp | 2 + .../classes/java/lang/ref/Finalizer.java | 3 + .../share/native/libjava/Finalizer.c | 35 ++ src/jdk.jfr/share/conf/jfr/default.jfc | 5 + src/jdk.jfr/share/conf/jfr/profile.jfc | 5 + .../runtime/TestFinalizerStatisticsEvent.java | 129 +++++++ test/lib/jdk/test/lib/jfr/EventNames.java | 1 + .../lib/jdk/test/lib/jfr/TestClassLoader.java | 9 +- 35 files changed, 1507 insertions(+), 538 deletions(-) create mode 100644 src/hotspot/share/jfr/periodic/jfrFinalizerStatisticsEvent.cpp create mode 100644 src/hotspot/share/jfr/periodic/jfrFinalizerStatisticsEvent.hpp create mode 100644 src/hotspot/share/jfr/support/jfrSymbolTable.cpp create mode 100644 src/hotspot/share/jfr/support/jfrSymbolTable.hpp create mode 100644 src/hotspot/share/services/finalizerService.cpp create mode 100644 src/hotspot/share/services/finalizerService.hpp create mode 100644 src/java.base/share/native/libjava/Finalizer.c create mode 100644 test/jdk/jdk/jfr/event/runtime/TestFinalizerStatisticsEvent.java diff --git a/make/data/hotspot-symbols/symbols-unix b/make/data/hotspot-symbols/symbols-unix index a34732739d3..d735f61b3a4 100644 --- a/make/data/hotspot-symbols/symbols-unix +++ b/make/data/hotspot-symbols/symbols-unix @@ -183,6 +183,7 @@ JVM_ReferenceRefersTo JVM_RegisterLambdaProxyClassForArchiving JVM_RegisterSignal JVM_ReleaseUTF +JVM_ReportFinalizationComplete JVM_ResumeThread JVM_SetArrayElement JVM_SetClassSigners diff --git a/src/hotspot/share/classfile/systemDictionary.cpp b/src/hotspot/share/classfile/systemDictionary.cpp index 51b767acf03..d4e7a474a02 100644 --- a/src/hotspot/share/classfile/systemDictionary.cpp +++ b/src/hotspot/share/classfile/systemDictionary.cpp @@ -77,6 +77,7 @@ #include "runtime/signature.hpp" #include "services/classLoadingService.hpp" #include "services/diagnosticCommand.hpp" +#include "services/finalizerService.hpp" #include "services/threadService.hpp" #include "utilities/macros.hpp" #include "utilities/utf8.hpp" @@ -1603,7 +1604,7 @@ bool SystemDictionary::do_unloading(GCTimer* gc_timer) { if (unloading_occurred) { MutexLocker ml2(is_concurrent ? Module_lock : NULL); JFR_ONLY(Jfr::on_unloading_classes();) - + MANAGEMENT_ONLY(FinalizerService::purge_unloaded();) MutexLocker ml1(is_concurrent ? SystemDictionary_lock : NULL); ClassLoaderDataGraph::clean_module_and_package_info(); constraints()->purge_loader_constraints(); diff --git a/src/hotspot/share/include/jvm.h b/src/hotspot/share/include/jvm.h index f12a4bec3d0..7783b00841d 100644 --- a/src/hotspot/share/include/jvm.h +++ b/src/hotspot/share/include/jvm.h @@ -753,6 +753,12 @@ JVM_AssertionStatusDirectives(JNIEnv *env, jclass unused); JNIEXPORT jboolean JNICALL JVM_SupportsCX8(void); +/* + * java.lang.ref.Finalizer + */ +JNIEXPORT void JNICALL +JVM_ReportFinalizationComplete(JNIEnv *env, jobject finalizee); + /************************************************************************* PART 2: Support for the Verifier and Class File Format Checker ************************************************************************/ diff --git a/src/hotspot/share/jfr/jni/jfrJavaSupport.cpp b/src/hotspot/share/jfr/jni/jfrJavaSupport.cpp index 861dad66d02..6553b276fbe 100644 --- a/src/hotspot/share/jfr/jni/jfrJavaSupport.cpp +++ b/src/hotspot/share/jfr/jni/jfrJavaSupport.cpp @@ -383,7 +383,7 @@ static void read_specialized_field(JavaValue* result, const Handle& h_oop, field } } -static bool find_field(InstanceKlass* ik, +static bool find_field(const InstanceKlass* ik, Symbol* name_symbol, Symbol* signature_symbol, fieldDescriptor* fd, @@ -395,29 +395,32 @@ static bool find_field(InstanceKlass* ik, return ik->find_local_field(name_symbol, signature_symbol, fd); } -static void lookup_field(JfrJavaArguments* args, InstanceKlass* klass, fieldDescriptor* fd, bool static_field) { +static void lookup_field(JfrJavaArguments* args, const InstanceKlass* ik, fieldDescriptor* fd, bool static_field) { assert(args != NULL, "invariant"); - assert(klass != NULL, "invariant"); - assert(klass->is_initialized(), "invariant"); + assert(ik != NULL, "invariant"); + assert(ik->is_initialized(), "invariant"); assert(fd != NULL, "invariant"); - find_field(klass, args->name(), args->signature(), fd, static_field, true); + find_field(ik, args->name(), args->signature(), fd, static_field, true); +} + +static void read_field(JfrJavaArguments* args, JavaValue* result, Thread* thread) { + const bool static_field = !args->has_receiver(); + fieldDescriptor fd; + const InstanceKlass* const ik = static_cast(args->klass()); + lookup_field(args, ik, &fd, static_field); + assert(fd.offset() > 0, "invariant"); + HandleMark hm(thread); + Handle h_oop(static_field ? Handle(thread, ik->java_mirror()) : Handle(thread, args->receiver())); + read_specialized_field(result, h_oop, &fd); } static void read_field(JfrJavaArguments* args, JavaValue* result, TRAPS) { assert(args != NULL, "invariant"); assert(result != NULL, "invariant"); DEBUG_ONLY(JfrJavaSupport::check_java_thread_in_vm(THREAD)); - InstanceKlass* const klass = static_cast(args->klass()); klass->initialize(CHECK); - const bool static_field = !args->has_receiver(); - fieldDescriptor fd; - lookup_field(args, klass, &fd, static_field); - assert(fd.offset() > 0, "invariant"); - - HandleMark hm(THREAD); - Handle h_oop(static_field ? Handle(THREAD, klass->java_mirror()) : Handle(THREAD, args->receiver())); - read_specialized_field(result, h_oop, &fd); + read_field(args, result, static_cast(THREAD)); } static void write_field(JfrJavaArguments* args, JavaValue* result, TRAPS) { @@ -448,6 +451,11 @@ void JfrJavaSupport::get_field(JfrJavaArguments* args, TRAPS) { read_field(args, args->result(), THREAD); } +void JfrJavaSupport::get_field(JfrJavaArguments* args, Thread* thread) { + assert(args != NULL, "invariant"); + read_field(args, args->result(), thread); +} + void JfrJavaSupport::get_field_local_ref(JfrJavaArguments* args, TRAPS) { assert(args != NULL, "invariant"); DEBUG_ONLY(check_java_thread_in_vm(THREAD)); @@ -487,20 +495,18 @@ Klass* JfrJavaSupport::klass(const jobject handle) { return obj->klass(); } -static char* allocate_string(bool c_heap, int length, JavaThread* jt) { +static char* allocate_string(bool c_heap, int length, Thread* thread) { return c_heap ? NEW_C_HEAP_ARRAY(char, length, mtTracing) : - NEW_RESOURCE_ARRAY_IN_THREAD(jt, char, length); + NEW_RESOURCE_ARRAY_IN_THREAD(thread, char, length); } -const char* JfrJavaSupport::c_str(oop string, JavaThread* t, bool c_heap /* false */) { - DEBUG_ONLY(check_java_thread_in_vm(t)); +const char* JfrJavaSupport::c_str(oop string, Thread* thread, bool c_heap /* false */) { char* str = NULL; const typeArrayOop value = java_lang_String::value(string); if (value != NULL) { const int length = java_lang_String::utf8_length(string, value); - str = allocate_string(c_heap, length + 1, t); + str = allocate_string(c_heap, length + 1, thread); if (str == NULL) { - JfrJavaSupport::throw_out_of_memory_error("Unable to allocate native memory", t); return NULL; } java_lang_String::as_utf8_string(string, value, str, length + 1); @@ -508,9 +514,8 @@ const char* JfrJavaSupport::c_str(oop string, JavaThread* t, bool c_heap /* fals return str; } -const char* JfrJavaSupport::c_str(jstring string, JavaThread* t, bool c_heap /* false */) { - DEBUG_ONLY(check_java_thread_in_vm(t)); - return string != NULL ? c_str(resolve_non_null(string), t, c_heap) : NULL; +const char* JfrJavaSupport::c_str(jstring string, Thread* thread, bool c_heap /* false */) { + return string != NULL ? c_str(resolve_non_null(string), thread, c_heap) : NULL; } /* diff --git a/src/hotspot/share/jfr/jni/jfrJavaSupport.hpp b/src/hotspot/share/jfr/jni/jfrJavaSupport.hpp index 6a2f200322c..ca97d90dd78 100644 --- a/src/hotspot/share/jfr/jni/jfrJavaSupport.hpp +++ b/src/hotspot/share/jfr/jni/jfrJavaSupport.hpp @@ -56,6 +56,7 @@ class JfrJavaSupport : public AllStatic { static void set_field(JfrJavaArguments* args, TRAPS); static void get_field(JfrJavaArguments* args, TRAPS); + static void get_field(JfrJavaArguments* args, Thread* thread); static void new_object(JfrJavaArguments* args, TRAPS); // global jni handle result @@ -75,8 +76,8 @@ class JfrJavaSupport : public AllStatic { // misc static Klass* klass(const jobject handle); - static const char* c_str(jstring string, JavaThread* jt, bool c_heap = false); - static const char* c_str(oop string, JavaThread* jt, bool c_heap = false); + static const char* c_str(jstring string, Thread* thread, bool c_heap = false); + static const char* c_str(oop string, Thread* thread, bool c_heap = false); // exceptions static void throw_illegal_state_exception(const char* message, TRAPS); diff --git a/src/hotspot/share/jfr/metadata/metadata.xml b/src/hotspot/share/jfr/metadata/metadata.xml index 795a0daee12..47962bdedd0 100644 --- a/src/hotspot/share/jfr/metadata/metadata.xml +++ b/src/hotspot/share/jfr/metadata/metadata.xml @@ -1099,6 +1099,13 @@ + + + + + + + diff --git a/src/hotspot/share/jfr/periodic/jfrFinalizerStatisticsEvent.cpp b/src/hotspot/share/jfr/periodic/jfrFinalizerStatisticsEvent.cpp new file mode 100644 index 00000000000..7404b8aa56c --- /dev/null +++ b/src/hotspot/share/jfr/periodic/jfrFinalizerStatisticsEvent.cpp @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2021, 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. + * + */ + +#include "precompiled.hpp" +#include "utilities/macros.hpp" +#if INCLUDE_MANAGEMENT +#include "classfile/classLoaderDataGraph.hpp" +#include "classfile/javaClasses.inline.hpp" +#include "jfr/jfrEvents.hpp" +#include "jfr/jni/jfrJavaSupport.hpp" +#include "jfr/periodic/jfrFinalizerStatisticsEvent.hpp" +#include "jfr/support/jfrSymbolTable.hpp" +#include "jfr/utilities/jfrTime.hpp" +#include "jfr/utilities/jfrTypes.hpp" +#include "oops/instanceKlass.inline.hpp" +#include "runtime/mutexLocker.hpp" +#include "runtime/thread.inline.hpp" +#include "services/finalizerService.hpp" + +static oop get_codesource(oop pd, Thread* thread) { + assert(pd != NULL, "invariant"); + assert(thread != NULL, "invariant"); + JavaValue result(T_OBJECT); + JfrJavaArguments args(&result); + args.set_klass(pd->klass()); + args.set_name("codesource"); + args.set_signature("Ljava/security/CodeSource;"); + args.set_receiver(pd); + JfrJavaSupport::get_field(&args, thread); + return result.get_oop(); +} + +// Caller needs ResourceMark +static const char* get_locationNoFragString(oop codesource, Thread* thread) { + assert(codesource != NULL, "invariant"); + assert(thread != NULL, "invariant"); + JavaValue result(T_OBJECT); + JfrJavaArguments args(&result); + args.set_klass(codesource->klass()); + args.set_name("locationNoFragString"); + args.set_signature("Ljava/lang/String;"); + args.set_receiver(codesource); + JfrJavaSupport::get_field(&args, thread); + const oop string_oop = result.get_oop(); + return string_oop != NULL ? JfrJavaSupport::c_str(string_oop, thread) : NULL; +} + +// Caller needs ResourceMark +static const char* codesource(const InstanceKlass* ik, Thread* thread) { + assert(ik != NULL, "invariant"); + assert(thread != NULL, "invariant"); + oop pd = java_lang_Class::protection_domain(ik->java_mirror()); + if (pd == NULL) { + return NULL; + } + oop codesource = get_codesource(pd, thread); + return codesource != NULL ? get_locationNoFragString(codesource, thread) : NULL; +} + +static void send_event(const FinalizerEntry* fe, const InstanceKlass* ik, const JfrTicks& timestamp, Thread* thread) { + assert(ik != NULL, "invariant"); + assert(ik->has_finalizer(), "invariant"); + assert(thread != NULL, "invariant"); + const char* const url = codesource(ik, thread); + const traceid url_symbol_id = url != NULL ? JfrSymbolTable::add(url) : 0; + EventFinalizerStatistics event(UNTIMED); + event.set_endtime(timestamp); + event.set_finalizableClass(ik); + event.set_codeSource(url_symbol_id); + if (fe == NULL) { + event.set_objects(0); + event.set_totalFinalizersRun(0); + } else { + assert(fe->klass() == ik, "invariant"); + event.set_objects(fe->objects_on_heap()); + event.set_totalFinalizersRun(fe->total_finalizers_run()); + } + event.commit(); +} + +void JfrFinalizerStatisticsEvent::send_unload_event(const InstanceKlass* ik) { + Thread* const thread = Thread::current(); + ResourceMark rm(thread); + send_event(FinalizerService::lookup(ik, thread), ik, JfrTicks::now(), thread); +} + +// Finalizer events generated by the periodic task will all have the same timestamp. + +class FinalizerStatisticsEventClosure : public FinalizerEntryClosure { + private: + Thread* _thread; + const JfrTicks _timestamp; + public: + FinalizerStatisticsEventClosure(Thread* thread) : _thread(thread), _timestamp(JfrTicks::now()) {} + virtual bool do_entry(const FinalizerEntry* fe) { + assert(fe != NULL, "invariant"); + send_event(fe, fe->klass(), _timestamp, _thread); + return true; + } +}; + +void JfrFinalizerStatisticsEvent::generate_events() { + Thread* const thread = Thread::current(); + ResourceMark rm(thread); + FinalizerStatisticsEventClosure fsec(thread); + MutexLocker lock(ClassLoaderDataGraph_lock); // To prevent entries from being removed by class unloading. + FinalizerService::do_entries(&fsec, thread); +} + +#endif // INCLUDE_MANAGEMENT diff --git a/src/hotspot/share/jfr/periodic/jfrFinalizerStatisticsEvent.hpp b/src/hotspot/share/jfr/periodic/jfrFinalizerStatisticsEvent.hpp new file mode 100644 index 00000000000..fe739495718 --- /dev/null +++ b/src/hotspot/share/jfr/periodic/jfrFinalizerStatisticsEvent.hpp @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2021, 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. + * + */ + +#ifndef SHARE_JFR_PERIODIC_JFRFINALIZERSTATISTICSEVENT_HPP +#define SHARE_JFR_PERIODIC_JFRFINALIZERSTATISTICSEVENT_HPP + +#include "memory/allocation.hpp" + +class InstanceKlass; + +class JfrFinalizerStatisticsEvent : AllStatic { + public: + static void send_unload_event(const InstanceKlass* ik) NOT_MANAGEMENT_RETURN; + static void generate_events() NOT_MANAGEMENT_RETURN; +}; + +#endif // SHARE_JFR_PERIODIC_JFRFINALIZERSTATISTICSEVENT_HPP diff --git a/src/hotspot/share/jfr/periodic/jfrPeriodic.cpp b/src/hotspot/share/jfr/periodic/jfrPeriodic.cpp index 905f1e53ac0..5b0ce8e2328 100644 --- a/src/hotspot/share/jfr/periodic/jfrPeriodic.cpp +++ b/src/hotspot/share/jfr/periodic/jfrPeriodic.cpp @@ -37,6 +37,7 @@ #include "gc/shared/gcVMOperations.hpp" #include "gc/shared/objectCountEventSender.hpp" #include "jfr/jfrEvents.hpp" +#include "jfr/periodic/jfrFinalizerStatisticsEvent.hpp" #include "jfr/periodic/jfrModuleEvent.hpp" #include "jfr/periodic/jfrOSInterface.hpp" #include "jfr/periodic/jfrThreadCPULoadEvent.hpp" @@ -472,10 +473,14 @@ TRACE_REQUEST_FUNC(JavaThreadStatistics) { } TRACE_REQUEST_FUNC(ClassLoadingStatistics) { +#if INCLUDE_MANAGEMENT EventClassLoadingStatistics event; event.set_loadedClassCount(ClassLoadingService::loaded_class_count()); event.set_unloadedClassCount(ClassLoadingService::unloaded_class_count()); event.commit(); +#else + log_debug(jfr, system)("Unable to generate requestable event ClassLoadingStatistics. The required jvm feature 'management' is missing."); +#endif } class JfrClassLoaderStatsClosure : public ClassLoaderStatsClosure { @@ -635,7 +640,6 @@ TRACE_REQUEST_FUNC(CodeSweeperConfiguration) { event.commit(); } - TRACE_REQUEST_FUNC(ShenandoahHeapRegionInformation) { #if INCLUDE_SHENANDOAHGC if (UseShenandoahGC) { @@ -644,3 +648,11 @@ TRACE_REQUEST_FUNC(ShenandoahHeapRegionInformation) { } #endif } + +TRACE_REQUEST_FUNC(FinalizerStatistics) { +#if INCLUDE_MANAGEMENT + JfrFinalizerStatisticsEvent::generate_events(); +#else + log_debug(jfr, system)("Unable to generate requestable event FinalizerStatistics. The required jvm feature 'management' is missing."); +#endif +} diff --git a/src/hotspot/share/jfr/recorder/checkpoint/types/jfrTypeSet.cpp b/src/hotspot/share/jfr/recorder/checkpoint/types/jfrTypeSet.cpp index 78d6ab48f97..46a7db1fc7c 100644 --- a/src/hotspot/share/jfr/recorder/checkpoint/types/jfrTypeSet.cpp +++ b/src/hotspot/share/jfr/recorder/checkpoint/types/jfrTypeSet.cpp @@ -55,8 +55,8 @@ typedef const ModuleEntry* ModPtr; typedef const ClassLoaderData* CldPtr; typedef const Method* MethodPtr; typedef const Symbol* SymbolPtr; -typedef const JfrSymbolId::SymbolEntry* SymbolEntryPtr; -typedef const JfrSymbolId::CStringEntry* CStringEntryPtr; +typedef const JfrSymbolTable::SymbolEntry* SymbolEntryPtr; +typedef const JfrSymbolTable::StringEntry* StringEntryPtr; static JfrCheckpointWriter* _writer = NULL; static JfrCheckpointWriter* _leakp_writer = NULL; @@ -64,18 +64,7 @@ static JfrArtifactSet* _artifacts = NULL; static JfrArtifactClosure* _subsystem_callback = NULL; static bool _class_unload = false; static bool _flushpoint = false; -static bool _clear_artifacts = false; - -// incremented on each rotation -static u8 checkpoint_id = 1; - -// creates a unique id by combining a checkpoint relative symbol id (2^24) -// with the current checkpoint id (2^40) -#define CREATE_SYMBOL_ID(sym_id) (((u8)((checkpoint_id << 24) | sym_id))) - -static traceid create_symbol_id(traceid artifact_id) { - return artifact_id != 0 ? CREATE_SYMBOL_ID(artifact_id) : 0; -} +static bool _initial_type_set = true; static bool current_epoch() { return _class_unload || _flushpoint; @@ -86,7 +75,7 @@ static bool previous_epoch() { } static bool is_initial_typeset_for_chunk() { - return _clear_artifacts && !_class_unload; + return _initial_type_set && !_class_unload; } static bool is_complete() { @@ -94,15 +83,15 @@ static bool is_complete() { } static traceid mark_symbol(KlassPtr klass, bool leakp) { - return klass != NULL ? create_symbol_id(_artifacts->mark(klass, leakp)) : 0; + return klass != NULL ? _artifacts->mark(klass, leakp) : 0; } static traceid mark_symbol(Symbol* symbol, bool leakp) { - return symbol != NULL ? create_symbol_id(_artifacts->mark(symbol, leakp)) : 0; + return symbol != NULL ? _artifacts->mark(symbol, leakp) : 0; } static traceid get_bootstrap_name(bool leakp) { - return create_symbol_id(_artifacts->bootstrap_name(leakp)); + return _artifacts->bootstrap_name(leakp); } static const char* primitive_name(KlassPtr type_array_klass) { @@ -927,14 +916,14 @@ static void write_methods() { } template <> -void set_serialized(SymbolEntryPtr ptr) { +void set_serialized(SymbolEntryPtr ptr) { assert(ptr != NULL, "invariant"); ptr->set_serialized(); assert(ptr->is_serialized(), "invariant"); } template <> -void set_serialized(CStringEntryPtr ptr) { +void set_serialized(StringEntryPtr ptr) { assert(ptr != NULL, "invariant"); ptr->set_serialized(); assert(ptr->is_serialized(), "invariant"); @@ -944,7 +933,7 @@ static int write_symbol(JfrCheckpointWriter* writer, SymbolEntryPtr entry, bool assert(writer != NULL, "invariant"); assert(entry != NULL, "invariant"); ResourceMark rm; - writer->write(create_symbol_id(entry->id())); + writer->write(entry->id()); writer->write(entry->value()->as_C_string()); return 1; } @@ -962,42 +951,42 @@ int write__symbol__leakp(JfrCheckpointWriter* writer, const void* e) { return write_symbol(writer, entry, true); } -static int write_cstring(JfrCheckpointWriter* writer, CStringEntryPtr entry, bool leakp) { +static int write_string(JfrCheckpointWriter* writer, StringEntryPtr entry, bool leakp) { assert(writer != NULL, "invariant"); assert(entry != NULL, "invariant"); - writer->write(create_symbol_id(entry->id())); + writer->write(entry->id()); writer->write(entry->value()); return 1; } -int write__cstring(JfrCheckpointWriter* writer, const void* e) { +int write__string(JfrCheckpointWriter* writer, const void* e) { assert(e != NULL, "invariant"); - CStringEntryPtr entry = (CStringEntryPtr)e; + StringEntryPtr entry = (StringEntryPtr)e; set_serialized(entry); - return write_cstring(writer, entry, false); + return write_string(writer, entry, false); } -int write__cstring__leakp(JfrCheckpointWriter* writer, const void* e) { +int write__string__leakp(JfrCheckpointWriter* writer, const void* e) { assert(e != NULL, "invariant"); - CStringEntryPtr entry = (CStringEntryPtr)e; - return write_cstring(writer, entry, true); + StringEntryPtr entry = (StringEntryPtr)e; + return write_string(writer, entry, true); } typedef SymbolPredicate SymPredicate; typedef JfrPredicatedTypeWriterImplHost SymbolEntryWriterImpl; typedef JfrTypeWriterHost SymbolEntryWriter; -typedef SymbolPredicate CStringPredicate; -typedef JfrPredicatedTypeWriterImplHost CStringEntryWriterImpl; -typedef JfrTypeWriterHost CStringEntryWriter; +typedef SymbolPredicate StringPredicate; +typedef JfrPredicatedTypeWriterImplHost StringEntryWriterImpl; +typedef JfrTypeWriterHost StringEntryWriter; typedef SymbolPredicate LeakSymPredicate; typedef JfrPredicatedTypeWriterImplHost LeakSymbolEntryWriterImpl; typedef JfrTypeWriterHost LeakSymbolEntryWriter; typedef CompositeFunctor CompositeSymbolWriter; -typedef SymbolPredicate LeakCStringPredicate; -typedef JfrPredicatedTypeWriterImplHost LeakCStringEntryWriterImpl; -typedef JfrTypeWriterHost LeakCStringEntryWriter; -typedef CompositeFunctor CompositeCStringWriter; +typedef SymbolPredicate LeakStringPredicate; +typedef JfrPredicatedTypeWriterImplHost LeakStringEntryWriterImpl; +typedef JfrTypeWriterHost LeakStringEntryWriter; +typedef CompositeFunctor CompositeStringWriter; static void write_symbols_with_leakp() { assert(_leakp_writer != NULL, "invariant"); @@ -1005,12 +994,12 @@ static void write_symbols_with_leakp() { LeakSymbolEntryWriter lsw(_leakp_writer, _class_unload); CompositeSymbolWriter csw(&lsw, &sw); _artifacts->iterate_symbols(csw); - CStringEntryWriter ccsw(_writer, _class_unload, true); // skip header - LeakCStringEntryWriter lccsw(_leakp_writer, _class_unload, true); // skip header - CompositeCStringWriter cccsw(&lccsw, &ccsw); - _artifacts->iterate_cstrings(cccsw); - sw.add(ccsw.count()); - lsw.add(lccsw.count()); + StringEntryWriter sew(_writer, _class_unload, true); // skip header + LeakStringEntryWriter lsew(_leakp_writer, _class_unload, true); // skip header + CompositeStringWriter csew(&lsew, &sew); + _artifacts->iterate_strings(csew); + sw.add(sew.count()); + lsw.add(lsew.count()); _artifacts->tally(sw); } @@ -1022,9 +1011,9 @@ static void write_symbols() { } SymbolEntryWriter sw(_writer, _class_unload); _artifacts->iterate_symbols(sw); - CStringEntryWriter csw(_writer, _class_unload, true); // skip header - _artifacts->iterate_cstrings(csw); - sw.add(csw.count()); + StringEntryWriter sew(_writer, _class_unload, true); // skip header + _artifacts->iterate_strings(sew); + sw.add(sew.count()); _artifacts->tally(sw); } @@ -1043,10 +1032,10 @@ static size_t teardown() { if (previous_epoch()) { clear_klasses_and_methods(); JfrKlassUnloading::clear(); - _clear_artifacts = true; - ++checkpoint_id; + _artifacts->increment_checkpoint_id(); + _initial_type_set = true; } else { - _clear_artifacts = false; + _initial_type_set = false; } return total_count; } @@ -1059,7 +1048,7 @@ static void setup(JfrCheckpointWriter* writer, JfrCheckpointWriter* leakp_writer if (_artifacts == NULL) { _artifacts = new JfrArtifactSet(class_unload); } else { - _artifacts->initialize(class_unload, _clear_artifacts); + _artifacts->initialize(class_unload); } if (!_class_unload) { JfrKlassUnloading::sort(previous_epoch()); @@ -1094,7 +1083,9 @@ size_t JfrTypeSet::serialize(JfrCheckpointWriter* writer, JfrCheckpointWriter* l void JfrTypeSet::clear() { ResourceMark rm; JfrKlassUnloading::clear(); - _clear_artifacts = true; + if (_artifacts != NULL) { + _artifacts->clear(); + } setup(NULL, NULL, false, false); register_klasses(); clear_packages(); diff --git a/src/hotspot/share/jfr/recorder/checkpoint/types/jfrTypeSetUtils.cpp b/src/hotspot/share/jfr/recorder/checkpoint/types/jfrTypeSetUtils.cpp index 5eb70fb23d2..19a9fa7516b 100644 --- a/src/hotspot/share/jfr/recorder/checkpoint/types/jfrTypeSetUtils.cpp +++ b/src/hotspot/share/jfr/recorder/checkpoint/types/jfrTypeSetUtils.cpp @@ -30,225 +30,7 @@ #include "oops/oop.inline.hpp" #include "oops/symbol.hpp" -static JfrSymbolId::CStringEntry* bootstrap = NULL; - -JfrSymbolId::JfrSymbolId() : - _sym_table(new SymbolTable(this)), - _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"); - assert(_cstring_table != NULL, "invariant"); - bootstrap = new CStringEntry(0, (const char*)&BOOTSTRAP_LOADER_NAME); - assert(bootstrap != NULL, "invariant"); - bootstrap->set_id(1); - _cstring_list = bootstrap; -} - -JfrSymbolId::~JfrSymbolId() { - clear(); - delete _sym_table; - delete _cstring_table; - delete bootstrap; -} - -void JfrSymbolId::clear() { - assert(_sym_table != NULL, "invariant"); - if (_sym_table->has_entries()) { - _sym_table->clear_entries(); - } - assert(!_sym_table->has_entries(), "invariant"); - - assert(_cstring_table != NULL, "invariant"); - if (_cstring_table->has_entries()) { - _cstring_table->clear_entries(); - } - assert(!_cstring_table->has_entries(), "invariant"); - - _sym_list = NULL; - _symbol_id_counter = 1; - - _sym_query = NULL; - _cstring_query = NULL; - - assert(bootstrap != NULL, "invariant"); - bootstrap->reset(); - _cstring_list = bootstrap; -} - -void JfrSymbolId::set_class_unload(bool class_unload) { - _class_unload = class_unload; -} - -void JfrSymbolId::on_link(const SymbolEntry* entry) { - assert(entry != NULL, "invariant"); - const_cast(entry->literal())->increment_refcount(); - assert(entry->id() == 0, "invariant"); - entry->set_id(++_symbol_id_counter); - entry->set_list_next(_sym_list); - _sym_list = entry; -} - -bool JfrSymbolId::on_equals(uintptr_t hash, const SymbolEntry* entry) { - assert(entry != NULL, "invariant"); - assert(entry->hash() == hash, "invariant"); - assert(_sym_query != NULL, "invariant"); - return _sym_query == entry->literal(); -} - -void JfrSymbolId::on_unlink(const SymbolEntry* entry) { - assert(entry != NULL, "invariant"); - 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"); - assert(_cstring_query != NULL, "invariant"); - return string_compare(_cstring_query, entry->literal()); -} - -void JfrSymbolId::on_unlink(const CStringEntry* entry) { - assert(entry != NULL, "invariant"); - JfrCHeapObj::free(const_cast(entry->literal()), strlen(entry->literal() + 1)); -} - -traceid JfrSymbolId::bootstrap_name(bool leakp) { - assert(bootstrap != NULL, "invariant"); - if (leakp) { - bootstrap->set_leakp(); - } - return 1; -} - -traceid JfrSymbolId::mark(const Symbol* symbol, bool leakp) { - assert(symbol != NULL, "invariant"); - return mark((uintptr_t)symbol->identity_hash(), symbol, leakp); -} - -traceid JfrSymbolId::mark(uintptr_t hash, const Symbol* data, bool leakp) { - assert(data != NULL, "invariant"); - assert(_sym_table != NULL, "invariant"); - _sym_query = data; - const SymbolEntry& entry = _sym_table->lookup_put(hash, data); - if (_class_unload) { - entry.set_unloading(); - } - if (leakp) { - entry.set_leakp(); - } - return entry.id(); -} - -traceid JfrSymbolId::mark(uintptr_t hash, const char* str, bool leakp) { - assert(str != NULL, "invariant"); - assert(_cstring_table != NULL, "invariant"); - _cstring_query = str; - const CStringEntry& entry = _cstring_table->lookup_put(hash, str); - if (_class_unload) { - entry.set_unloading(); - } - if (leakp) { - entry.set_leakp(); - } - return entry.id(); -} - -/* -* hidden classes symbol is the external name + -* the address of its InstanceKlass slash appended: -* java.lang.invoke.LambdaForm$BMH/22626602 -* -* caller needs ResourceMark -*/ - -uintptr_t JfrSymbolId::hidden_klass_name_hash(const InstanceKlass* ik) { - assert(ik != NULL, "invariant"); - assert(ik->is_hidden(), "invariant"); - const oop mirror = ik->java_mirror_no_keepalive(); - assert(mirror != NULL, "invariant"); - return (uintptr_t)mirror->identity_hash(); -} - -static const char* create_hidden_klass_symbol(const InstanceKlass* ik, uintptr_t hash) { - assert(ik != NULL, "invariant"); - assert(ik->is_hidden(), "invariant"); - assert(hash != 0, "invariant"); - char* hidden_symbol = NULL; - const oop mirror = ik->java_mirror_no_keepalive(); - assert(mirror != NULL, "invariant"); - char hash_buf[40]; - sprintf(hash_buf, "/" UINTX_FORMAT, hash); - const size_t hash_len = strlen(hash_buf); - const size_t result_len = ik->name()->utf8_length(); - hidden_symbol = NEW_RESOURCE_ARRAY(char, result_len + hash_len + 1); - ik->name()->as_klass_external_name(hidden_symbol, (int)result_len + 1); - assert(strlen(hidden_symbol) == result_len, "invariant"); - strcpy(hidden_symbol + result_len, hash_buf); - assert(strlen(hidden_symbol) == result_len + hash_len, "invariant"); - return hidden_symbol; -} - -bool JfrSymbolId::is_hidden_klass(const Klass* k) { - assert(k != NULL, "invariant"); - return k->is_instance_klass() && ((const InstanceKlass*)k)->is_hidden(); -} - -traceid JfrSymbolId::mark_hidden_klass_name(const InstanceKlass* ik, bool leakp) { - assert(ik != NULL, "invariant"); - assert(ik->is_hidden(), "invariant"); - const uintptr_t hash = hidden_klass_name_hash(ik); - const char* const hidden_symbol = create_hidden_klass_symbol(ik, hash); - return mark(hash, hidden_symbol, leakp); -} - -traceid JfrSymbolId::mark(const Klass* k, bool leakp) { - assert(k != NULL, "invariant"); - traceid symbol_id = 0; - if (is_hidden_klass(k)) { - assert(k->is_instance_klass(), "invariant"); - symbol_id = mark_hidden_klass_name((const InstanceKlass*)k, leakp); - } - if (0 == symbol_id) { - Symbol* const sym = k->name(); - if (sym != NULL) { - symbol_id = mark(sym, leakp); - } - } - assert(symbol_id > 0, "a symbol handler must mark the symbol for writing"); - return symbol_id; -} - -JfrArtifactSet::JfrArtifactSet(bool class_unload) : _symbol_id(new JfrSymbolId()), +JfrArtifactSet::JfrArtifactSet(bool class_unload) : _symbol_table(NULL), _klass_list(NULL), _total_count(0) { initialize(class_unload); @@ -258,47 +40,53 @@ JfrArtifactSet::JfrArtifactSet(bool class_unload) : _symbol_id(new JfrSymbolId() static const size_t initial_klass_list_size = 256; const int initial_klass_loader_set_size = 64; -void JfrArtifactSet::initialize(bool class_unload, bool clear /* false */) { - assert(_symbol_id != NULL, "invariant"); - if (clear) { - _symbol_id->clear(); +void JfrArtifactSet::initialize(bool class_unload) { + if (_symbol_table == NULL) { + _symbol_table = JfrSymbolTable::create(); + assert(_symbol_table != NULL, "invariant"); } - _symbol_id->set_class_unload(class_unload); + assert(_symbol_table != NULL, "invariant"); + _symbol_table->set_class_unload(class_unload); _total_count = 0; // resource allocation _klass_list = new GrowableArray(initial_klass_list_size); _klass_loader_set = new GrowableArray(initial_klass_loader_set_size); } +void JfrArtifactSet::clear() { + if (_symbol_table != NULL) { + _symbol_table->clear(); + } +} + JfrArtifactSet::~JfrArtifactSet() { - _symbol_id->clear(); - delete _symbol_id; + delete _symbol_table; // _klass_list and _klass_loader_list will be cleared by a ResourceMark } traceid JfrArtifactSet::bootstrap_name(bool leakp) { - return _symbol_id->bootstrap_name(leakp); + return _symbol_table->bootstrap_name(leakp); } traceid JfrArtifactSet::mark_hidden_klass_name(const Klass* klass, bool leakp) { assert(klass->is_instance_klass(), "invariant"); - return _symbol_id->mark_hidden_klass_name((const InstanceKlass*)klass, leakp); + return _symbol_table->mark_hidden_klass_name((const InstanceKlass*)klass, leakp); } traceid JfrArtifactSet::mark(uintptr_t hash, const Symbol* sym, bool leakp) { - return _symbol_id->mark(hash, sym, leakp); + return _symbol_table->mark(hash, sym, leakp); } traceid JfrArtifactSet::mark(const Klass* klass, bool leakp) { - return _symbol_id->mark(klass, leakp); + return _symbol_table->mark(klass, leakp); } traceid JfrArtifactSet::mark(const Symbol* symbol, bool leakp) { - return _symbol_id->mark(symbol, leakp); + return _symbol_table->mark(symbol, leakp); } traceid JfrArtifactSet::mark(uintptr_t hash, const char* const str, bool leakp) { - return _symbol_id->mark(hash, str, leakp); + return _symbol_table->mark(hash, str, leakp); } bool JfrArtifactSet::has_klass_entries() const { @@ -324,3 +112,9 @@ void JfrArtifactSet::register_klass(const Klass* k) { size_t JfrArtifactSet::total_count() const { return _total_count; } + +void JfrArtifactSet::increment_checkpoint_id() { + assert(_symbol_table != NULL, "invariant"); + _symbol_table->increment_checkpoint_id(); +} + diff --git a/src/hotspot/share/jfr/recorder/checkpoint/types/jfrTypeSetUtils.hpp b/src/hotspot/share/jfr/recorder/checkpoint/types/jfrTypeSetUtils.hpp index 905a16d28c5..d82811fe84c 100644 --- a/src/hotspot/share/jfr/recorder/checkpoint/types/jfrTypeSetUtils.hpp +++ b/src/hotspot/share/jfr/recorder/checkpoint/types/jfrTypeSetUtils.hpp @@ -26,8 +26,8 @@ #define SHARE_JFR_RECORDER_CHECKPOINT_TYPES_JFRTYPESETUTILS_HPP #include "jfr/recorder/checkpoint/types/traceid/jfrTraceId.inline.hpp" +#include "jfr/support/jfrSymbolTable.hpp" #include "jfr/utilities/jfrAllocation.hpp" -#include "jfr/utilities/jfrHashtable.hpp" #include "oops/klass.hpp" #include "oops/method.hpp" @@ -187,98 +187,6 @@ class LeakPredicate { } }; -template -class ListEntry : public JfrHashtableEntry { - public: - ListEntry(uintptr_t hash, const T& data) : JfrHashtableEntry(hash, data), - _list_next(NULL), _serialized(false), _unloading(false), _leakp(false) {} - const ListEntry* list_next() const { return _list_next; } - void reset() const { - _list_next = NULL; _serialized = false; _unloading = false; _leakp = false; - } - void set_list_next(const ListEntry* next) const { _list_next = next; } - bool is_serialized() const { return _serialized; } - void set_serialized() const { _serialized = true; } - bool is_unloading() const { return _unloading; } - void set_unloading() const { _unloading = true; } - bool is_leakp() const { return _leakp; } - void set_leakp() const { _leakp = true; } - private: - mutable const ListEntry* _list_next; - mutable bool _serialized; - mutable bool _unloading; - mutable bool _leakp; -}; - -class JfrSymbolId : public JfrCHeapObj { - template class, typename, size_t> - friend class HashTableHost; - typedef HashTableHost SymbolTable; - typedef HashTableHost CStringTable; - friend class JfrArtifactSet; - public: - typedef SymbolTable::HashEntry SymbolEntry; - typedef CStringTable::HashEntry CStringEntry; - private: - SymbolTable* _sym_table; - 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; - - // hashtable(s) callbacks - void on_link(const SymbolEntry* entry); - bool on_equals(uintptr_t hash, const SymbolEntry* entry); - void on_unlink(const SymbolEntry* entry); - void on_link(const CStringEntry* entry); - bool on_equals(uintptr_t hash, const CStringEntry* entry); - void on_unlink(const CStringEntry* entry); - - template - void iterate(Functor& functor, const T* list) { - const T* symbol = list; - while (symbol != NULL) { - const T* next = symbol->list_next(); - functor(symbol); - symbol = next; - } - } - - traceid mark_hidden_klass_name(const InstanceKlass* k, bool leakp); - bool is_hidden_klass(const Klass* k); - uintptr_t hidden_klass_name_hash(const InstanceKlass* ik); - - public: - JfrSymbolId(); - ~JfrSymbolId(); - - void clear(); - void set_class_unload(bool class_unload); - - traceid mark(uintptr_t hash, const Symbol* sym, bool leakp); - traceid mark(const Klass* k, bool leakp); - traceid mark(const Symbol* symbol, bool leakp); - traceid mark(uintptr_t hash, const char* str, bool leakp); - traceid bootstrap_name(bool leakp); - - template - void iterate_symbols(Functor& functor) { - iterate(functor, _sym_list); - } - - template - void iterate_cstrings(Functor& functor) { - iterate(functor, _cstring_list); - } - - bool has_entries() const { return has_symbol_entries() || has_cstring_entries(); } - bool has_symbol_entries() const { return _sym_list != NULL; } - bool has_cstring_entries() const { return _cstring_list != NULL; } -}; - /** * When processing a set of artifacts, there will be a need * to track transitive dependencies originating with each artifact. @@ -295,7 +203,7 @@ class JfrSymbolId : public JfrCHeapObj { */ class JfrArtifactSet : public JfrCHeapObj { private: - JfrSymbolId* _symbol_id; + JfrSymbolTable* _symbol_table; GrowableArray* _klass_list; GrowableArray* _klass_loader_set; size_t _total_count; @@ -305,7 +213,8 @@ class JfrArtifactSet : public JfrCHeapObj { ~JfrArtifactSet(); // caller needs ResourceMark - void initialize(bool class_unload, bool clear = false); + void initialize(bool class_unload); + void clear(); traceid mark(uintptr_t hash, const Symbol* sym, bool leakp); traceid mark(const Klass* klass, bool leakp); @@ -314,15 +223,16 @@ class JfrArtifactSet : public JfrCHeapObj { traceid mark_hidden_klass_name(const Klass* klass, bool leakp); traceid bootstrap_name(bool leakp); - const JfrSymbolId::SymbolEntry* map_symbol(const Symbol* symbol) const; - const JfrSymbolId::SymbolEntry* map_symbol(uintptr_t hash) const; - const JfrSymbolId::CStringEntry* map_cstring(uintptr_t hash) const; + const JfrSymbolTable::SymbolEntry* map_symbol(const Symbol* symbol) const; + const JfrSymbolTable::SymbolEntry* map_symbol(uintptr_t hash) const; + const JfrSymbolTable::StringEntry* map_string(uintptr_t hash) const; bool has_klass_entries() const; int entries() const; size_t total_count() const; void register_klass(const Klass* k); bool should_do_loader_klass(const Klass* k); + void increment_checkpoint_id(); template void iterate_klasses(Functor& functor) const { @@ -335,12 +245,12 @@ class JfrArtifactSet : public JfrCHeapObj { template void iterate_symbols(T& functor) { - _symbol_id->iterate_symbols(functor); + _symbol_table->iterate_symbols(functor); } template - void iterate_cstrings(T& functor) { - _symbol_id->iterate_cstrings(functor); + void iterate_strings(T& functor) { + _symbol_table->iterate_strings(functor); } template diff --git a/src/hotspot/share/jfr/recorder/service/jfrPostBox.cpp b/src/hotspot/share/jfr/recorder/service/jfrPostBox.cpp index bfade7236b7..09408ff17a5 100644 --- a/src/hotspot/share/jfr/recorder/service/jfrPostBox.cpp +++ b/src/hotspot/share/jfr/recorder/service/jfrPostBox.cpp @@ -26,6 +26,8 @@ #include "jfr/recorder/service/jfrPostBox.hpp" #include "jfr/utilities/jfrTryLock.hpp" #include "runtime/atomic.hpp" +#include "runtime/handles.hpp" +#include "runtime/interfaceSupport.inline.hpp" #include "runtime/thread.inline.hpp" #define MSG_IS_SYNCHRONOUS ( (MSGBIT(MSG_ROTATE)) | \ @@ -110,7 +112,9 @@ void JfrPostBox::asynchronous_post(int msg) { void JfrPostBox::synchronous_post(int msg) { assert(is_synchronous(msg), "invariant"); assert(!JfrMsg_lock->owned_by_self(), "should not hold JfrMsg_lock here!"); - MonitorLocker msg_lock(JfrMsg_lock); + NoHandleMark nhm; + ThreadBlockInVM transition(JavaThread::current()); + MonitorLocker msg_lock(JfrMsg_lock, Mutex::_no_safepoint_check_flag); deposit(msg); // serial_id is used to check when what we send in has been processed. // _msg_read_serial is read under JfrMsg_lock protection. @@ -168,6 +172,6 @@ void JfrPostBox::notify_waiters() { // safeguard to ensure no threads are left waiting void JfrPostBox::notify_collection_stop() { - MutexLocker msg_lock(JfrMsg_lock); + assert(JfrMsg_lock->owned_by_self(), "invariant"); JfrMsg_lock->notify_all(); } diff --git a/src/hotspot/share/jfr/recorder/service/jfrRecorderThreadLoop.cpp b/src/hotspot/share/jfr/recorder/service/jfrRecorderThreadLoop.cpp index 6cbd487d514..1145b3c21b8 100644 --- a/src/hotspot/share/jfr/recorder/service/jfrRecorderThreadLoop.cpp +++ b/src/hotspot/share/jfr/recorder/service/jfrRecorderThreadLoop.cpp @@ -51,23 +51,24 @@ void recorderthread_entry(JavaThread* thread, JavaThread* unused) { log_debug(jfr, system)("Recorder thread STARTED"); { + // Run as _thread_in_native to minimize impact on safepoint synchronization. + NoHandleMark nhm; + ThreadToNativeFromVM transition(thread); + bool done = false; int msgs = 0; JfrRecorderService service; - MutexLocker msg_lock(JfrMsg_lock); + + MonitorLocker msg_lock(JfrMsg_lock, Mutex::_no_safepoint_check_flag); // JFR MESSAGE LOOP PROCESSING - BEGIN while (!done) { if (post_box.is_empty()) { - JfrMsg_lock->wait(); + msg_lock.wait(); } msgs = post_box.collect(); - JfrMsg_lock->unlock(); { - // Run as _thread_in_native as much a possible - // to minimize impact on safepoint synchronizations. - NoHandleMark nhm; - ThreadToNativeFromVM transition(thread); + MutexUnlocker mul(JfrMsg_lock, Mutex::_no_safepoint_check_flag); if (PROCESS_FULL_BUFFERS) { service.process_full_buffers(); } @@ -82,18 +83,16 @@ void recorderthread_entry(JavaThread* thread, JavaThread* unused) { service.flushpoint(); } } - JfrMsg_lock->lock(); post_box.notify_waiters(); if (SHUTDOWN) { log_debug(jfr, system)("Request to STOP recorder"); done = true; } } // JFR MESSAGE LOOP PROCESSING - END - - } // JfrMsg_lock scope + post_box.notify_collection_stop(); + } // JfrMsg_lock scope and the thread returns to _thread_in_vm assert(!JfrMsg_lock->owned_by_self(), "invariant"); - post_box.notify_collection_stop(); JfrRecorder::on_recorder_thread_exit(); #undef START diff --git a/src/hotspot/share/jfr/support/jfrKlassUnloading.cpp b/src/hotspot/share/jfr/support/jfrKlassUnloading.cpp index 7d7b5ca9161..b1794a7a1e1 100644 --- a/src/hotspot/share/jfr/support/jfrKlassUnloading.cpp +++ b/src/hotspot/share/jfr/support/jfrKlassUnloading.cpp @@ -23,11 +23,14 @@ */ #include "precompiled.hpp" +#include "jfr/jfrEvents.hpp" +#include "jfr/periodic/jfrFinalizerStatisticsEvent.hpp" #include "jfr/recorder/checkpoint/types/traceid/jfrTraceId.inline.hpp" #include "jfr/support/jfrKlassUnloading.hpp" #include "jfr/utilities/jfrPredicate.hpp" #include "jfr/utilities/jfrRelation.hpp" #include "runtime/mutexLocker.hpp" +#include "utilities/macros.hpp" static const int initial_array_size = 64; @@ -107,9 +110,22 @@ static bool add_to_unloaded_klass_set(traceid klass_id, bool current_epoch) { return true; } +#if INCLUDE_MANAGEMENT +static void send_finalizer_event(const Klass* k) { + if (!k->is_instance_klass()) { + return; + } + const InstanceKlass* const ik = InstanceKlass::cast(k); + if (ik->has_finalizer()) { + JfrFinalizerStatisticsEvent::send_unload_event(ik); + } +} +#endif + bool JfrKlassUnloading::on_unload(const Klass* k) { assert(k != NULL, "invariant"); assert_locked_or_safepoint(ClassLoaderDataGraph_lock); + MANAGEMENT_ONLY(send_finalizer_event(k);) if (IS_JDK_JFR_EVENT_SUBKLASS(k)) { ++event_klass_unloaded_count; } diff --git a/src/hotspot/share/jfr/support/jfrSymbolTable.cpp b/src/hotspot/share/jfr/support/jfrSymbolTable.cpp new file mode 100644 index 00000000000..786fff37efd --- /dev/null +++ b/src/hotspot/share/jfr/support/jfrSymbolTable.cpp @@ -0,0 +1,316 @@ +/* + * Copyright (c) 2021, 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. + * + */ + +#include "precompiled.hpp" +#include "classfile/javaClasses.inline.hpp" +#include "classfile/classLoaderData.hpp" +#include "jfr/support/jfrSymbolTable.hpp" +#include "oops/instanceKlass.hpp" +#include "oops/oop.inline.hpp" +#include "oops/symbol.hpp" +#include "runtime/mutexLocker.hpp" + +// incremented on each rotation +static u8 checkpoint_id = 1; + +// creates a unique id by combining a checkpoint relative symbol id (2^24) +// with the current checkpoint id (2^40) +#define CREATE_SYMBOL_ID(sym_id) (((u8)((checkpoint_id << 24) | sym_id))) + +static traceid create_symbol_id(traceid artifact_id) { + return artifact_id != 0 ? CREATE_SYMBOL_ID(artifact_id) : 0; +} + +static uintptr_t string_hash(const char* str) { + return java_lang_String::hash_code(reinterpret_cast(str), static_cast(strlen(str))); +} + +static JfrSymbolTable::StringEntry* bootstrap = NULL; + +static JfrSymbolTable* _instance = NULL; + +static JfrSymbolTable& instance() { + assert(_instance != NULL, "invariant"); + return *_instance; +} + +JfrSymbolTable* JfrSymbolTable::create() { + assert(_instance == NULL, "invariant"); + assert_lock_strong(ClassLoaderDataGraph_lock); + _instance = new JfrSymbolTable(); + return _instance; +} + +void JfrSymbolTable::destroy() { + assert_lock_strong(ClassLoaderDataGraph_lock); + if (_instance != NULL) { + delete _instance; + _instance = NULL; + } + assert(_instance == NULL, "invariant"); +} + +JfrSymbolTable::JfrSymbolTable() : + _symbols(new Symbols(this)), + _strings(new Strings(this)), + _symbol_list(NULL), + _string_list(NULL), + _symbol_query(NULL), + _string_query(NULL), + _id_counter(1), + _class_unload(false) { + assert(_symbols != NULL, "invariant"); + assert(_strings != NULL, "invariant"); + bootstrap = new StringEntry(0, (const char*)&BOOTSTRAP_LOADER_NAME); + assert(bootstrap != NULL, "invariant"); + bootstrap->set_id(create_symbol_id(1)); + _string_list = bootstrap; +} + +JfrSymbolTable::~JfrSymbolTable() { + clear(); + delete _symbols; + delete _strings; + delete bootstrap; +} + +void JfrSymbolTable::clear() { + assert(_symbols != NULL, "invariant"); + if (_symbols->has_entries()) { + _symbols->clear_entries(); + } + assert(!_symbols->has_entries(), "invariant"); + + assert(_strings != NULL, "invariant"); + if (_strings->has_entries()) { + _strings->clear_entries(); + } + assert(!_strings->has_entries(), "invariant"); + + _symbol_list = NULL; + _id_counter = 1; + + _symbol_query = NULL; + _string_query = NULL; + + assert(bootstrap != NULL, "invariant"); + bootstrap->reset(); + _string_list = bootstrap; +} + +void JfrSymbolTable::set_class_unload(bool class_unload) { + _class_unload = class_unload; +} + +void JfrSymbolTable::increment_checkpoint_id() { + assert_lock_strong(ClassLoaderDataGraph_lock); + clear(); + ++checkpoint_id; +} + +template +inline void JfrSymbolTable::assign_id(T* entry) { + assert(entry != NULL, "invariant"); + assert(entry->id() == 0, "invariant"); + entry->set_id(create_symbol_id(++_id_counter)); +} + +void JfrSymbolTable::on_link(const SymbolEntry* entry) { + assign_id(entry); + const_cast(entry->literal())->increment_refcount(); + entry->set_list_next(_symbol_list); + _symbol_list = entry; +} + +bool JfrSymbolTable::on_equals(uintptr_t hash, const SymbolEntry* entry) { + assert(entry != NULL, "invariant"); + assert(entry->hash() == hash, "invariant"); + assert(_symbol_query != NULL, "invariant"); + return _symbol_query == entry->literal(); +} + +void JfrSymbolTable::on_unlink(const SymbolEntry* entry) { + assert(entry != NULL, "invariant"); + const_cast(entry->literal())->decrement_refcount(); +} + +static const char* resource_to_c_heap_string(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 JfrSymbolTable::on_link(const StringEntry* entry) { + assign_id(entry); + const_cast(entry)->set_literal(resource_to_c_heap_string(entry->literal())); + entry->set_list_next(_string_list); + _string_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 JfrSymbolTable::on_equals(uintptr_t hash, const StringEntry* entry) { + assert(entry != NULL, "invariant"); + assert(entry->hash() == hash, "invariant"); + assert(_string_query != NULL, "invariant"); + return string_compare(_string_query, entry->literal()); +} + +void JfrSymbolTable::on_unlink(const StringEntry* entry) { + assert(entry != NULL, "invariant"); + JfrCHeapObj::free(const_cast(entry->literal()), strlen(entry->literal() + 1)); +} + +traceid JfrSymbolTable::bootstrap_name(bool leakp) { + assert(bootstrap != NULL, "invariant"); + if (leakp) { + bootstrap->set_leakp(); + } + return bootstrap->id(); +} + +traceid JfrSymbolTable::mark(const Symbol* sym, bool leakp /* false */) { + assert(sym != NULL, "invariant"); + return mark((uintptr_t)sym->identity_hash(), sym, leakp); +} + +traceid JfrSymbolTable::mark(uintptr_t hash, const Symbol* sym, bool leakp) { + assert(sym != NULL, "invariant"); + assert(_symbols != NULL, "invariant"); + _symbol_query = sym; + const SymbolEntry& entry = _symbols->lookup_put(hash, sym); + if (_class_unload) { + entry.set_unloading(); + } + if (leakp) { + entry.set_leakp(); + } + return entry.id(); +} + +traceid JfrSymbolTable::mark(const char* str, bool leakp /* false*/) { + return mark(string_hash(str), str, leakp); +} + +traceid JfrSymbolTable::mark(uintptr_t hash, const char* str, bool leakp) { + assert(str != NULL, "invariant"); + assert(_strings != NULL, "invariant"); + _string_query = str; + const StringEntry& entry = _strings->lookup_put(hash, str); + if (_class_unload) { + entry.set_unloading(); + } + if (leakp) { + entry.set_leakp(); + } + return entry.id(); +} + +/* +* hidden classes symbol is the external name + +* the address of its InstanceKlass slash appended: +* java.lang.invoke.LambdaForm$BMH/22626602 +* +* caller needs ResourceMark +*/ + +uintptr_t JfrSymbolTable::hidden_klass_name_hash(const InstanceKlass* ik) { + assert(ik != NULL, "invariant"); + assert(ik->is_hidden(), "invariant"); + const oop mirror = ik->java_mirror_no_keepalive(); + assert(mirror != NULL, "invariant"); + return (uintptr_t)mirror->identity_hash(); +} + +static const char* create_hidden_klass_symbol(const InstanceKlass* ik, uintptr_t hash) { + assert(ik != NULL, "invariant"); + assert(ik->is_hidden(), "invariant"); + assert(hash != 0, "invariant"); + char* hidden_symbol = NULL; + const oop mirror = ik->java_mirror_no_keepalive(); + assert(mirror != NULL, "invariant"); + char hash_buf[40]; + sprintf(hash_buf, "/" UINTX_FORMAT, hash); + const size_t hash_len = strlen(hash_buf); + const size_t result_len = ik->name()->utf8_length(); + hidden_symbol = NEW_RESOURCE_ARRAY(char, result_len + hash_len + 1); + ik->name()->as_klass_external_name(hidden_symbol, (int)result_len + 1); + assert(strlen(hidden_symbol) == result_len, "invariant"); + strcpy(hidden_symbol + result_len, hash_buf); + assert(strlen(hidden_symbol) == result_len + hash_len, "invariant"); + return hidden_symbol; +} + +bool JfrSymbolTable::is_hidden_klass(const Klass* k) { + assert(k != NULL, "invariant"); + return k->is_instance_klass() && ((const InstanceKlass*)k)->is_hidden(); +} + +traceid JfrSymbolTable::mark_hidden_klass_name(const InstanceKlass* ik, bool leakp) { + assert(ik != NULL, "invariant"); + assert(ik->is_hidden(), "invariant"); + const uintptr_t hash = hidden_klass_name_hash(ik); + const char* const hidden_symbol = create_hidden_klass_symbol(ik, hash); + return mark(hash, hidden_symbol, leakp); +} + +traceid JfrSymbolTable::mark(const Klass* k, bool leakp) { + assert(k != NULL, "invariant"); + traceid symbol_id = 0; + if (is_hidden_klass(k)) { + assert(k->is_instance_klass(), "invariant"); + symbol_id = mark_hidden_klass_name((const InstanceKlass*)k, leakp); + } else { + Symbol* const sym = k->name(); + if (sym != NULL) { + symbol_id = mark(sym, leakp); + } + } + assert(symbol_id > 0, "a symbol handler must mark the symbol for writing"); + return symbol_id; +} + +template +traceid JfrSymbolTable::add_impl(const T* sym) { + assert(sym != NULL, "invariant"); + assert(_instance != NULL, "invariant"); + assert_locked_or_safepoint(ClassLoaderDataGraph_lock); + return instance().mark(sym); +} + +traceid JfrSymbolTable::add(const Symbol* sym) { + return add_impl(sym); +} + +traceid JfrSymbolTable::add(const char* str) { + return add_impl(str); +} diff --git a/src/hotspot/share/jfr/support/jfrSymbolTable.hpp b/src/hotspot/share/jfr/support/jfrSymbolTable.hpp new file mode 100644 index 00000000000..5a6d9beaa17 --- /dev/null +++ b/src/hotspot/share/jfr/support/jfrSymbolTable.hpp @@ -0,0 +1,148 @@ +/* + * Copyright (c) 2021, 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. + * + */ + +#ifndef SHARE_JFR_SUPPORT_JFRSYMBOLTABLE_HPP +#define SHARE_JFR_SUPPORT_JFRSYMBOLTABLE_HPP + +#include "jfr/utilities/jfrHashtable.hpp" +#include "jfr/utilities/jfrTypes.hpp" + +template +class ListEntry : public JfrHashtableEntry { + public: + ListEntry(uintptr_t hash, const T& data) : JfrHashtableEntry(hash, data), + _list_next(NULL), _serialized(false), _unloading(false), _leakp(false) {} + const ListEntry* list_next() const { return _list_next; } + void reset() const { + _list_next = NULL; _serialized = false; _unloading = false; _leakp = false; + } + void set_list_next(const ListEntry* next) const { _list_next = next; } + bool is_serialized() const { return _serialized; } + void set_serialized() const { _serialized = true; } + bool is_unloading() const { return _unloading; } + void set_unloading() const { _unloading = true; } + bool is_leakp() const { return _leakp; } + void set_leakp() const { _leakp = true; } + private: + mutable const ListEntry* _list_next; + mutable bool _serialized; + mutable bool _unloading; + mutable bool _leakp; +}; + +/* + * This table maps an oop/Symbol* or a char* to the Jfr type 'Symbol'. + * + * It provides an interface over the corresponding constant pool (TYPE_SYMBOL), + * which is represented in the binary format as a sequence of checkpoint events. + * The returned id can be used as a foreign key, but please note that the id is + * epoch-relative, and is therefore only valid in the current epoch / chunk. + * The table is cleared as part of rotation. + * + * Caller must ensure mutual exclusion by means of the ClassLoaderDataGraph_lock or by safepointing. + */ +class JfrSymbolTable : public JfrCHeapObj { + template class, typename, size_t> + friend class HashTableHost; + typedef HashTableHost Symbols; + typedef HashTableHost Strings; + friend class JfrArtifactSet; + + public: + typedef Symbols::HashEntry SymbolEntry; + typedef Strings::HashEntry StringEntry; + + static traceid add(const Symbol* sym); + static traceid add(const char* str); + + private: + Symbols* _symbols; + Strings* _strings; + const SymbolEntry* _symbol_list; + const StringEntry* _string_list; + const Symbol* _symbol_query; + const char* _string_query; + traceid _id_counter; + bool _class_unload; + + JfrSymbolTable(); + ~JfrSymbolTable(); + static JfrSymbolTable* create(); + static void destroy(); + + void clear(); + void increment_checkpoint_id(); + void set_class_unload(bool class_unload); + + traceid mark(uintptr_t hash, const Symbol* sym, bool leakp); + traceid mark(const Klass* k, bool leakp); + traceid mark(const Symbol* sym, bool leakp = false); + traceid mark(const char* str, bool leakp = false); + traceid mark(uintptr_t hash, const char* str, bool leakp); + traceid bootstrap_name(bool leakp); + + bool has_entries() const { return has_symbol_entries() || has_string_entries(); } + bool has_symbol_entries() const { return _symbol_list != NULL; } + bool has_string_entries() const { return _string_list != NULL; } + + traceid mark_hidden_klass_name(const InstanceKlass* k, bool leakp); + bool is_hidden_klass(const Klass* k); + uintptr_t hidden_klass_name_hash(const InstanceKlass* ik); + + // hashtable(s) callbacks + void on_link(const SymbolEntry* entry); + bool on_equals(uintptr_t hash, const SymbolEntry* entry); + void on_unlink(const SymbolEntry* entry); + void on_link(const StringEntry* entry); + bool on_equals(uintptr_t hash, const StringEntry* entry); + void on_unlink(const StringEntry* entry); + + template + static traceid add_impl(const T* sym); + + template + void assign_id(T* entry); + + template + void iterate_symbols(Functor& functor) { + iterate(functor, _symbol_list); + } + + template + void iterate_strings(Functor& functor) { + iterate(functor, _string_list); + } + + template + void iterate(Functor& functor, const T* list) { + const T* symbol = list; + while (symbol != NULL) { + const T* next = symbol->list_next(); + functor(symbol); + symbol = next; + } + } +}; + +#endif // SHARE_JFR_SUPPORT_JFRSYMBOLTABLE_HPP diff --git a/src/hotspot/share/logging/logTag.hpp b/src/hotspot/share/logging/logTag.hpp index 53cd1d36433..13f9a93d0c8 100644 --- a/src/hotspot/share/logging/logTag.hpp +++ b/src/hotspot/share/logging/logTag.hpp @@ -72,6 +72,7 @@ LOG_TAG(event) \ LOG_TAG(exceptions) \ LOG_TAG(exit) \ + LOG_TAG(finalizer) \ LOG_TAG(fingerprint) \ DEBUG_ONLY(LOG_TAG(foreign)) \ LOG_TAG(free) \ diff --git a/src/hotspot/share/oops/instanceKlass.cpp b/src/hotspot/share/oops/instanceKlass.cpp index d562cdef21a..2bc6a7402f7 100644 --- a/src/hotspot/share/oops/instanceKlass.cpp +++ b/src/hotspot/share/oops/instanceKlass.cpp @@ -84,6 +84,7 @@ #include "runtime/reflectionUtils.hpp" #include "runtime/thread.inline.hpp" #include "services/classLoadingService.hpp" +#include "services/finalizerService.hpp" #include "services/threadService.hpp" #include "utilities/dtrace.hpp" #include "utilities/events.hpp" @@ -96,7 +97,6 @@ #include "jfr/jfrEvents.hpp" #endif - #ifdef DTRACE_ENABLED @@ -1364,8 +1364,9 @@ instanceOop InstanceKlass::register_finalizer(instanceOop i, TRAPS) { // Pass the handle as argument, JavaCalls::call expects oop as jobjects JavaValue result(T_VOID); JavaCallArguments args(h_i); - methodHandle mh (THREAD, Universe::finalizer_register_method()); + methodHandle mh(THREAD, Universe::finalizer_register_method()); JavaCalls::call(&result, mh, &args, CHECK_NULL); + MANAGEMENT_ONLY(FinalizerService::on_register(h_i(), THREAD);) return h_i(); } diff --git a/src/hotspot/share/prims/jvm.cpp b/src/hotspot/share/prims/jvm.cpp index 28f6361157c..00b3386eb5f 100644 --- a/src/hotspot/share/prims/jvm.cpp +++ b/src/hotspot/share/prims/jvm.cpp @@ -104,6 +104,9 @@ #if INCLUDE_JFR #include "jfr/jfr.hpp" #endif +#if INCLUDE_MANAGEMENT +#include "services/finalizerService.hpp" +#endif #include @@ -681,6 +684,12 @@ JVM_ENTRY(jobject, JVM_Clone(JNIEnv* env, jobject handle)) return JNIHandles::make_local(THREAD, new_obj()); JVM_END +// java.lang.ref.Finalizer //////////////////////////////////////////////////// + +JVM_ENTRY(void, JVM_ReportFinalizationComplete(JNIEnv * env, jobject finalizee)) + MANAGEMENT_ONLY(FinalizerService::on_complete(JNIHandles::resolve_non_null(finalizee), THREAD);) +JVM_END + // java.io.File /////////////////////////////////////////////////////////////// JVM_LEAF(char*, JVM_NativePath(char* path)) @@ -3853,3 +3862,4 @@ JVM_END JVM_ENTRY_NO_ENV(jint, JVM_FindSignal(const char *name)) return os::get_signal_number(name); JVM_END + diff --git a/src/hotspot/share/runtime/serviceThread.cpp b/src/hotspot/share/runtime/serviceThread.cpp index 1c6a7b7874e..6b84a02e3fe 100644 --- a/src/hotspot/share/runtime/serviceThread.cpp +++ b/src/hotspot/share/runtime/serviceThread.cpp @@ -47,6 +47,7 @@ #include "prims/resolvedMethodTable.hpp" #include "services/diagnosticArgument.hpp" #include "services/diagnosticFramework.hpp" +#include "services/finalizerService.hpp" #include "services/gcNotifier.hpp" #include "services/lowMemoryDetector.hpp" #include "services/threadIdTable.hpp" @@ -141,6 +142,7 @@ void ServiceThread::service_thread_entry(JavaThread* jt, TRAPS) { bool has_dcmd_notification_event = false; bool stringtable_work = false; bool symboltable_work = false; + bool finalizerservice_work = false; bool resolved_method_table_work = false; bool thread_id_table_work = false; bool protection_domain_table_work = false; @@ -171,6 +173,7 @@ void ServiceThread::service_thread_entry(JavaThread* jt, TRAPS) { (has_dcmd_notification_event = (!UseNotificationThread && DCmdFactory::has_pending_jmx_notification())) | (stringtable_work = StringTable::has_work()) | (symboltable_work = SymbolTable::has_work()) | + (finalizerservice_work = FinalizerService::has_work()) | (resolved_method_table_work = ResolvedMethodTable::has_work()) | (thread_id_table_work = ThreadIdTable::has_work()) | (protection_domain_table_work = SystemDictionary::pd_cache_table()->has_work()) | @@ -198,6 +201,10 @@ void ServiceThread::service_thread_entry(JavaThread* jt, TRAPS) { SymbolTable::do_concurrent_work(jt); } + if (finalizerservice_work) { + FinalizerService::do_concurrent_work(jt); + } + if (has_jvmti_events) { _jvmti_event->post(); _jvmti_event = NULL; // reset diff --git a/src/hotspot/share/services/classLoadingService.cpp b/src/hotspot/share/services/classLoadingService.cpp index 9da4496971c..16a6fd005a4 100644 --- a/src/hotspot/share/services/classLoadingService.cpp +++ b/src/hotspot/share/services/classLoadingService.cpp @@ -119,49 +119,62 @@ void ClassLoadingService::init() { } } -void ClassLoadingService::notify_class_unloaded(InstanceKlass* k) { - DTRACE_CLASSLOAD_PROBE(unloaded, k, false); - // Classes that can be unloaded must be non-shared - _classes_unloaded_count->inc(); +bool ClassLoadingService::set_verbose(bool verbose) { + MutexLocker m(Management_lock); + // verbose will be set to the previous value + LogLevelType level = verbose ? LogLevel::Info : LogLevel::Off; + LogConfiguration::configure_stdout(level, false, LOG_TAGS(class, load)); + reset_trace_class_unloading(); + return verbose; +} - if (UsePerfData) { - // add the class size - size_t size = compute_class_size(k); - _classbytes_unloaded->inc(size); +// Caller to this function must own Management_lock +void ClassLoadingService::reset_trace_class_unloading() { + assert(Management_lock->owned_by_self(), "Must own the Management_lock"); + bool value = MemoryService::get_verbose() || ClassLoadingService::get_verbose(); + LogLevelType level = value ? LogLevel::Info : LogLevel::Off; + LogConfiguration::configure_stdout(level, false, LOG_TAGS(class, unload)); +} - // Compute method size & subtract from running total. - // We are called during phase 1 of mark sweep, so it's - // still ok to iterate through Method*s here. - Array* methods = k->methods(); - for (int i = 0; i < methods->length(); i++) { - _class_methods_size->inc(-methods->at(i)->size()); - } - } +jlong ClassLoadingService::loaded_class_count() { + return _classes_loaded_count->get_value() + _shared_classes_loaded_count->get_value(); } -void ClassLoadingService::notify_class_loaded(InstanceKlass* k, bool shared_class) { - DTRACE_CLASSLOAD_PROBE(loaded, k, shared_class); - PerfCounter* classes_counter = (shared_class ? _shared_classes_loaded_count - : _classes_loaded_count); - // increment the count - classes_counter->inc(); +jlong ClassLoadingService::unloaded_class_count() { + return _classes_unloaded_count->get_value() + _shared_classes_unloaded_count->get_value(); +} - if (UsePerfData) { - PerfCounter* classbytes_counter = (shared_class ? _shared_classbytes_loaded - : _classbytes_loaded); - // add the class size - size_t size = compute_class_size(k); - classbytes_counter->inc(size); - } +jlong ClassLoadingService::loaded_class_bytes() { + return UsePerfData ? _classbytes_loaded->get_value() + _shared_classbytes_loaded->get_value() : -1; } -size_t ClassLoadingService::compute_class_size(InstanceKlass* k) { - // lifted from ClassStatistics.do_class(Klass* k) +jlong ClassLoadingService::unloaded_class_bytes() { + return UsePerfData ? _classbytes_unloaded->get_value() + _shared_classbytes_unloaded->get_value() : -1; +} + +jlong ClassLoadingService::loaded_shared_class_count() { + return _shared_classes_loaded_count->get_value(); +} - size_t class_size = 0; +jlong ClassLoadingService::unloaded_shared_class_count() { + return _shared_classes_unloaded_count->get_value(); +} - class_size += k->size(); +jlong ClassLoadingService::loaded_shared_class_bytes() { + return UsePerfData ? _shared_classbytes_loaded->get_value() : -1; +} +jlong ClassLoadingService::unloaded_shared_class_bytes() { + return UsePerfData ? _shared_classbytes_unloaded->get_value() : -1; +} + +jlong ClassLoadingService::class_method_data_size() { + return UsePerfData ? _class_methods_size->get_value() : -1; +} + +static size_t compute_class_size(InstanceKlass* k) { + // lifted from ClassStatistics.do_class(Klass* k) + size_t class_size = k->size(); if (k->is_instance_klass()) { class_size += k->methods()->size(); // FIXME: Need to count the contents of methods @@ -177,21 +190,40 @@ size_t ClassLoadingService::compute_class_size(InstanceKlass* k) { return class_size * oopSize; } -bool ClassLoadingService::set_verbose(bool verbose) { - MutexLocker m(Management_lock); - // verbose will be set to the previous value - LogLevelType level = verbose ? LogLevel::Info : LogLevel::Off; - LogConfiguration::configure_stdout(level, false, LOG_TAGS(class, load)); - reset_trace_class_unloading(); - return verbose; +void ClassLoadingService::notify_class_loaded(InstanceKlass* k, bool shared_class) { + DTRACE_CLASSLOAD_PROBE(loaded, k, shared_class); + PerfCounter* classes_counter = (shared_class ? _shared_classes_loaded_count + : _classes_loaded_count); + // increment the count + classes_counter->inc(); + + if (UsePerfData) { + PerfCounter* classbytes_counter = (shared_class ? _shared_classbytes_loaded + : _classbytes_loaded); + // add the class size + size_t size = compute_class_size(k); + classbytes_counter->inc(size); + } } -// Caller to this function must own Management_lock -void ClassLoadingService::reset_trace_class_unloading() { - assert(Management_lock->owned_by_self(), "Must own the Management_lock"); - bool value = MemoryService::get_verbose() || ClassLoadingService::get_verbose(); - LogLevelType level = value ? LogLevel::Info : LogLevel::Off; - LogConfiguration::configure_stdout(level, false, LOG_TAGS(class, unload)); +void ClassLoadingService::notify_class_unloaded(InstanceKlass* k) { + DTRACE_CLASSLOAD_PROBE(unloaded, k, false); + // Classes that can be unloaded must be non-shared + _classes_unloaded_count->inc(); + + if (UsePerfData) { + // add the class size + size_t size = compute_class_size(k); + _classbytes_unloaded->inc(size); + + // Compute method size & subtract from running total. + // We are called during phase 1 of mark sweep, so it's + // still ok to iterate through Method*s here. + Array* methods = k->methods(); + for (int i = 0; i < methods->length(); i++) { + _class_methods_size->inc(-methods->at(i)->size()); + } + } } #endif // INCLUDE_MANAGEMENT diff --git a/src/hotspot/share/services/classLoadingService.hpp b/src/hotspot/share/services/classLoadingService.hpp index db422cbf11f..f9db3da5091 100644 --- a/src/hotspot/share/services/classLoadingService.hpp +++ b/src/hotspot/share/services/classLoadingService.hpp @@ -35,7 +35,7 @@ class InstanceKlass; // VM monitoring and management support for the Class Loading subsystem class ClassLoadingService : public AllStatic { -private: + private: // Counters for classes loaded from class files static PerfCounter* _classes_loaded_count; static PerfCounter* _classes_unloaded_count; @@ -50,59 +50,20 @@ class ClassLoadingService : public AllStatic { static PerfVariable* _class_methods_size; - static size_t compute_class_size(InstanceKlass* k); - -public: - static void init(); - - static bool get_verbose() { return log_is_enabled(Info, class, load); } - static bool set_verbose(bool verbose); + public: + static void init() NOT_MANAGEMENT_RETURN; + static bool set_verbose(bool verbose) NOT_MANAGEMENT_RETURN_(false); static void reset_trace_class_unloading() NOT_MANAGEMENT_RETURN; - - static jlong loaded_class_count() { - return _classes_loaded_count->get_value() + _shared_classes_loaded_count->get_value(); - } - static jlong unloaded_class_count() { - return _classes_unloaded_count->get_value() + _shared_classes_unloaded_count->get_value(); - } - static jlong loaded_class_bytes() { - if (UsePerfData) { - return _classbytes_loaded->get_value() + _shared_classbytes_loaded->get_value(); - } else { - return -1; - } - } - static jlong unloaded_class_bytes() { - if (UsePerfData) { - return _classbytes_unloaded->get_value() + _shared_classbytes_unloaded->get_value(); - } else { - return -1; - } - } - - static jlong loaded_shared_class_count() { - return _shared_classes_loaded_count->get_value(); - } - static jlong unloaded_shared_class_count() { - return _shared_classes_unloaded_count->get_value(); - } - static jlong loaded_shared_class_bytes() { - if (UsePerfData) { - return _shared_classbytes_loaded->get_value(); - } else { - return -1; - } - } - static jlong unloaded_shared_class_bytes() { - if (UsePerfData) { - return _shared_classbytes_unloaded->get_value(); - } else { - return -1; - } - } - static jlong class_method_data_size() { - return (UsePerfData ? _class_methods_size->get_value() : -1); - } + static jlong loaded_class_count() NOT_MANAGEMENT_RETURN_(0L); + static jlong unloaded_class_count() NOT_MANAGEMENT_RETURN_(0L); + static jlong loaded_class_bytes() NOT_MANAGEMENT_RETURN_(0L); + static jlong unloaded_class_bytes() NOT_MANAGEMENT_RETURN_(0L); + static jlong loaded_shared_class_count() NOT_MANAGEMENT_RETURN_(0L); + static jlong unloaded_shared_class_count() NOT_MANAGEMENT_RETURN_(0L); + static jlong loaded_shared_class_bytes() NOT_MANAGEMENT_RETURN_(0L); + static jlong unloaded_shared_class_bytes() NOT_MANAGEMENT_RETURN_(0L); + static jlong class_method_data_size() NOT_MANAGEMENT_RETURN_(0L); + static bool get_verbose() { return log_is_enabled(Info, class, load); } static void notify_class_loaded(InstanceKlass* k, bool shared_class) NOT_MANAGEMENT_RETURN; diff --git a/src/hotspot/share/services/finalizerService.cpp b/src/hotspot/share/services/finalizerService.cpp new file mode 100644 index 00000000000..557d4511280 --- /dev/null +++ b/src/hotspot/share/services/finalizerService.cpp @@ -0,0 +1,313 @@ +/* + * Copyright (c) 2021, 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. + * + */ + +#include "precompiled.hpp" +#include "utilities/macros.hpp" +#if INCLUDE_MANAGEMENT +#include "classfile/classLoaderDataGraph.inline.hpp" +#include "memory/resourceArea.hpp" +#include "logging/log.hpp" +#include "runtime/atomic.hpp" +#include "runtime/interfaceSupport.inline.hpp" +#include "runtime/mutexLocker.hpp" +#include "runtime/synchronizer.hpp" +#include "runtime/thread.inline.hpp" +#include "services/finalizerService.hpp" +#include "utilities/concurrentHashTableTasks.inline.hpp" +#include "utilities/debug.hpp" + +FinalizerEntry::FinalizerEntry(const InstanceKlass* ik) : + _ik(ik), + _objects_on_heap(0), + _total_finalizers_run(0) {} + +const InstanceKlass* FinalizerEntry::klass() const { + return _ik; +} + +uintptr_t FinalizerEntry::objects_on_heap() const { + return Atomic::load(&_objects_on_heap); +} + +uintptr_t FinalizerEntry::total_finalizers_run() const { + return Atomic::load(&_total_finalizers_run); +} + +void FinalizerEntry::on_register() { + Atomic::inc(&_objects_on_heap, memory_order_relaxed); +} + +void FinalizerEntry::on_complete() { + Atomic::inc(&_total_finalizers_run, memory_order_relaxed); + Atomic::dec(&_objects_on_heap, memory_order_relaxed); +} + +static inline uintx hash_function(const InstanceKlass* ik) { + assert(ik != nullptr, "invariant"); + return primitive_hash(ik); +} + +static inline uintx hash_function(const FinalizerEntry* fe) { + return hash_function(fe->klass()); +} + +class FinalizerEntryLookup : StackObj { + private: + const InstanceKlass* const _ik; + public: + FinalizerEntryLookup(const InstanceKlass* ik) : _ik(ik) {} + uintx get_hash() const { return hash_function(_ik); } + bool equals(FinalizerEntry** value, bool* is_dead) { + assert(value != nullptr, "invariant"); + assert(*value != nullptr, "invariant"); + return (*value)->klass() == _ik; + } +}; + +class FinalizerTableConfig : public AllStatic { + public: + typedef FinalizerEntry* Value; // value of the Node in the hashtable + + static uintx get_hash(Value const& value, bool* is_dead) { + return hash_function(value); + } + static void* allocate_node(void* context, size_t size, Value const& value) { + return AllocateHeap(size, mtServiceability); + } + static void free_node(void* context, void* memory, Value const& value) { + FreeHeap(memory); + } +}; + +typedef ConcurrentHashTable FinalizerHashtable; +static FinalizerHashtable* _table = nullptr; +static const size_t DEFAULT_TABLE_SIZE = 2048; +// 2^24 is max size, like StringTable. +static const size_t MAX_SIZE = 24; +static volatile bool _has_work = false; + +static size_t ceil_log2(size_t value) { + size_t ret; + for (ret = 1; ((size_t)1 << ret) < value; ++ret); + return ret; +} + +class FinalizerEntryLookupResult { + private: + FinalizerEntry* _result; + public: + FinalizerEntryLookupResult() : _result(nullptr) {} + void operator()(FinalizerEntry* node) { + assert(node != nullptr, "invariant"); + _result = node; + } + FinalizerEntry* result() const { return _result; } +}; + +class FinalizerEntryLookupGet { + private: + FinalizerEntry* _result; + public: + FinalizerEntryLookupGet() : _result(nullptr) {} + void operator()(FinalizerEntry** node) { + assert(node != nullptr, "invariant"); + _result = *node; + } + FinalizerEntry* result() const { return _result; } +}; + +static inline void set_has_work(bool value) { + Atomic::store(&_has_work, value); +} + +static inline bool has_work() { + return Atomic::load(&_has_work); +} + +static void request_resize() { + if (!has_work()) { + MutexLocker ml(Service_lock, Mutex::_no_safepoint_check_flag); + if (!has_work()) { + set_has_work(true); + Service_lock->notify_all(); + } + } +} + +static FinalizerEntry* add_to_table_if_needed(const InstanceKlass* ik, Thread* thread) { + FinalizerEntryLookup lookup(ik); + FinalizerEntry* entry = nullptr; + bool grow_hint = false; + do { + // We have looked up the entry once, proceed with insertion. + entry = new FinalizerEntry(ik); + if (_table->insert(thread, lookup, entry, &grow_hint)) { + break; + } + // In case another thread did a concurrent add, return value already in the table. + // This could fail if the entry got deleted concurrently, so loop back until success. + FinalizerEntryLookupGet felg; + if (_table->get(thread, lookup, felg, &grow_hint)) { + entry = felg.result(); + break; + } + } while (true); + if (grow_hint) { + request_resize(); + } + assert(entry != nullptr, "invariant"); + return entry; +} + +static void do_table_concurrent_work(JavaThread* jt) { + if (!_table->is_max_size_reached()) { + FinalizerHashtable::GrowTask gt(_table); + if (!gt.prepare(jt)) { + return; + } + while (gt.do_task(jt)) { + gt.pause(jt); + { + ThreadBlockInVM tbivm(jt); + } + gt.cont(jt); + } + gt.done(jt); + } + set_has_work(false); +} + +bool FinalizerService::has_work() { + return ::has_work(); +} + +void FinalizerService::do_concurrent_work(JavaThread* service_thread) { + assert(service_thread != nullptr, "invariant"); + assert(has_work(), "invariant"); + do_table_concurrent_work(service_thread); +} + +void FinalizerService::init() { + assert(_table == nullptr, "invariant"); + const size_t start_size_log_2 = ceil_log2(DEFAULT_TABLE_SIZE); + _table = new FinalizerHashtable(start_size_log_2, MAX_SIZE); +} + +static FinalizerEntry* lookup_entry(const InstanceKlass* ik, Thread* thread) { + FinalizerEntryLookup lookup(ik); + FinalizerEntryLookupGet felg; + _table->get(thread, lookup, felg); + return felg.result(); +} + +const FinalizerEntry* FinalizerService::lookup(const InstanceKlass* ik, Thread* thread) { + assert(ik != nullptr, "invariant"); + assert(thread != nullptr, "invariant"); + assert(ik->has_finalizer(), "invariant"); + return lookup_entry(ik, thread); +} + +// Add if not exist. +static FinalizerEntry* get_entry(const InstanceKlass* ik, Thread* thread) { + assert(ik != nullptr, "invariant"); + assert(ik->has_finalizer(), "invariant"); + FinalizerEntry* const entry = lookup_entry(ik, thread); + return entry != nullptr ? entry : add_to_table_if_needed(ik, thread); +} + +static FinalizerEntry* get_entry(oop finalizee, Thread* thread) { + assert(finalizee != nullptr, "invariant"); + assert(finalizee->is_instance(), "invariant"); + return get_entry(InstanceKlass::cast(finalizee->klass()), thread); +} + +static void log_registered(oop finalizee, Thread* thread) { + ResourceMark rm(thread); + const intptr_t identity_hash = ObjectSynchronizer::FastHashCode(thread, finalizee); + log_info(finalizer)("Registered object (" INTPTR_FORMAT ") of class %s as finalizable", identity_hash, finalizee->klass()->external_name()); +} + +void FinalizerService::on_register(oop finalizee, Thread* thread) { + FinalizerEntry* const fe = get_entry(finalizee, thread); + assert(fe != nullptr, "invariant"); + fe->on_register(); + if (log_is_enabled(Info, finalizer)) { + log_registered(finalizee, thread); + } +} + +static void log_completed(oop finalizee, Thread* thread) { + ResourceMark rm(thread); + const intptr_t identity_hash = ObjectSynchronizer::FastHashCode(thread, finalizee); + log_info(finalizer)("Finalizer was run for object (" INTPTR_FORMAT ") of class %s", identity_hash, finalizee->klass()->external_name()); +} + +void FinalizerService::on_complete(oop finalizee, JavaThread* finalizer_thread) { + FinalizerEntry* const fe = get_entry(finalizee, finalizer_thread); + assert(fe != nullptr, "invariant"); + fe->on_complete(); + if (log_is_enabled(Info, finalizer)) { + log_completed(finalizee, finalizer_thread); + } +} + +class FinalizerScan : public StackObj { + private: + FinalizerEntryClosure* _closure; + public: + FinalizerScan(FinalizerEntryClosure* closure) : _closure(closure) {} + bool operator()(FinalizerEntry** fe) { + return _closure->do_entry(*fe); + } +}; + +void FinalizerService::do_entries(FinalizerEntryClosure* closure, Thread* thread) { + assert(closure != nullptr, "invariant"); + FinalizerScan scan(closure); + _table->do_scan(thread, scan); +} + +static bool remove_entry(const InstanceKlass* ik) { + assert(ik != nullptr, "invariant"); + FinalizerEntryLookup lookup(ik); + return _table->remove(Thread::current(), lookup); +} + +static void on_unloading(Klass* klass) { + assert(klass != nullptr, "invariant"); + if (!klass->is_instance_klass()) { + return; + } + const InstanceKlass* const ik = InstanceKlass::cast(klass); + if (ik->has_finalizer()) { + remove_entry(ik); + } +} + +void FinalizerService::purge_unloaded() { + assert_locked_or_safepoint(ClassLoaderDataGraph_lock); + ClassLoaderDataGraph::classes_unloading_do(&on_unloading); +} + +#endif // INCLUDE_MANAGEMENT diff --git a/src/hotspot/share/services/finalizerService.hpp b/src/hotspot/share/services/finalizerService.hpp new file mode 100644 index 00000000000..74c4452e08a --- /dev/null +++ b/src/hotspot/share/services/finalizerService.hpp @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2021, 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. + * + */ + +#ifndef SHARE_SERVICES_FINALIZERSERVICE_HPP +#define SHARE_SERVICES_FINALIZERSERVICE_HPP + +#include "memory/allocation.hpp" +#include "oops/oopsHierarchy.hpp" + +class InstanceKlass; +class JavaThread; +class Thread; + +class FinalizerEntry : public CHeapObj { + private: + const InstanceKlass* const _ik; + uintptr_t _objects_on_heap; + uintptr_t _total_finalizers_run; + public: + FinalizerEntry(const InstanceKlass* ik); + const InstanceKlass* klass() const NOT_MANAGEMENT_RETURN_(nullptr); + uintptr_t objects_on_heap() const NOT_MANAGEMENT_RETURN_(0L); + uintptr_t total_finalizers_run() const NOT_MANAGEMENT_RETURN_(0L); + void on_register() NOT_MANAGEMENT_RETURN; + void on_complete() NOT_MANAGEMENT_RETURN; +}; + +class FinalizerEntryClosure : public StackObj { + public: + virtual bool do_entry(const FinalizerEntry* fe) = 0; +}; + +class FinalizerService : AllStatic { + friend class ServiceThread; + private: + static bool has_work() NOT_MANAGEMENT_RETURN_(false); + static void do_concurrent_work(JavaThread* service_thread) NOT_MANAGEMENT_RETURN; + public: + static void init() NOT_MANAGEMENT_RETURN; + static void purge_unloaded() NOT_MANAGEMENT_RETURN; + static void on_register(oop finalizee, Thread* thread) NOT_MANAGEMENT_RETURN; + static void on_complete(oop finalizee, JavaThread* finalizer_thread) NOT_MANAGEMENT_RETURN; + static void do_entries(FinalizerEntryClosure* closure, Thread* thread) NOT_MANAGEMENT_RETURN; + static const FinalizerEntry* lookup(const InstanceKlass* ik, Thread* thread) NOT_MANAGEMENT_RETURN_(nullptr); +}; + +#endif // SHARE_SERVICES_FINALIZERSERVICE_HPP diff --git a/src/hotspot/share/services/management.cpp b/src/hotspot/share/services/management.cpp index a6c1efebac5..09c8750f6f9 100644 --- a/src/hotspot/share/services/management.cpp +++ b/src/hotspot/share/services/management.cpp @@ -55,6 +55,7 @@ #include "services/classLoadingService.hpp" #include "services/diagnosticCommand.hpp" #include "services/diagnosticFramework.hpp" +#include "services/finalizerService.hpp" #include "services/writeableFlags.hpp" #include "services/heapDumper.hpp" #include "services/lowMemoryDetector.hpp" @@ -94,6 +95,7 @@ void management_init() { ThreadService::init(); RuntimeService::init(); ClassLoadingService::init(); + FinalizerService::init(); #else ThreadService::init(); #endif // INCLUDE_MANAGEMENT @@ -206,6 +208,15 @@ InstanceKlass* Management::initialize_klass(Klass* k, TRAPS) { return ik; } + +void Management::record_vm_init_completed() { + // Initialize the timestamp to get the current time + _vm_init_done_time->set_value(os::javaTimeMillis()); + + // Update the timestamp to the vm init done time + _stamp.update(); +} + void Management::record_vm_startup_time(jlong begin, jlong duration) { // if the performance counter is not initialized, // then vm initialization failed; simply return. @@ -216,6 +227,14 @@ void Management::record_vm_startup_time(jlong begin, jlong duration) { PerfMemory::set_accessible(true); } +jlong Management::begin_vm_creation_time() { + return _begin_vm_creation_time->get_value(); +} + +jlong Management::vm_init_done_time() { + return _vm_init_done_time->get_value(); +} + jlong Management::timestamp() { TimeStamp t; t.update(); diff --git a/src/hotspot/share/services/management.hpp b/src/hotspot/share/services/management.hpp index 96b50eb3e61..8c5be096b3c 100644 --- a/src/hotspot/share/services/management.hpp +++ b/src/hotspot/share/services/management.hpp @@ -70,20 +70,10 @@ class Management : public AllStatic { static void record_vm_startup_time(jlong begin, jlong duration) NOT_MANAGEMENT_RETURN; - static void record_vm_init_completed() { - // Initialize the timestamp to get the current time - _vm_init_done_time->set_value(os::javaTimeMillis()); + static void record_vm_init_completed() NOT_MANAGEMENT_RETURN; - // Update the timestamp to the vm init done time - _stamp.update(); - } - - static jlong begin_vm_creation_time() { - return _begin_vm_creation_time->get_value(); - } - static jlong vm_init_done_time() { - return _vm_init_done_time->get_value(); - } + static jlong begin_vm_creation_time() NOT_MANAGEMENT_RETURN_(0L); + static jlong vm_init_done_time() NOT_MANAGEMENT_RETURN_(0L); // methods to return a Klass*. static InstanceKlass* java_lang_management_ThreadInfo_klass(TRAPS); diff --git a/src/hotspot/share/utilities/macros.hpp b/src/hotspot/share/utilities/macros.hpp index 501ba0dbabc..9b362b88c11 100644 --- a/src/hotspot/share/utilities/macros.hpp +++ b/src/hotspot/share/utilities/macros.hpp @@ -126,9 +126,11 @@ #if INCLUDE_MANAGEMENT #define NOT_MANAGEMENT_RETURN /* next token must be ; */ #define NOT_MANAGEMENT_RETURN_(code) /* next token must be ; */ +#define MANAGEMENT_ONLY(x) x #else #define NOT_MANAGEMENT_RETURN {} #define NOT_MANAGEMENT_RETURN_(code) { return code; } +#define MANAGEMENT_ONLY(x) #endif // INCLUDE_MANAGEMENT #ifndef INCLUDE_EPSILONGC diff --git a/src/java.base/share/classes/java/lang/ref/Finalizer.java b/src/java.base/share/classes/java/lang/ref/Finalizer.java index 9b402caca44..d5838b7a6b1 100644 --- a/src/java.base/share/classes/java/lang/ref/Finalizer.java +++ b/src/java.base/share/classes/java/lang/ref/Finalizer.java @@ -86,6 +86,7 @@ private void runFinalizer(JavaLangAccess jla) { assert finalizee != null; if (!(finalizee instanceof java.lang.Enum)) { jla.invokeFinalize(finalizee); + reportComplete(finalizee); // Clear stack slot containing this variable, to decrease // the chances of false retention with a conservative GC @@ -95,6 +96,8 @@ private void runFinalizer(JavaLangAccess jla) { super.clear(); } + private static native void reportComplete(Object finalizee); + /* Create a privileged secondary finalizer thread in the system thread * group for the given Runnable, and wait for it to complete. * diff --git a/src/java.base/share/native/libjava/Finalizer.c b/src/java.base/share/native/libjava/Finalizer.c new file mode 100644 index 00000000000..d0b81f63d6e --- /dev/null +++ b/src/java.base/share/native/libjava/Finalizer.c @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021, 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. + */ + +#include "jvm.h" + +#include "java_lang_ref_Finalizer.h" + +JNIEXPORT void JNICALL +Java_java_lang_ref_Finalizer_reportComplete(JNIEnv* env, jclass cls, jobject finalizee) { + JVM_ReportFinalizationComplete(env, finalizee); +} + + diff --git a/src/jdk.jfr/share/conf/jfr/default.jfc b/src/jdk.jfr/share/conf/jfr/default.jfc index 653d4235c2d..de27641ff1f 100644 --- a/src/jdk.jfr/share/conf/jfr/default.jfc +++ b/src/jdk.jfr/share/conf/jfr/default.jfc @@ -825,6 +825,11 @@ true + + true + endChunk + + diff --git a/src/jdk.jfr/share/conf/jfr/profile.jfc b/src/jdk.jfr/share/conf/jfr/profile.jfc index cb66119791f..72404855ef9 100644 --- a/src/jdk.jfr/share/conf/jfr/profile.jfc +++ b/src/jdk.jfr/share/conf/jfr/profile.jfc @@ -825,6 +825,11 @@ true + + true + endChunk + + diff --git a/test/jdk/jdk/jfr/event/runtime/TestFinalizerStatisticsEvent.java b/test/jdk/jdk/jfr/event/runtime/TestFinalizerStatisticsEvent.java new file mode 100644 index 00000000000..1024855a7f1 --- /dev/null +++ b/test/jdk/jdk/jfr/event/runtime/TestFinalizerStatisticsEvent.java @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2021, 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. + */ + +package jdk.jfr.event.runtime; + +import java.util.List; +import jdk.jfr.Recording; +import jdk.jfr.consumer.RecordedClass; +import jdk.jfr.consumer.RecordedEvent; +import jdk.test.lib.Asserts; +import jdk.test.lib.jfr.EventNames; +import jdk.test.lib.jfr.Events; +import jdk.test.lib.jfr.TestClassLoader; + +/** + * @test + * @summary The test verifies that classes overriding finalize() are represented as events. + * @key jfr + * @requires vm.hasJFR + * @library /test/lib /test/jdk + * @run main/othervm -Xlog:class+unload,finalizer -Xmx16m jdk.jfr.event.runtime.TestFinalizerStatisticsEvent + */ + +public final class TestFinalizerStatisticsEvent { + private final static String TEST_CLASS_NAME = "jdk.jfr.event.runtime.TestFinalizerStatisticsEvent$TestClassOverridingFinalize"; + private final static String TEST_CLASS_UNLOAD_NAME = "jdk.jfr.event.runtime.TestFinalizerStatisticsEvent$TestClassUnloadOverridingFinalize"; + private final static String EVENT_PATH = EventNames.FinalizerStatistics; + + // Declare as public static to prevent the compiler from optimizing away all unread writes + public static TestClassLoader unloadableClassLoader; + public static Class unloadOverrideClass; + public static Object overridingInstance; + + public static void main(String[] args) throws Throwable { + Recording recording1 = new Recording(); + recording1.enable(EVENT_PATH); + Recording recording2 = new Recording(); + recording2.enable(EVENT_PATH); + recording1.start(); + allocateAndGC(); + recording2.start(); // rotation writes an event for TEST_CLASS_NAME into recording1 + unloadableClassLoader = new TestClassLoader(); + unloadOverrideClass = unloadableClassLoader.loadClass(TEST_CLASS_UNLOAD_NAME); + unloadOverrideClass = null; + unloadableClassLoader = null; + allocateAndGC(); // the unloading of class TEST_CLASS_UNLOAD_NAME is intercepted and an event is written into both recording1 and recording2 + recording2.stop(); // rotation writes an event for TEST_CLASS_NAME into both recording1 and recording2 + allocateAndGC(); + recording1.stop(); // rotation writes an event for TEST_CLASS_NAME into recording1 which now has 4 events reflecting this test case (3 chunks + 1 unload) + + try { + verify(recording2); + verify(recording1); + } + finally { + recording2.close(); + recording1.close(); + } + } + + private static void allocateAndGC() { + overridingInstance = new TestClassOverridingFinalize(); + overridingInstance = null; + System.gc(); + } + + private static void verify(Recording recording) throws Throwable { + boolean foundTestClassName = false; + boolean foundTestClassUnloadName = false; + List events = Events.fromRecording(recording); + Events.hasEvents(events); + for (RecordedEvent event : events) { + System.out.println("Event:" + event); + RecordedClass overridingClass = event.getValue("finalizableClass"); + switch (overridingClass.getName()) { + case TEST_CLASS_NAME: { + Asserts.assertTrue(event.getString("codeSource").startsWith("file://")); + foundTestClassName = true; + break; + } + case TEST_CLASS_UNLOAD_NAME: { + Asserts.assertTrue(event.getString("codeSource").startsWith("file://")); + foundTestClassUnloadName = true; + break; + } + } + } + Asserts.assertTrue(foundTestClassName, "The class: " + TEST_CLASS_NAME + " overriding finalize() is not found"); + Asserts.assertTrue(foundTestClassUnloadName, "The class: " + TEST_CLASS_UNLOAD_NAME + " overriding finalize() is not found"); + } + + static public class TestClassOverridingFinalize { + public boolean finalized = false; + + @Override + protected void finalize() { + finalized = true; + } + } + + static public class TestClassUnloadOverridingFinalize { + public boolean finalized = false; + + @Override + protected void finalize() { + finalized = true; + } + } +} diff --git a/test/lib/jdk/test/lib/jfr/EventNames.java b/test/lib/jdk/test/lib/jfr/EventNames.java index 1ede96ebfe3..899c434a839 100644 --- a/test/lib/jdk/test/lib/jfr/EventNames.java +++ b/test/lib/jdk/test/lib/jfr/EventNames.java @@ -91,6 +91,7 @@ public class EventNames { public static final String RedefineClasses = PREFIX + "RedefineClasses"; public static final String RetransformClasses = PREFIX + "RetransformClasses"; public static final String ClassRedefinition = PREFIX + "ClassRedefinition"; + public static final String FinalizerStatistics = PREFIX + "FinalizerStatistics"; // This event is hard to test public final static String ReservedStackActivation = PREFIX + "ReservedStackActivation"; diff --git a/test/lib/jdk/test/lib/jfr/TestClassLoader.java b/test/lib/jdk/test/lib/jfr/TestClassLoader.java index fe9e25d26a9..4e463828c66 100644 --- a/test/lib/jdk/test/lib/jfr/TestClassLoader.java +++ b/test/lib/jdk/test/lib/jfr/TestClassLoader.java @@ -28,6 +28,10 @@ import java.io.DataInputStream; import java.io.IOException; import java.io.InputStream; +import java.net.URL; +import java.security.cert.Certificate; +import java.security.CodeSource; +import java.security.ProtectionDomain; /** * Custom class loader which will try to load the class via getResourceAsStream(). @@ -53,7 +57,10 @@ public Class loadClass(String name) throws ClassNotFoundException { dis = new DataInputStream(is); dis.readFully(buf); dis.close(); - return defineClass(name, buf, 0, buf.length); + URL url = getResource(resourceName); + CodeSource cs = new CodeSource(url, (Certificate[])null); + ProtectionDomain pd = new ProtectionDomain(cs, null); + return defineClass(name, buf, 0, buf.length, pd); } } catch (SecurityException e) { // This error will happen quite often (for example when loading