diff --git a/src/hotspot/share/ci/ciInstanceKlass.cpp b/src/hotspot/share/ci/ciInstanceKlass.cpp index 6243258acd9b7..70d4d865b3051 100644 --- a/src/hotspot/share/ci/ciInstanceKlass.cpp +++ b/src/hotspot/share/ci/ciInstanceKlass.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 2026, 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 @@ -432,6 +432,55 @@ ciField* ciInstanceKlass::get_field_by_name(ciSymbol* name, ciSymbol* signature, return field; } +#ifdef ASSERT +static void assert_injected_field(InternalFieldStream& fs) { + assert(!fs.done(), "invarinat"); + fieldDescriptor fd = fs.field_descriptor(); + assert(fd.is_injected(), "invariant"); +} +#endif + +// ------------------------------------------------------------------ +// ciInstanceKlass::get_injected_instance_field_by_name +// +// Implements also compute_injected_fields(). +// +ciField* ciInstanceKlass::get_injected_instance_field_by_name(ciSymbol* name, ciSymbol* signature) { + VM_ENTRY_MARK; + InstanceKlass* const k = get_instanceKlass(); + const Symbol* const name_symbol = name->get_symbol(); + assert(name_symbol != nullptr, "invariant"); + const Symbol* const sig_sym = signature->get_symbol(); + assert(sig_sym != nullptr, "invariant"); + + if (_has_injected_fields == -1) { + if (super() != nullptr && super()->has_injected_fields()) { + _has_injected_fields = 1; + } + } + + ciField* injected = nullptr; + for (InternalFieldStream fs(k); !fs.done(); fs.next()) { + if (fs.access_flags().is_static()) continue; + DEBUG_ONLY(assert_injected_field(fs);) + if (_has_injected_fields == -1) { + _has_injected_fields = 1; + } + if (fs.name() == name_symbol && fs.signature() == sig_sym) { + fieldDescriptor fd = fs.field_descriptor(); + assert(fd.is_injected(), "invariant"); + injected = new (CURRENT_THREAD_ENV->arena()) ciField(&fd); + break; + } + } + + if (_has_injected_fields == -1) { + _has_injected_fields = 0; + } + + return injected; +} + // This is essentially a shortcut for: // get_field_by_offset(field_offset, is_static)->layout_type() // except this does not require allocating memory for a new ciField diff --git a/src/hotspot/share/ci/ciInstanceKlass.hpp b/src/hotspot/share/ci/ciInstanceKlass.hpp index a84c63981c9b7..7f17a0f309522 100644 --- a/src/hotspot/share/ci/ciInstanceKlass.hpp +++ b/src/hotspot/share/ci/ciInstanceKlass.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 2026, 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 @@ -215,6 +215,7 @@ class ciInstanceKlass : public ciKlass { ciInstanceKlass* get_canonical_holder(int offset); ciField* get_field_by_offset(int field_offset, bool is_static); ciField* get_field_by_name(ciSymbol* name, ciSymbol* signature, bool is_static); + ciField* get_injected_instance_field_by_name(ciSymbol* name, ciSymbol* signature); BasicType get_field_type_by_offset(int field_offset, bool is_static); // total number of nonstatic fields (including inherited): diff --git a/src/hotspot/share/classfile/javaClasses.cpp b/src/hotspot/share/classfile/javaClasses.cpp index ef1eeec14dd47..aba33ba5ea7cd 100644 --- a/src/hotspot/share/classfile/javaClasses.cpp +++ b/src/hotspot/share/classfile/javaClasses.cpp @@ -3588,6 +3588,7 @@ int java_lang_reflect_Field::_modifiers_offset; int java_lang_reflect_Field::_trusted_final_offset; int java_lang_reflect_Field::_signature_offset; int java_lang_reflect_Field::_annotations_offset; +JFR_ONLY(int java_lang_reflect_Field::_jfr_epoch_offset;) #define FIELD_FIELDS_DO(macro) \ macro(_clazz_offset, k, vmSymbols::clazz_name(), class_signature, false); \ @@ -3602,11 +3603,13 @@ int java_lang_reflect_Field::_annotations_offset; void java_lang_reflect_Field::compute_offsets() { InstanceKlass* k = vmClasses::reflect_Field_klass(); FIELD_FIELDS_DO(FIELD_COMPUTE_OFFSET); + JFR_ONLY(FIELD_INJECTED_FIELDS(INJECTED_FIELD_COMPUTE_OFFSET);) } #if INCLUDE_CDS void java_lang_reflect_Field::serialize_offsets(SerializeClosure* f) { FIELD_FIELDS_DO(FIELD_SERIALIZE_OFFSET); + JFR_ONLY(FIELD_INJECTED_FIELDS(INJECTED_FIELD_SERIALIZE_OFFSET);) } #endif @@ -3672,6 +3675,29 @@ void java_lang_reflect_Field::set_annotations(oop field, oop value) { field->obj_field_put(_annotations_offset, value); } +#if INCLUDE_JFR +u2 java_lang_reflect_Field::epoch(oop ref) { + return static_cast(ref->int_field(_jfr_epoch_offset)); +} + +int jdk_internal_event_JfrEpoch::_jfr_epoch_offset; + +void jdk_internal_event_JfrEpoch::compute_offsets() { + InstanceKlass* k = vmClasses::jfrEpoch_klass(); + JFR_EPOCH_INJECTED_FIELDS(INJECTED_FIELD_COMPUTE_OFFSET); +} + +#if INCLUDE_CDS +void jdk_internal_event_JfrEpoch::serialize_offsets(SerializeClosure* f) { + JFR_EPOCH_INJECTED_FIELDS(INJECTED_FIELD_SERIALIZE_OFFSET); +} +#endif + +u2 jdk_internal_event_JfrEpoch::epoch(oop ref) { + return static_cast(ref->int_field(_jfr_epoch_offset)); +} +#endif // INCLUDE_JFR + oop java_lang_reflect_RecordComponent::create(InstanceKlass* holder, RecordComponent* component, TRAPS) { // Allocate java.lang.reflect.RecordComponent instance HandleMark hm(THREAD); @@ -5397,6 +5423,7 @@ void java_lang_InternalError::serialize_offsets(SerializeClosure* f) { f(jdk_internal_misc_UnsafeConstants) \ f(java_lang_boxing_object) \ f(vector_VectorPayload) \ + JFR_ONLY(f(jdk_internal_event_JfrEpoch)) \ //end #define BASIC_JAVA_CLASSES_DO(f) \ diff --git a/src/hotspot/share/classfile/javaClasses.hpp b/src/hotspot/share/classfile/javaClasses.hpp index 3276d398faf04..9beec9d9489cf 100644 --- a/src/hotspot/share/classfile/javaClasses.hpp +++ b/src/hotspot/share/classfile/javaClasses.hpp @@ -820,6 +820,32 @@ class java_lang_reflect_Constructor : public java_lang_reflect_AccessibleObject }; +#if INCLUDE_JFR + +#define JFR_EPOCH_INJECTED_FIELDS(macro) \ + macro(jdk_internal_event_JfrEpoch, jfr_epoch, int_signature, false) + +class jdk_internal_event_JfrEpoch : AllStatic { + private: + static int _jfr_epoch_offset; + + static void compute_offsets(); + + public: + static void serialize_offsets(SerializeClosure* f) NOT_CDS_RETURN; + + static u2 epoch(oop field); + static int epoch_offset() { CHECK_INIT(_jfr_epoch_offset); } + + // Debugging + friend class JavaClasses; +}; + +#define FIELD_INJECTED_FIELDS(macro) \ + macro(java_lang_reflect_Field, jfr_epoch, int_signature, false) + +#endif // INCLUDE_JFR + // Interface to java.lang.reflect.Field objects class java_lang_reflect_Field : public java_lang_reflect_AccessibleObject { @@ -834,6 +860,7 @@ class java_lang_reflect_Field : public java_lang_reflect_AccessibleObject { static int _trusted_final_offset; static int _signature_offset; static int _annotations_offset; + JFR_ONLY(static int _jfr_epoch_offset;) static void compute_offsets(); @@ -864,6 +891,9 @@ class java_lang_reflect_Field : public java_lang_reflect_AccessibleObject { static void set_signature(oop constructor, oop value); static void set_annotations(oop constructor, oop value); + JFR_ONLY(static u2 epoch(oop field);) + JFR_ONLY(static int epoch_offset() { CHECK_INIT(_jfr_epoch_offset); }) + // Debugging friend class JavaClasses; }; diff --git a/src/hotspot/share/classfile/javaClassesImpl.hpp b/src/hotspot/share/classfile/javaClassesImpl.hpp index 77975598dedc7..d12b548ad6169 100644 --- a/src/hotspot/share/classfile/javaClassesImpl.hpp +++ b/src/hotspot/share/classfile/javaClassesImpl.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, 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 @@ -43,7 +43,9 @@ VTHREAD_INJECTED_FIELDS(macro) \ INTERNALERROR_INJECTED_FIELDS(macro) \ STACKCHUNK_INJECTED_FIELDS(macro) \ - CONSTANTPOOL_INJECTED_FIELDS(macro) + CONSTANTPOOL_INJECTED_FIELDS(macro) \ + JFR_ONLY(FIELD_INJECTED_FIELDS(macro)) \ + JFR_ONLY(JFR_EPOCH_INJECTED_FIELDS(macro)) #define INJECTED_FIELD_COMPUTE_OFFSET(klass, name, signature, may_be_java) \ klass::_##name##_offset = JavaClasses::compute_injected_offset(InjectedFieldID::klass##_##name##_enum); diff --git a/src/hotspot/share/classfile/vmClassMacros.hpp b/src/hotspot/share/classfile/vmClassMacros.hpp index 71d6b9f22b23a..4dfc714d9b533 100644 --- a/src/hotspot/share/classfile/vmClassMacros.hpp +++ b/src/hotspot/share/classfile/vmClassMacros.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2026, 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 @@ -193,6 +193,9 @@ /* Scoped Values */ \ do_klass(ScopedValue_Carrier_klass, java_lang_ScopedValue_Carrier ) \ \ + /* Jfr */ \ + JFR_ONLY(do_klass(jfrEpoch_klass, jdk_internal_event_JfrEpoch )) \ + \ /*end*/ #endif // SHARE_CLASSFILE_VMCLASSMACROS_HPP diff --git a/src/hotspot/share/include/jvm.h b/src/hotspot/share/include/jvm.h index f99e8f162c603..8f02dc7057d7f 100644 --- a/src/hotspot/share/include/jvm.h +++ b/src/hotspot/share/include/jvm.h @@ -329,6 +329,9 @@ JVM_FindScopedValueBindings(JNIEnv *env, jclass threadClass); JNIEXPORT jlong JNICALL JVM_GetNextThreadIdOffset(JNIEnv *env, jclass threadClass); +JNIEXPORT jboolean JNICALL +JVM_JfrEpochUpdate(JNIEnv* env, jclass jfrEpoch, jobject obj); + /* * jdk.internal.vm.Continuation */ diff --git a/src/hotspot/share/jfr/jfr.cpp b/src/hotspot/share/jfr/jfr.cpp index d9892f80b6f99..2f3db6e254a45 100644 --- a/src/hotspot/share/jfr/jfr.cpp +++ b/src/hotspot/share/jfr/jfr.cpp @@ -28,6 +28,7 @@ #include "jfr/jni/jfrJavaSupport.hpp" #include "jfr/leakprofiler/leakProfiler.hpp" #include "jfr/recorder/checkpoint/jfrCheckpointManager.hpp" +#include "jfr/recorder/checkpoint/types/traceid/jfrTraceIdEpoch.hpp" #include "jfr/recorder/jfrRecorder.hpp" #include "jfr/recorder/repository/jfrEmergencyDump.hpp" #include "jfr/recorder/repository/jfrRepository.hpp" @@ -44,7 +45,6 @@ #include "runtime/java.hpp" #include "runtime/javaThread.hpp" - bool Jfr::is_enabled() { return JfrRecorder::is_enabled(); } @@ -181,6 +181,10 @@ void Jfr::on_report_java_out_of_memory() { } } +bool Jfr::update_epoch(oop oop) { + return JfrTraceIdEpoch::update(oop); +} + #if INCLUDE_CDS void Jfr::on_restoration(const Klass* k, JavaThread* jt) { assert(k != nullptr, "invariant"); diff --git a/src/hotspot/share/jfr/jfr.hpp b/src/hotspot/share/jfr/jfr.hpp index ac6a232dda1b2..b7a64f9116cc5 100644 --- a/src/hotspot/share/jfr/jfr.hpp +++ b/src/hotspot/share/jfr/jfr.hpp @@ -80,6 +80,7 @@ class Jfr : AllStatic { static bool has_sample_request(JavaThread* jt); static void check_and_process_sample_request(JavaThread* jt); static void on_report_java_out_of_memory(); + static bool update_epoch(oop obj); CDS_ONLY(static void on_restoration(const Klass* k, JavaThread* jt);) }; diff --git a/src/hotspot/share/jfr/recorder/checkpoint/types/traceid/jfrOopTraceId.hpp b/src/hotspot/share/jfr/recorder/checkpoint/types/traceid/jfrOopTraceId.hpp index c277bacf4ffe8..48b18ad90b975 100644 --- a/src/hotspot/share/jfr/recorder/checkpoint/types/traceid/jfrOopTraceId.hpp +++ b/src/hotspot/share/jfr/recorder/checkpoint/types/traceid/jfrOopTraceId.hpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2020, 2022, Oracle and/or its affiliates. All rights reserved. +* Copyright (c) 2020, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -28,6 +28,7 @@ #include "jfr/utilities/jfrTypes.hpp" #include "memory/allocation.hpp" #include "oops/oopsHierarchy.hpp" +#include "runtime/atomicAccess.hpp" template class JfrOopTraceId : AllStatic { @@ -37,6 +38,7 @@ class JfrOopTraceId : AllStatic { static u2 current_epoch(); static void set_epoch(oop ref); static void set_epoch(oop ref, u2 epoch); + static bool cas_epoch(oop ref); static bool is_excluded(oop ref); static void exclude(oop ref); static void include(oop ref); diff --git a/src/hotspot/share/jfr/recorder/checkpoint/types/traceid/jfrOopTraceId.inline.hpp b/src/hotspot/share/jfr/recorder/checkpoint/types/traceid/jfrOopTraceId.inline.hpp index 6c023a51b914a..3d067a161cb0a 100644 --- a/src/hotspot/share/jfr/recorder/checkpoint/types/traceid/jfrOopTraceId.inline.hpp +++ b/src/hotspot/share/jfr/recorder/checkpoint/types/traceid/jfrOopTraceId.inline.hpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2020, 2022, Oracle and/or its affiliates. All rights reserved. +* Copyright (c) 2020, 2026, 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 @@ -57,6 +57,17 @@ inline void JfrOopTraceId::set_epoch(oop ref) { set_epoch(ref, JfrTraceIdEpoch::epoch_generation()); } +template +inline bool JfrOopTraceId::cas_epoch(oop ref) { + const int expected = epoch(ref); + const int current = current_epoch(); + if (expected == current) { + return false; + } + int* const epoch_address = ref->field_addr(T::epoch_offset()); + return AtomicAccess::cmpxchg(epoch_address, expected, current) == expected; +} + template inline bool JfrOopTraceId::is_excluded(oop ref) { return T::is_excluded(ref); diff --git a/src/hotspot/share/jfr/recorder/checkpoint/types/traceid/jfrTraceIdEpoch.cpp b/src/hotspot/share/jfr/recorder/checkpoint/types/traceid/jfrTraceIdEpoch.cpp index cb4b33a36487f..f08cf79a3407b 100644 --- a/src/hotspot/share/jfr/recorder/checkpoint/types/traceid/jfrTraceIdEpoch.cpp +++ b/src/hotspot/share/jfr/recorder/checkpoint/types/traceid/jfrTraceIdEpoch.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2026, 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 @@ -22,6 +22,9 @@ * */ +#include "classfile/javaClasses.inline.hpp" +#include "classfile/vmSymbols.hpp" +#include "jfr/recorder/checkpoint/types/traceid/jfrOopTraceId.inline.hpp" #include "jfr/recorder/checkpoint/types/traceid/jfrTraceIdEpoch.hpp" #include "jfr/support/jfrThreadId.inline.hpp" #include "runtime/atomicAccess.hpp" @@ -65,3 +68,28 @@ void JfrTraceIdEpoch::reset_method_tracer_tag_state() { bool JfrTraceIdEpoch::has_method_tracer_changed_tag_state() { return AtomicAccess::load_acquire(&_method_tracer_state); } + +#ifdef ASSERT +static void assert_epoch_supported_type(oop obj) { + assert(obj != nullptr, "invariant"); + assert(obj->is_instance(), "invariant"); + const InstanceKlass* const ik = InstanceKlass::cast(obj->klass()); + assert(ik != nullptr, "invariant"); + vmSymbolID sid = vmSymbols::find_sid(ik->name()); + assert(sid == vmSymbolID::java_lang_reflect_Field_enum || + sid == vmSymbolID::jdk_internal_event_JfrEpoch_enum, "invariant"); +} +#endif + +// Switch on the vmSymbolID for dispatch. +bool JfrTraceIdEpoch::update(oop obj) { + DEBUG_ONLY(assert_epoch_supported_type(obj);) + const InstanceKlass* const ik = InstanceKlass::cast(obj->klass()); + assert(ik != nullptr, "invariant"); + const vmSymbolID sid = vmSymbols::find_sid(ik->name()); + if (sid == vmSymbolID::java_lang_reflect_Field_enum) { + return JfrOopTraceId::cas_epoch(obj); + } + assert(sid == vmSymbolID::jdk_internal_event_JfrEpoch_enum, "invariant"); + return JfrOopTraceId::cas_epoch(obj); +} diff --git a/src/hotspot/share/jfr/recorder/checkpoint/types/traceid/jfrTraceIdEpoch.hpp b/src/hotspot/share/jfr/recorder/checkpoint/types/traceid/jfrTraceIdEpoch.hpp index 9e2d2f0708a80..abad80dffea5e 100644 --- a/src/hotspot/share/jfr/recorder/checkpoint/types/traceid/jfrTraceIdEpoch.hpp +++ b/src/hotspot/share/jfr/recorder/checkpoint/types/traceid/jfrTraceIdEpoch.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2026, 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 @@ -135,6 +135,9 @@ class JfrTraceIdEpoch : AllStatic { static void set_method_tracer_tag_state(); static void reset_method_tracer_tag_state(); static bool has_method_tracer_changed_tag_state(); + + static bool update(oop obj); + }; #endif // SHARE_JFR_RECORDER_CHECKPOINT_TYPES_TRACEID_JFRTRACEIDEPOCH_HPP diff --git a/src/hotspot/share/jfr/support/jfrIntrinsics.hpp b/src/hotspot/share/jfr/support/jfrIntrinsics.hpp index 31a81e7d7b544..cd158bccbc917 100644 --- a/src/hotspot/share/jfr/support/jfrIntrinsics.hpp +++ b/src/hotspot/share/jfr/support/jfrIntrinsics.hpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2012, 2025, Oracle and/or its affiliates. All rights reserved. +* Copyright (c) 2012, 2026, 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 @@ -49,11 +49,14 @@ class JfrIntrinsicSupport : AllStatic { #define JFR_TEMPLATES(template) \ template(jdk_jfr_internal_management_HiddenWait, "jdk/jfr/internal/management/HiddenWait") \ template(jdk_jfr_internal_JVM, "jdk/jfr/internal/JVM") \ - template(jdk_jfr_internal_event_EventWriter, "jdk/jfr/internal/event/EventWriter") \ + template(jdk_jfr_internal_event_EventWriter, "jdk/jfr/internal/event/EventWriter") \ template(jdk_jfr_internal_event_EventConfiguration_signature, "Ljdk/jfr/internal/event/EventConfiguration;") \ template(getEventWriter_signature, "()Ljdk/jfr/internal/event/EventWriter;") \ template(eventConfiguration_name, "eventConfiguration") \ template(commit_name, "commit") \ + template(jdk_internal_event_JfrEpoch, "jdk/internal/event/JfrEpoch") \ + template(update_JfrEpoch_signature, "(Ljdk/internal/event/JfrEpoch;)Z") \ + template(update_Field_signature, "(Ljava/lang/reflect/Field;)Z") \ #define JFR_INTRINSICS(do_intrinsic, do_class, do_name, do_signature, do_alias) \ do_intrinsic(_counterTime, jdk_jfr_internal_JVM, counterTime_name, void_long_signature, F_SN) \ @@ -62,7 +65,10 @@ class JfrIntrinsicSupport : AllStatic { do_name( getClassId_name, "getClassId") \ do_intrinsic(_getEventWriter, jdk_jfr_internal_JVM, getEventWriter_name, getEventWriter_signature, F_SN) \ do_name( getEventWriter_name, "getEventWriter") \ - do_intrinsic(_jvm_commit, jdk_jfr_internal_JVM, commit_name, long_long_signature, F_SN) + do_intrinsic(_jvm_commit, jdk_jfr_internal_JVM, commit_name, long_long_signature, F_SN) \ + do_intrinsic(_update_epoch_Field, jdk_internal_event_JfrEpoch, update_name, update_Field_signature, F_SN) \ + do_intrinsic(_update_epoch_JfrEpoch, jdk_internal_event_JfrEpoch, update_name, update_JfrEpoch_signature, F_SN) + #else // !INCLUDE_JFR #define JFR_TEMPLATES(template) diff --git a/src/hotspot/share/oops/fieldStreams.hpp b/src/hotspot/share/oops/fieldStreams.hpp index 08b04409a972a..ac18ac6c17f04 100644 --- a/src/hotspot/share/oops/fieldStreams.hpp +++ b/src/hotspot/share/oops/fieldStreams.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2026, 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 @@ -65,10 +65,12 @@ class FieldStreamBase : public StackObj { _reader.read_field_counts(&java_fields_count, &injected_fields_count); if (_limit < _index) { _limit = java_fields_count + injected_fields_count; - } else { - assert( _limit <= java_fields_count + injected_fields_count, "Safety check"); } - if (_limit != 0) { + assert(_limit <= java_fields_count + injected_fields_count, "Safety check"); + if (_index < _limit) { + if (_index != 0) { + _reader.skip_to_field_info(_index); + } _reader.read_field_info(_fi_buf); } } @@ -182,7 +184,7 @@ class JavaFieldStream : public FieldStreamBase { // Iterate over only the internal fields class InternalFieldStream : public FieldStreamBase { public: - InternalFieldStream(InstanceKlass* k): FieldStreamBase(k->fieldinfo_stream(), k->constants(), k->java_fields_count(), 0) {} + InternalFieldStream(InstanceKlass* k): FieldStreamBase(k->fieldinfo_stream(), k->constants(), k->java_fields_count(), -1) {} }; diff --git a/src/hotspot/share/opto/c2compiler.cpp b/src/hotspot/share/opto/c2compiler.cpp index 5d170f919c8c8..f38366ed74da3 100644 --- a/src/hotspot/share/opto/c2compiler.cpp +++ b/src/hotspot/share/opto/c2compiler.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 2026, 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 @@ -753,6 +753,8 @@ bool C2Compiler::is_intrinsic_supported(vmIntrinsics::ID id) { case vmIntrinsics::_counterTime: case vmIntrinsics::_getEventWriter: case vmIntrinsics::_jvm_commit: + case vmIntrinsics::_update_epoch_Field: + case vmIntrinsics::_update_epoch_JfrEpoch: #endif case vmIntrinsics::_currentTimeMillis: case vmIntrinsics::_nanoTime: diff --git a/src/hotspot/share/opto/library_call.cpp b/src/hotspot/share/opto/library_call.cpp index ff7bc2c10d3b0..a462dafc8af53 100644 --- a/src/hotspot/share/opto/library_call.cpp +++ b/src/hotspot/share/opto/library_call.cpp @@ -496,6 +496,8 @@ bool LibraryCallKit::try_to_inline(int predicate) { case vmIntrinsics::_counterTime: return inline_native_time_funcs(CAST_FROM_FN_PTR(address, JfrTime::time_function()), "counterTime"); case vmIntrinsics::_getEventWriter: return inline_native_getEventWriter(); case vmIntrinsics::_jvm_commit: return inline_native_jvm_commit(); + case vmIntrinsics::_update_epoch_Field: // fallthrough + case vmIntrinsics::_update_epoch_JfrEpoch: return inline_native_update_epoch(); #endif case vmIntrinsics::_currentTimeMillis: return inline_native_time_funcs(CAST_FROM_FN_PTR(address, os::javaTimeMillis), "currentTimeMillis"); case vmIntrinsics::_nanoTime: return inline_native_time_funcs(CAST_FROM_FN_PTR(address, os::javaTimeNanos), "nanoTime"); @@ -3703,6 +3705,121 @@ void LibraryCallKit::extend_setCurrentThread(Node* jt, Node* thread) { set_all_memory(_gvn.transform(thread_compare_mem)); } +//------------------------inline_native_update_epoch------------------ +// +// The generated code is a function of the argument type. +// +bool LibraryCallKit::inline_native_update_epoch() { + enum { _true_path = 1, _false_path = 2, PATH_LIMIT }; + + // Save input memory. + Node* input_memory_state = reset_memory(); + set_all_memory(input_memory_state); + + // Argument is an oop whose class has an injected instance field, + // called 'jfr_epoch' of type T_INT, used for holding a jfr epoch value. + Node* oop = argument(0); + const TypeInstPtr* tinst = _gvn.type(oop)->isa_instptr(); + assert(tinst != nullptr, "oop is null"); + assert(tinst->is_loaded(), "klass is not loaded"); + ciInstanceKlass* const ik = tinst->instance_klass(); + + ciField* const field = ik->get_injected_instance_field_by_name(ciSymbol::make("jfr_epoch"), + ciSymbol::make("I")); + + assert(field != nullptr, "field 'jfr_epoch' of type I not injected in klass %s", ik->name()->as_utf8()); + + const int jfr_epoch_field_offset = field->offset_in_bytes(); + Node* oop_epoch_field_offset = basic_plus_adr(oop, jfr_epoch_field_offset); + const TypePtr* adr_type = _gvn.type(oop_epoch_field_offset)->isa_ptr(); + const int alias_idx = C->get_alias_index(adr_type); + BasicType bt = field->layout_type(); + const Type * oop_epoch_field_type = Type::get_const_basic_type(bt); + + // Load the epoch value from the oop. + Node* oop_epoch = access_load_at(oop, + oop_epoch_field_offset, + adr_type, oop_epoch_field_type, + bt, IN_HEAP | MO_UNORDERED); + + // Load the current JFR epoch generation. The value is unsigned 16-bit, so we type it as T_CHAR. + Node* epoch_generation_address = makecon(TypeRawPtr::make(JfrIntrinsicSupport::epoch_generation_address())); + Node* current_epoch_generation = make_load(control(), epoch_generation_address, TypeInt::CHAR, T_CHAR, MemNode::unordered); + + // Compare the epoch in the oop against the current JFR epoch generation. + Node* const epochs_cmp = _gvn.transform(new CmpINode(current_epoch_generation, oop_epoch)); + Node* epochs_equal_test = _gvn.transform(new BoolNode(epochs_cmp, BoolTest::eq)); + IfNode* iff_epochs_equal = create_and_map_if(control(), epochs_equal_test, PROB_LIKELY(0.999), COUNT_UNKNOWN); + + // True path. + Node* epochs_are_equal = _gvn.transform(new IfTrueNode(iff_epochs_equal)); + + // False path. + Node* epochs_are_not_equal = _gvn.transform(new IfFalseNode(iff_epochs_equal)); + + set_control(_gvn.transform(epochs_are_not_equal)); + + // Attempt to cas the current JFR epoch generation into the oop epoch field. + DecoratorSet decorators = IN_HEAP; + decorators |= mo_decorator_for_access_kind(Volatile); + + Node* result = access_atomic_cmpxchg_val_at(oop, + oop_epoch_field_offset, + adr_type, alias_idx, + oop_epoch, // expected value + current_epoch_generation, // new value + oop_epoch_field_type, + bt, + decorators); + + // Compare the result of the cas operation to the expected value. + Node* const cas_cmp_to_expected_value = _gvn.transform(new CmpINode(result, oop_epoch)); + Node* cas_operation_test = _gvn.transform(new BoolNode(cas_cmp_to_expected_value, BoolTest::eq)); + IfNode* iff_cas_success = create_and_map_if(control(), cas_operation_test, PROB_LIKELY(0.999), COUNT_UNKNOWN); + + // True path. + Node* cas_success = _gvn.transform(new IfTrueNode(iff_cas_success)); + + // False path. + Node* cas_failure = _gvn.transform(new IfFalseNode(iff_cas_success)); + + // Cas result region and phi nodes. + RegionNode* cas_operation_rgn = new RegionNode(PATH_LIMIT); + record_for_igvn(cas_operation_rgn); + PhiNode* cas_operation_mem = new PhiNode(cas_operation_rgn, Type::MEMORY, TypePtr::BOTTOM); + record_for_igvn(cas_operation_mem); + PhiNode* cas_result = new PhiNode(cas_operation_rgn, TypeInt::BOOL); + record_for_igvn(cas_result); + + cas_operation_rgn->init_req(_true_path, _gvn.transform(cas_success)); + cas_operation_rgn->init_req(_false_path, _gvn.transform(cas_failure)); + cas_operation_mem->init_req(_true_path, reset_memory()); + cas_operation_mem->init_req(_false_path, input_memory_state); + cas_result->init_req(_true_path, _gvn.intcon(1)); + cas_result->init_req(_false_path, _gvn.intcon(0)); + + // Epoch compare region and phi nodes. + RegionNode* epoch_compare_rgn = new RegionNode(PATH_LIMIT); + record_for_igvn(epoch_compare_rgn); + PhiNode* epoch_compare_mem = new PhiNode(epoch_compare_rgn, Type::MEMORY, TypePtr::BOTTOM); + record_for_igvn(epoch_compare_mem); + PhiNode* result_value = new PhiNode(epoch_compare_rgn, TypeInt::BOOL); + record_for_igvn(result_value); + + epoch_compare_rgn->init_req(_true_path, _gvn.transform(epochs_are_equal)); + epoch_compare_rgn->init_req(_false_path, _gvn.transform(cas_operation_rgn)); + epoch_compare_mem->init_req(_true_path, _gvn.transform(input_memory_state)); + epoch_compare_mem->init_req(_false_path, _gvn.transform(cas_operation_mem)); + result_value->init_req(_true_path, _gvn.intcon(0)); + result_value->init_req(_false_path, _gvn.transform(cas_result)); + + // Set output state. + set_result(epoch_compare_rgn, result_value); + set_all_memory(_gvn.transform(epoch_compare_mem)); + + return true; +} + #endif // JFR_HAVE_INTRINSICS //------------------------inline_native_currentCarrierThread------------------ diff --git a/src/hotspot/share/opto/library_call.hpp b/src/hotspot/share/opto/library_call.hpp index 9aae48302cf03..88b0e2cf9b34d 100644 --- a/src/hotspot/share/opto/library_call.hpp +++ b/src/hotspot/share/opto/library_call.hpp @@ -269,6 +269,7 @@ class LibraryCallKit : public GraphKit { bool inline_native_getEventWriter(); bool inline_native_jvm_commit(); void extend_setCurrentThread(Node* jt, Node* thread); + bool inline_native_update_epoch(); #endif bool inline_native_Class_query(vmIntrinsics::ID id); bool inline_native_subtype_check(); diff --git a/src/hotspot/share/prims/jvm.cpp b/src/hotspot/share/prims/jvm.cpp index 423e1a5a1f41d..bd6eb176ee871 100644 --- a/src/hotspot/share/prims/jvm.cpp +++ b/src/hotspot/share/prims/jvm.cpp @@ -100,6 +100,7 @@ #include "services/threadService.hpp" #include "utilities/checkedCast.hpp" #include "utilities/copy.hpp" +#include "utilities/debug.hpp" #include "utilities/defaultStream.hpp" #include "utilities/dtrace.hpp" #include "utilities/events.hpp" @@ -2890,6 +2891,16 @@ JVM_ENTRY(jlong, JVM_GetNextThreadIdOffset(JNIEnv* env, jclass threadClass)) return ThreadIdentifier::unsafe_offset(); JVM_END +JVM_ENTRY(jboolean, JVM_JfrEpochUpdate(JNIEnv* env, jclass jfrEpoch, jobject obj)) +#if INCLUDE_JFR + oop oop = JNIHandles::resolve_non_null(obj); + return Jfr::update_epoch(oop) ? JNI_TRUE : JNI_FALSE; +#else + ShouldNotCallThis(); + return JNI_FALSE; +#endif +JVM_END + JVM_ENTRY(void, JVM_Interrupt(JNIEnv* env, jobject jthread)) ThreadsListHandle tlh(thread); JavaThread* receiver = nullptr; diff --git a/src/java.base/share/classes/java/lang/reflect/Field.java b/src/java.base/share/classes/java/lang/reflect/Field.java index a4f0afa01999d..de9d49dd5e665 100644 --- a/src/java.base/share/classes/java/lang/reflect/Field.java +++ b/src/java.base/share/classes/java/lang/reflect/Field.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1996, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1996, 2026, 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 @@ -1537,8 +1537,7 @@ private void postSetFinal(Class caller, boolean unreflect) { VM.initialErr().println(sb); } - // record JFR event - FinalFieldMutationEvent.offer(getDeclaringClass(), getName()); + FinalFieldMutationEvent.offer(root); } /** diff --git a/src/java.base/share/classes/jdk/internal/event/FinalFieldMutationEvent.java b/src/java.base/share/classes/jdk/internal/event/FinalFieldMutationEvent.java index 4db29d43bd62c..9c64ea4d2ab47 100644 --- a/src/java.base/share/classes/jdk/internal/event/FinalFieldMutationEvent.java +++ b/src/java.base/share/classes/jdk/internal/event/FinalFieldMutationEvent.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, 2026, 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 @@ -24,18 +24,25 @@ */ package jdk.internal.event; +import java.lang.reflect.Field; + public class FinalFieldMutationEvent extends Event { + public Class declaringClass; public String fieldName; /** - * Commit a FinalFieldMutationEvent if enabled. + * We throttle FinalFieldMutation JFR events by generating only one event per field + * during each JFR epoch. + * + * An event is emitted if enabled, the epoch encoded in the root instance differs + * from the current epoch, and a thread exclusively updates to the current epoch. */ - public static void offer(Class declaringClass, String fieldName) { - if (enabled()) { + public static void offer(Field root) { + if (enabled() && JfrEpoch.update(root)) { var event = new FinalFieldMutationEvent(); - event.declaringClass = declaringClass; - event.fieldName = fieldName; + event.declaringClass = root.getDeclaringClass(); + event.fieldName = root.getName(); event.commit(); } } diff --git a/src/java.base/share/classes/jdk/internal/event/JfrEpoch.java b/src/java.base/share/classes/jdk/internal/event/JfrEpoch.java new file mode 100644 index 0000000000000..0631f8c4f861e --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/event/JfrEpoch.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.event; + +import java.lang.reflect.Field; +import jdk.internal.vm.annotation.IntrinsicCandidate; + +/** + * The update method serves as an intrinsic for the exact argument signature. + * When extending functionality to other types, first ensure that the VM is + * configured to accept and handle that specific type. Once VM support for the new type is confirmed, + * implement an additional update method overload and decorate it with @IntrinsicCandidate. + * + * JfrEpoch is itself a supported type already configured by the VM, + * so instances can be used directly when full abstraction isn’t needed. + */ +public final class JfrEpoch { + + /** + * The update methods takes a state object as an argument and returns true + * if the epoch was exclusively updated by the current thread. + */ + + @IntrinsicCandidate + static native boolean update(JfrEpoch epoch); + + @IntrinsicCandidate + static native boolean update(Field root); +} diff --git a/src/java.base/share/native/libjava/JfrEpoch.c b/src/java.base/share/native/libjava/JfrEpoch.c new file mode 100644 index 0000000000000..1265c277647af --- /dev/null +++ b/src/java.base/share/native/libjava/JfrEpoch.c @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2026, 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 "jdk_internal_event_JfrEpoch.h" + +JNIEXPORT jboolean JNICALL +Java_jdk_internal_event_JfrEpoch_update(JNIEnv* env, jclass jfrEpoch, jobject obj) { + return JVM_JfrEpochUpdate(env, jfrEpoch, obj); +} diff --git a/src/jdk.jfr/share/classes/jdk/jfr/events/FinalFieldMutationEvent.java b/src/jdk.jfr/share/classes/jdk/jfr/events/FinalFieldMutationEvent.java index 8f90ad2c8fd9c..b66771314731d 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/events/FinalFieldMutationEvent.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/events/FinalFieldMutationEvent.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, 2026, 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 @@ -34,7 +34,9 @@ import jdk.jfr.internal.RemoveFields; @Category("Java Application") -@Label("Final Field Mutation") +@Label("Final Field Mutation Sample") +@Description("The event only represents a sample." + + "There might be more mutations of the same final field, possibly with different stack traces.") @Name(Type.EVENT_NAME_PREFIX + "FinalFieldMutation") @RemoveFields("duration") @StackFilter({ diff --git a/test/jdk/jdk/jfr/jvm/TestJdkEpochThrottle.java b/test/jdk/jdk/jfr/jvm/TestJdkEpochThrottle.java new file mode 100644 index 0000000000000..28df49051a9dc --- /dev/null +++ b/test/jdk/jdk/jfr/jvm/TestJdkEpochThrottle.java @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2026, 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.jvm; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.List; + +import jdk.jfr.Event; +import jdk.jfr.Recording; +import jdk.jfr.consumer.RecordedClass; +import jdk.jfr.consumer.RecordedEvent; +import jdk.jfr.consumer.RecordedObject; + +import jdk.test.lib.Asserts; +import jdk.test.lib.jfr.EventNames; +import jdk.test.lib.jfr.Events; + +/** + * @test + * @requires vm.flagless + * @requires vm.hasJFR + * @library /test/lib + * @run main/othervm --enable-final-field-mutation=ALL-UNNAMED jdk.jfr.jvm.TestJdkEpochThrottle + */ +public class TestJdkEpochThrottle { + + /** + * Test class with final fields. + */ + static class TestClass { + final int value0; + final int value1; + final int value2; + TestClass(int value0, int value1, int value2) { + this.value0 = value0; + this.value1 = value1; + this.value2 = value2; + } + } + + private static String EVENT_NAME = EventNames.FinalFieldMutation; + + public static void main(String... args) throws Exception { + testFieldSet(); + } + + /** + * Normally, a FinalFieldMutation event is sent on every mutation of a final field. + * With JFR epoch throttling in the JDK applied, we control event emission to + * only happen once per mutated final field, per epoch. + */ + private static void testFieldSet() throws Exception { + try (Recording firstRecording = new Recording()) { + firstRecording.enable(EVENT_NAME).withStackTrace(); + firstRecording.start(); + // Epoch 1. + var obj = new TestClass(100, 100, 100); + mutateFinalFields(obj); + Recording secondRecording = new Recording(); + secondRecording.start(); + // Epoch 2. + mutateFinalFields(obj); + secondRecording.stop(); + // Epoch 3. + mutateFinalFields(obj); + firstRecording.stop(); + // Epoch 4. + validate(secondRecording, 3); + secondRecording.close(); + validate(firstRecording, 9); + } + } + + /** + * Mutate each final field twice using distinct Field instances. + * Because epoch throttling is applied to final field mutation events, + * only the initial mutation of each final field per epoch should result in a FinalFieldMutation event. + * I.e, out of 6 final field mutations, we only see 3 FinalFieldMutation events per epoch, + * one event per field. + */ + private static void mutateFinalFields(TestClass obj) throws Exception { + for (int i = 0; i < 6; ++i) { + getTestClassField("value" + i % 3).setInt(obj, 200); + } + } + + private static Field getTestClassField(String fieldName) throws Exception { + Field field = TestClass.class.getDeclaredField(fieldName); + field.setAccessible(true); + return field; + } + + private static void validate(Recording recording, int expectedEvents) throws Exception { + List events = Events.fromRecording(recording); + Asserts.assertEquals(expectedEvents, events.size(), "Unexpected number of events."); + for (RecordedEvent event : events) { + Asserts.assertTrue(event.getEventType().getName().equals(EVENT_NAME), "invalid event type"); + Asserts.assertTrue(event.getClass("declaringClass").getName().equals(TestClass.class.getName()), "invalid declaring class"); + Asserts.assertTrue(event.getString("fieldName").startsWith("value"), "invalid field name"); + } + } +}