| /* |
| * Copyright (c) 2023, 2025, 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 "classfile/moduleEntry.hpp" |
| #include "interpreter/bytecodes.hpp" |
| #include "jfrfiles/jfrEventIds.hpp" |
| #include "jfr/jni/jfrJavaSupport.hpp" |
| #include "jfr/recorder/jfrRecorder.hpp" |
| #include "jfr/recorder/jfrEventSetting.inline.hpp" |
| #include "jfr/recorder/checkpoint/jfrCheckpointWriter.hpp" |
| #include "jfr/recorder/checkpoint/types/traceid/jfrTraceId.inline.hpp" |
| #include "jfr/recorder/repository/jfrChunkWriter.hpp" |
| #include "jfr/recorder/stacktrace/jfrStackTraceRepository.hpp" |
| #include "jfr/recorder/storage/jfrReferenceCountedStorage.hpp" |
| #include "jfr/support/jfrDeprecationEventWriter.hpp" |
| #include "jfr/support/jfrDeprecationManager.hpp" |
| #include "jfr/support/jfrKlassUnloading.hpp" |
| #include "jfr/support/jfrMethodData.hpp" |
| #include "jfr/support/jfrMethodLookup.hpp" |
| #include "jfr/utilities/jfrBlob.hpp" |
| #include "jfr/utilities/jfrLinkedList.inline.hpp" |
| #include "jfr/utilities/jfrTime.hpp" |
| #include "logging/log.hpp" |
| #include "memory/resourceArea.inline.hpp" |
| #include "oops/instanceKlass.inline.hpp" |
| #include "oops/method.inline.hpp" |
| #include "runtime/atomic.hpp" |
| #include "runtime/interfaceSupport.inline.hpp" |
| #include "runtime/thread.inline.hpp" |
| |
| // for strstr |
| #include <string.h> |
| |
| static bool _enqueue_klasses = false; |
| |
| void JfrDeprecationManager::on_recorder_stop() { |
| _enqueue_klasses = false; |
| } |
| |
| static inline traceid load_traceid(const Method* method) { |
| assert(method != nullptr, "invariant"); |
| // If the Jfr system is not yet running we only tag the artifacts, not enqueuing klasses. |
| return _enqueue_klasses ? JfrTraceId::load(method) : JfrTraceId::load_no_enqueue(method); |
| } |
| |
| JfrDeprecatedEdge::JfrDeprecatedEdge(const Method* method, Method* sender, int bci, u1 frame_type, JavaThread* jt) : |
| _invocation_time(JfrTicks::now()), |
| _stacktrace(), |
| _type_set(), |
| _next(nullptr), |
| _deprecated_ik(method->method_holder()), |
| _deprecated_methodid(load_traceid(method)), |
| _sender_ik(sender->method_holder()), |
| _sender_methodid(load_traceid(sender)), |
| // Our stacktrace will be hand-rolled into a blob. |
| // We don't need anything from the stacktrace |
| // subsystem except for a unique id. |
| _stack_trace_id(JfrStackTraceRepository::next_id()), |
| _bci(bci), |
| _linenumber(sender->line_number_from_bci(bci)), |
| _frame_type(frame_type), |
| _for_removal(method->deprecated_for_removal()) {} |
| |
| bool JfrDeprecatedEdge::has_stacktrace() const { |
| return _stacktrace.valid(); |
| } |
| |
| void JfrDeprecatedEdge::set_stacktrace(const JfrBlobHandle& blob) { |
| assert(!has_stacktrace(), "invariant"); |
| _stacktrace = blob; |
| } |
| |
| const JfrBlobHandle& JfrDeprecatedEdge::stacktrace() const { |
| assert(has_stacktrace(), "invariant"); |
| return _stacktrace; |
| } |
| |
| bool JfrDeprecatedEdge::has_type_set() const { |
| return _type_set.valid(); |
| } |
| |
| const JfrBlobHandle& JfrDeprecatedEdge::type_set() const { |
| assert(has_type_set(), "invariant"); |
| return _type_set; |
| } |
| |
| void JfrDeprecatedEdge::install_type_set(const JfrBlobHandle& type_set) { |
| assert(!has_type_set(), "invariant"); |
| _type_set = type_set; |
| } |
| |
| typedef JfrLinkedList<JfrDeprecatedEdge> DeprecatedEdgeList; |
| |
| static DeprecatedEdgeList _list; // Newly constructed edges are concurrently added to this list. |
| static DeprecatedEdgeList _pending_list; // During epoch rotation (safepoint) entries in _list are moved onto _pending_list |
| static DeprecatedEdgeList _resolved_list; // Fully resolved edges (event, stacktrace and typeset blobs). |
| |
| static JfrDeprecatedEdge* allocate_edge(const Method* method, Method* sender, int bci, u1 frame_type, JavaThread* jt) { |
| DEBUG_ONLY(JfrJavaSupport::check_java_thread_in_vm(jt);) |
| assert(method != nullptr, "invariant"); |
| assert(method->deprecated(), "invariant"); |
| assert(sender != nullptr, "invariant"); |
| assert(jt != nullptr, "invariant"); |
| return new JfrDeprecatedEdge(method, sender, bci, frame_type, jt); |
| } |
| |
| static void create_edge(const Method* method, Method* sender, int bci, u1 frame_type, JavaThread* jt) { |
| JfrDeprecatedEdge* edge; |
| // We need the JavaThread to be in _thread_in_vm when creating the edge. |
| // This is because the method artifacts needs to be tagged in the correct epoch. |
| if (jt->thread_state() != _thread_in_vm) { |
| assert(jt->is_Compiler_thread(), "invariant"); |
| // Can safepoint here. |
| ThreadInVMfromNative transition(jt); |
| edge = allocate_edge(method, sender, bci, frame_type, jt); |
| } else { |
| edge = allocate_edge(method, sender, bci, frame_type, jt); |
| } |
| _list.add(edge); |
| } |
| |
| static constexpr const size_t max_num_edges = 10000; |
| |
| static void log_max_num_edges_reached() { |
| log_info(jfr)("The number of deprecated method invocations recorded has reached a maximum limit of %zu.", max_num_edges); |
| log_info(jfr)("Deprecated method invocations will not be recorded from now on."); |
| log_info(jfr)("Reduce the number of deprecated method invocations and try again."); |
| } |
| |
| static bool max_limit_not_reached() { |
| static size_t num_edges = 0; |
| size_t compare_value; |
| do { |
| compare_value = Atomic::load(&num_edges); |
| if (compare_value == max_num_edges) { |
| return false; |
| } |
| } while (compare_value != Atomic::cmpxchg(&num_edges, compare_value, compare_value + 1)); |
| if (compare_value + 1 == max_num_edges) { |
| log_max_num_edges_reached(); |
| } |
| return true; |
| } |
| |
| /* |
| * Two cases for JDK modules as outlined by JEP 200: The Modular JDK. |
| * |
| * The modular structure of the JDK implements the following principles: |
| * 1. Standard modules, whose specifications are governed by the JCP, have names starting with the string "java.". |
| * 2. All other modules are merely part of the JDK, and have names starting with the string "jdk.". |
| * */ |
| static inline bool is_jdk_module(const char* module_name) { |
| assert(module_name != nullptr, "invariant"); |
| return strstr(module_name, "java.") == module_name || strstr(module_name, "jdk.") == module_name; |
| } |
| |
| static inline bool is_unnamed_module(const ModuleEntry* module) { |
| return module == nullptr || !module->is_named(); |
| } |
| |
| static inline bool is_jdk_module(const ModuleEntry* module, JavaThread* jt) { |
| assert(jt != nullptr, "invariant"); |
| if (is_unnamed_module(module)) { |
| return false; |
| } |
| ResourceMark rm(jt); |
| const Symbol* const module_symbol = module->name(); |
| assert(module_symbol != nullptr, "invariant"); |
| const char* const module_name = module_symbol->as_C_string(); |
| return is_jdk_module(module_name); |
| } |
| |
| static inline bool is_not_jdk_module(const ModuleEntry* module, JavaThread* jt) { |
| return !is_jdk_module(module, jt); |
| } |
| |
| static inline bool jfr_is_started_on_command_line() { |
| return JfrRecorder::is_started_on_commandline(); |
| } |
| |
| static bool should_record(const Method* method, const Method* sender, JavaThread* jt) { |
| assert(method != nullptr, "invariant"); |
| assert(method->deprecated(), "invariant"); |
| assert(sender != nullptr, "invariant"); |
| assert(!sender->is_native(), "invariant"); |
| assert(jt != nullptr, "invariant"); |
| assert(jfr_is_started_on_command_line(), "invariant"); |
| const ModuleEntry* const deprecated_module = method->method_holder()->module(); |
| // Only record invoked deprecated methods in the JDK. |
| if (is_not_jdk_module(deprecated_module, jt)) { |
| return false; |
| } |
| const ModuleEntry* const sender_module = sender->method_holder()->module(); |
| // Only record senders not in the JDK and if we are still within budget. |
| return is_not_jdk_module(sender_module, jt) && max_limit_not_reached(); |
| } |
| |
| static inline bool is_invoke_bytecode(const Method* sender, int bci) { |
| assert(sender != nullptr, "invariant"); |
| assert(sender->validate_bci(bci) >= 0, "invariant"); |
| const Bytecodes::Code bc = (Bytecodes::Code)*sender->bcp_from(bci); |
| switch (bc) { |
| case Bytecodes::_invokevirtual: |
| case Bytecodes::_invokestatic: |
| case Bytecodes::_invokeinterface: |
| case Bytecodes::_invokespecial: |
| case Bytecodes::_invokedynamic: { |
| return true; |
| } |
| default: { |
| return false; |
| } |
| } |
| return false; |
| } |
| |
| // This is the entry point for newly discovered edges in JfrResolution.cpp. |
| void JfrDeprecationManager::on_link(const Method* method, Method* sender, int bci, u1 frame_type, JavaThread* jt) { |
| assert(method != nullptr, "invariant"); |
| assert(method->deprecated(), "invariant"); |
| assert(sender != nullptr, "invariant"); |
| assert(!sender->is_native(), "invariant"); |
| assert(jt != nullptr, "invariant"); |
| assert(JfrRecorder::is_started_on_commandline(), "invariant"); |
| if (should_record(method, sender, jt)) { |
| if (is_invoke_bytecode(sender, bci)) { |
| if (!JfrMethodData::mark_deprecated_call_site(sender, bci, jt)) { |
| return; |
| } |
| } |
| create_edge(method, sender, bci, frame_type, jt); |
| } |
| } |
| |
| static void transfer_list() { |
| assert(SafepointSynchronize::is_at_safepoint(), "invariant"); |
| assert(_pending_list.is_empty(), "invariant"); |
| DeprecatedEdgeList::NodePtr head = _list.cut(); |
| assert(_list.is_empty(), "invariant"); |
| if (head != nullptr) { |
| _pending_list.add_list(head); |
| } |
| } |
| |
| void JfrDeprecationManager::on_safepoint_clear() { |
| assert(!_enqueue_klasses, "invariant"); |
| // We are now starting JFR, so begin enqueuing tagged klasses. |
| _enqueue_klasses = true; |
| JfrDeprecatedEventWriterState::on_initialization(); |
| transfer_list(); |
| } |
| |
| void JfrDeprecationManager::on_safepoint_write() { |
| assert(_enqueue_klasses, "invariant"); |
| transfer_list(); |
| } |
| |
| static bool is_klass_unloaded(traceid klass_id) { |
| assert_locked_or_safepoint(ClassLoaderDataGraph_lock); |
| return JfrKlassUnloading::is_unloaded(klass_id, true); |
| } |
| |
| static void add_to_leakp_set(const InstanceKlass* ik, traceid method_id) { |
| // The lock is needed to ensure the klass unloading lists do not grow in the middle of inspection. |
| assert_locked_or_safepoint(ClassLoaderDataGraph_lock); |
| assert(ik != nullptr, "invariant"); |
| if (is_klass_unloaded(JfrMethodLookup::klass_id(method_id))) { |
| return; |
| } |
| const Method* const method = JfrMethodLookup::lookup(ik, method_id); |
| assert(method != nullptr, "invariant"); |
| assert(method->method_holder() == ik, "invariant"); |
| JfrTraceId::load_leakp_previous_epoch(ik, method); // now has the leakp marker |
| } |
| |
| static void add_to_leakp_set(const JfrDeprecatedEdge* edge) { |
| assert(edge != nullptr, "invariant"); |
| add_to_leakp_set(edge->deprecated_ik(), edge->deprecated_methodid()); |
| add_to_leakp_set(edge->sender_ik(), edge->sender_methodid()); |
| } |
| |
| // Keeps track of nodes processed from the _pending_list. |
| static DeprecatedEdgeList::NodePtr _pending_head = nullptr; |
| static DeprecatedEdgeList::NodePtr _pending_tail = nullptr; |
| |
| inline DeprecatedEdgeList::NodePtr pending_head() { |
| return Atomic::load(&_pending_head); |
| } |
| |
| // The test for a pending head can be read concurrently from a thread doing class unloading. |
| inline static bool has_pending_head() { |
| return pending_head() != nullptr; |
| } |
| |
| inline static bool no_pending_head() { |
| return !has_pending_head(); |
| } |
| |
| inline static void set_pending_head(DeprecatedEdgeList::NodePtr head) { |
| Atomic::store(&_pending_head, head); |
| } |
| |
| class PendingListProcessor { |
| private: |
| JfrCheckpointWriter& _writer; |
| JavaThread* _jt; |
| public: |
| PendingListProcessor(JfrCheckpointWriter& writer, JavaThread* jt) : _writer(writer), _jt(jt) {} |
| bool process(DeprecatedEdgeList::NodePtr edge) { |
| assert(edge != nullptr, "invariant"); |
| JfrDeprecatedStackTraceWriter::install_stacktrace_blob(edge, _writer, _jt); |
| assert(edge->has_stacktrace(), "invariant"); |
| add_to_leakp_set(edge); |
| if (no_pending_head()) { |
| set_pending_head(edge); |
| } |
| _pending_tail = edge; |
| return true; |
| } |
| }; |
| |
| // Resets the pending head and tail. |
| // Resets blob write states for nodes on the resolved list, dirtied in the previous epoch. |
| static void reset_type_set_blobs() { |
| set_pending_head(nullptr); |
| _pending_tail = nullptr; |
| if (_resolved_list.is_nonempty()) { |
| JfrDeprecatedEventClear clear; |
| _resolved_list.iterate(clear); |
| } |
| } |
| |
| void JfrDeprecationManager::prepare_type_set(JavaThread* jt) { |
| reset_type_set_blobs(); |
| if (_pending_list.is_nonempty()) { |
| JfrCheckpointWriter writer(true /* prev epoch */, jt, false /* header */); |
| PendingListProcessor plp(writer, jt); |
| _pending_list.iterate(plp); |
| assert(has_pending_head(), "invariant"); |
| assert(_pending_tail != nullptr, "invariant"); |
| assert(_pending_tail->next() == nullptr, "invariant"); |
| // Excise already resolved edges to link them. |
| _pending_tail->set_next(_resolved_list.cut()); |
| // Re-insertion. |
| _resolved_list.add_list(pending_head()); |
| _pending_list.clear(); |
| } |
| assert(_pending_list.is_empty(), "invariant"); |
| } |
| |
| bool JfrDeprecationManager::has_unresolved_entry() { |
| return _list.is_nonempty() || has_pending_head() || _pending_list.is_nonempty(); |
| } |
| |
| static inline bool has_stacktrace() { |
| return JfrEventSetting::has_stacktrace(JfrDeprecatedInvocationEvent); |
| } |
| |
| static inline void write_events(JfrChunkWriter& cw, Thread* thread, bool on_error) { |
| assert(_resolved_list.is_nonempty(), "invariant"); |
| JfrCheckpointWriter type_set_writer(!on_error, thread, false); |
| JfrDeprecatedEventWriter ebw(cw, type_set_writer, has_stacktrace()); |
| _resolved_list.iterate(ebw); |
| } |
| |
| static inline void write_stacktraces(JfrChunkWriter& cw) { |
| assert(has_stacktrace(), "invariant"); |
| JfrDeprecatedStackTraceWriter scw(cw); |
| _resolved_list.iterate(scw); |
| } |
| |
| // First, all events are serialized, and unique type set blobs are written into the |
| // JfrCheckpoint system to be serialized to the chunk upon return. |
| // Then, we consolidate all stack trace blobs into a single TYPE_STACKTRACE checkpoint |
| // and serialize it directly to the chunk. |
| void JfrDeprecationManager::write_edges(JfrChunkWriter& cw, Thread* thread, bool on_error /* false */) { |
| if (_resolved_list.is_nonempty() && JfrEventSetting::is_enabled(JfrDeprecatedInvocationEvent)) { |
| write_events(cw, thread, on_error); |
| if (has_stacktrace()) { |
| write_stacktraces(cw); |
| } |
| } |
| } |
| |
| void JfrDeprecationManager::on_type_set(JfrChunkWriter* cw, Thread* thread) { |
| assert(_pending_list.is_empty(), "invariant"); |
| if (has_pending_head()) { |
| assert(_pending_tail != nullptr, "invariant"); |
| // Install type set blobs for the pending, i.e. unresolved nodes. |
| JfrReferenceCountedStorage::install(pending_head(), _pending_tail->next()); |
| } |
| if (cw != nullptr) { |
| write_edges(*cw, thread); |
| } |
| } |
| |
| void JfrDeprecationManager::on_level_setting_update(int64_t new_level) { |
| JfrDeprecatedEventWriterState::on_level_setting_update(new_level); |
| } |