diff --git a/src/hotspot/share/gc/shared/allocTracer.cpp b/src/hotspot/share/gc/shared/allocTracer.cpp index db89e4cf08af29de175c481ce945532728537e15..9355f8ed495870e461c599540d0b9722d20edf86 100644 --- a/src/hotspot/share/gc/shared/allocTracer.cpp +++ b/src/hotspot/share/gc/shared/allocTracer.cpp @@ -22,7 +22,6 @@ * */ -#include "precompiled.hpp" #include "gc/shared/allocTracer.hpp" #include "jfr/jfrEvents.hpp" #include "runtime/handles.hpp" diff --git a/src/hotspot/share/gc/shared/allocTracer.hpp b/src/hotspot/share/gc/shared/allocTracer.hpp index 593d4538f86d8889380f962d5f7aa6807fef7394..2f2cfb71c63672068809efdf534a0c99b1655c65 100644 --- a/src/hotspot/share/gc/shared/allocTracer.hpp +++ b/src/hotspot/share/gc/shared/allocTracer.hpp @@ -25,14 +25,22 @@ #ifndef SHARE_VM_GC_SHARED_ALLOCTRACER_HPP #define SHARE_VM_GC_SHARED_ALLOCTRACER_HPP -#include "memory/allocation.hpp" #include "runtime/handles.hpp" +#include "gc/shared/gcId.hpp" +#include "utilities/globalDefinitions.hpp" class AllocTracer : AllStatic { + private: + static void send_opto_array_allocation_event(Klass* klass, oop obj,size_t alloc_size, Thread* thread); + static void send_opto_instance_allocation_event(Klass* klass, oop obj, Thread* thread); public: static void send_allocation_outside_tlab(Klass* klass, HeapWord* obj, size_t alloc_size, Thread* thread); static void send_allocation_in_new_tlab(Klass* klass, HeapWord* obj, size_t tlab_size, size_t alloc_size, Thread* thread); static void send_allocation_requiring_gc_event(size_t size, uint gcId); + static void opto_slow_allocation_enter(bool is_array, Thread* thread); + static void opto_slow_allocation_leave(bool is_array, Thread* thread); + static void send_slow_allocation_event(Klass* klass, oop obj,size_t alloc_size, Thread* thread); + static void send_opto_fast_allocation_event(Klass* klass, oop obj, size_t alloc_size, Thread* thread); }; #endif // SHARE_VM_GC_SHARED_ALLOCTRACER_HPP diff --git a/src/hotspot/share/gc/shared/allocTracer.inline.hpp b/src/hotspot/share/gc/shared/allocTracer.inline.hpp new file mode 100644 index 0000000000000000000000000000000000000000..09dddd9a0bd43482458a90c1accdee148be292c3 --- /dev/null +++ b/src/hotspot/share/gc/shared/allocTracer.inline.hpp @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2019 Alibaba Group Holding Limited. 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. Alibaba 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. + * + */ +#ifndef SHARE_VM_GC_INTERFACE_ALLOCTRACER_INLINE_HPP +#define SHARE_VM_GC_INTERFACE_ALLOCTRACER_INLINE_HPP + +#include "gc/shared/gcId.hpp" +#include "runtime/handles.hpp" +#include "utilities/globalDefinitions.hpp" +#include "gc/shared/allocTracer.hpp" +#include "jfr/jfrEvents.hpp" + +typedef uintptr_t TraceAddress; + +inline void AllocTracer::opto_slow_allocation_enter(bool is_array, Thread* thread) { + if (JfrOptionSet::sample_object_allocations() && ObjectProfiler::enabled()) { + assert(thread != NULL, "Invariant"); + assert(thread->is_Java_thread(), "Invariant"); + thread->jfr_thread_local()->incr_alloc_count(1); + if (is_array) { + thread->jfr_thread_local()->set_cached_event_id(JfrOptoArrayObjectAllocationEvent); + } else { + thread->jfr_thread_local()->set_cached_event_id(JfrOptoInstanceObjectAllocationEvent); + } + } +} + +inline void AllocTracer::opto_slow_allocation_leave(bool is_array, Thread* thread) { +#ifndef PRODUCT + if (JfrOptionSet::sample_object_allocations() && ObjectProfiler::enabled()) { + JfrThreadLocal* tl = thread->jfr_thread_local(); + assert(!tl->has_cached_event_id(), "Invariant"); + assert(tl->alloc_count_until_sample() >= tl ->alloc_count(), "Invariant"); + } +#endif +} + +inline void AllocTracer::send_opto_array_allocation_event(Klass* klass, oop obj, size_t alloc_size, Thread* thread) { + EventOptoArrayObjectAllocation event; + if (event.should_commit()) { + event.set_objectClass(klass); + event.set_address((TraceAddress)obj); + event.set_allocationSize(alloc_size); + event.commit(); + } +} + +inline void AllocTracer::send_opto_instance_allocation_event(Klass* klass, oop obj, Thread* thread) { + EventOptoInstanceObjectAllocation event; + if (event.should_commit()) { + event.set_objectClass(klass); + event.set_address((TraceAddress)obj); + event.commit(); + } +} + +inline void AllocTracer::send_slow_allocation_event(Klass* klass, oop obj, size_t alloc_size, Thread* thread) { + if (JfrOptionSet::sample_object_allocations()) { + assert(thread != NULL, "Illegal parameter: thread is NULL"); + assert(thread == Thread::current(), "Invariant"); + if (thread->jfr_thread_local()->has_cached_event_id()) { + assert(thread->is_Java_thread(), "Only allow to be called from java thread"); + jlong alloc_count = thread->jfr_thread_local()->alloc_count(); + jlong alloc_count_until_sample = thread->jfr_thread_local()->alloc_count_until_sample(); + assert(alloc_count > 0 || alloc_count <= alloc_count_until_sample, "Invariant"); + if (alloc_count == alloc_count_until_sample) { + JfrEventId event_id = thread->jfr_thread_local()->cached_event_id(); + if (event_id ==JfrOptoArrayObjectAllocationEvent) { + send_opto_array_allocation_event(klass, obj, alloc_size, thread); + } else if(event_id == JfrOptoInstanceObjectAllocationEvent) { + send_opto_instance_allocation_event(klass, obj, thread); + } else { + ShouldNotReachHere(); + } + jlong interval = JfrOptionSet::object_allocations_sampling_interval(); + thread->jfr_thread_local()->incr_alloc_count_until_sample(interval); + } + thread->jfr_thread_local()->clear_cached_event_id(); + } + } +} + +inline void AllocTracer::send_opto_fast_allocation_event(Klass* klass, oop obj, size_t alloc_size, Thread* thread) { + assert(JfrOptionSet::sample_object_allocations(), "Invariant"); + assert(thread != NULL, "Invariant"); + assert(thread->is_Java_thread(), "Invariant"); + assert(!thread->jfr_thread_local()->has_cached_event_id(), "Invariant"); + + if (klass->is_array_klass()) { + send_opto_array_allocation_event(klass, obj, alloc_size, thread); + } else { + send_opto_instance_allocation_event(klass, obj, thread); + } + jlong interval = JfrOptionSet::object_allocations_sampling_interval(); + thread->jfr_thread_local()->incr_alloc_count_until_sample(interval); +} + +#endif /* SHARE_VM_GC_INTERFACE_ALLOCTRACER_INLINE_HPP */ diff --git a/src/hotspot/share/gc/shared/collectedHeap.hpp b/src/hotspot/share/gc/shared/collectedHeap.hpp index 959631b888c6c572af62c3745e451190d21bb3a1..ea631a5373907f09c4f4481d9618ceea33ce61a2 100644 --- a/src/hotspot/share/gc/shared/collectedHeap.hpp +++ b/src/hotspot/share/gc/shared/collectedHeap.hpp @@ -27,6 +27,7 @@ #include "gc/shared/gcCause.hpp" #include "gc/shared/gcWhen.hpp" +#include "gc/shared/allocTracer.hpp" #include "memory/allocation.hpp" #include "runtime/handles.hpp" #include "runtime/perfData.hpp" @@ -181,6 +182,15 @@ class CollectedHeap : public CHeapObj { Z }; + // Implicit Jfr inline methods. + static void trace_slow_allocation(Klass* klass, oop obj, size_t alloc_size, Thread* thread) { + AllocTracer::send_slow_allocation_event(klass, obj, alloc_size, thread); + } + + static void trace_allocation_outside_tlab(Klass* klass, HeapWord* obj, size_t alloc_size, Thread* thread) { + AllocTracer::send_allocation_outside_tlab(klass, obj, alloc_size, thread); + } + static inline size_t filler_array_max_size() { return _filler_array_max_size; } diff --git a/src/hotspot/share/gc/shared/collectedHeap.inline.hpp b/src/hotspot/share/gc/shared/collectedHeap.inline.hpp index f34e4cf254db74cc630a4d53f0cd00fad93a3d49..ee830202de425e512d3395583dbffb13055cefed 100644 --- a/src/hotspot/share/gc/shared/collectedHeap.inline.hpp +++ b/src/hotspot/share/gc/shared/collectedHeap.inline.hpp @@ -28,6 +28,8 @@ #include "gc/shared/collectedHeap.hpp" #include "oops/oop.inline.hpp" #include "utilities/align.hpp" +#include "jfr/recorder/service/jfrOptionSet.hpp" +#include "jfr/objectprofiler/objectProfiler.hpp" inline HeapWord* CollectedHeap::align_allocation_or_fail(HeapWord* addr, HeapWord* end, diff --git a/src/hotspot/share/gc/shared/memAllocator.cpp b/src/hotspot/share/gc/shared/memAllocator.cpp index af81bc193778f2f7f6bb1c8d2e07cc83117ad483..60a3a8241d6fb2a78cab95078abba8e791bf1982 100644 --- a/src/hotspot/share/gc/shared/memAllocator.cpp +++ b/src/hotspot/share/gc/shared/memAllocator.cpp @@ -58,6 +58,7 @@ class MemAllocator::Allocation: StackObj { void notify_allocation_jvmti_sampler(); void notify_allocation_low_memory_detector(); void notify_allocation_jfr_sampler(); + void notify_allocation_jfr_object_profiling(); void notify_allocation_dtrace_sampler(); void check_for_bad_heap_word_value() const; #ifdef ASSERT @@ -250,6 +251,11 @@ void MemAllocator::Allocation::notify_allocation_jfr_sampler() { } } +void MemAllocator::Allocation::notify_allocation_jfr_object_profiling() { + size_t size_in_bytes = _allocator._word_size * HeapWordSize; + CollectedHeap::trace_slow_allocation(_allocator._klass, obj(), size_in_bytes, _thread); +} + void MemAllocator::Allocation::notify_allocation_dtrace_sampler() { if (DTraceAllocProbes) { // support for Dtrace object alloc event (no-op most of the time) @@ -264,6 +270,7 @@ void MemAllocator::Allocation::notify_allocation_dtrace_sampler() { void MemAllocator::Allocation::notify_allocation() { notify_allocation_low_memory_detector(); notify_allocation_jfr_sampler(); + notify_allocation_jfr_object_profiling(); notify_allocation_dtrace_sampler(); notify_allocation_jvmti_sampler(); } diff --git a/src/hotspot/share/jfr/dcmd/jfrDcmds.cpp b/src/hotspot/share/jfr/dcmd/jfrDcmds.cpp index 8765109416087b60cb4258e4e73ffefe667e7303..69608022afeef535fabc390f624234c7820e11d3 100644 --- a/src/hotspot/share/jfr/dcmd/jfrDcmds.cpp +++ b/src/hotspot/share/jfr/dcmd/jfrDcmds.cpp @@ -542,6 +542,7 @@ void JfrStopFlightRecordingDCmd::execute(DCmdSource source, TRAPS) { JfrConfigureFlightRecorderDCmd::JfrConfigureFlightRecorderDCmd(outputStream* output, bool heap) : DCmdWithParser(output, heap), + _on_vm_start(false), _repository_path("repositorypath", "Path to repository,.e.g \\\"My Repository\\\"", "STRING", false, NULL), _dump_path("dumppath", "Path to dump,.e.g \\\"My Dump path\\\"", "STRING", false, NULL), _stack_depth("stackdepth", "Stack Depth", "JULONG", false, "64"), @@ -550,7 +551,9 @@ JfrConfigureFlightRecorderDCmd::JfrConfigureFlightRecorderDCmd(outputStream* out _thread_buffer_size("thread_buffer_size", "Size of a thread buffer", "MEMORY SIZE", false, "8k"), _memory_size("memorysize", "Overall memory size, ", "MEMORY SIZE", false, "10m"), _max_chunk_size("maxchunksize", "Size of an individual disk chunk", "MEMORY SIZE", false, "12m"), - _sample_threads("samplethreads", "Activate Thread sampling", "BOOLEAN", false, "true") { + _sample_threads("samplethreads", "Activate Thread sampling", "BOOLEAN", false, "true"), + _sample_object_allocations("sampleobjectallocations","object allocations sampling enable / disable", "BOOLEAN", false, "false"), + _object_allocations_sampling_interval("objectallocationssamplinginterval", "object allocations sampling interval", "JLONG", false, "1024") { _dcmdparser.add_dcmd_option(&_repository_path); _dcmdparser.add_dcmd_option(&_dump_path); _dcmdparser.add_dcmd_option(&_stack_depth); @@ -560,6 +563,8 @@ JfrConfigureFlightRecorderDCmd::JfrConfigureFlightRecorderDCmd(outputStream* out _dcmdparser.add_dcmd_option(&_memory_size); _dcmdparser.add_dcmd_option(&_max_chunk_size); _dcmdparser.add_dcmd_option(&_sample_threads); + _dcmdparser.add_dcmd_option(&_sample_object_allocations); + _dcmdparser.add_dcmd_option(&_object_allocations_sampling_interval); }; int JfrConfigureFlightRecorderDCmd::num_arguments() { @@ -590,6 +595,8 @@ void JfrConfigureFlightRecorderDCmd::execute(DCmdSource source, TRAPS) { Handle h_dcmd_instance(THREAD, dcmd); assert(h_dcmd_instance.not_null(), "invariant"); + jobject on_vm_start = JfrJavaSupport::new_java_lang_Boolean(_on_vm_start, CHECK); + jstring repository_path = NULL; if (_repository_path.is_set() && _repository_path.value() != NULL) { repository_path = JfrJavaSupport::new_string(_repository_path.value(), CHECK); @@ -635,16 +642,27 @@ void JfrConfigureFlightRecorderDCmd::execute(DCmdSource source, TRAPS) { sample_threads = JfrJavaSupport::new_java_lang_Boolean(_sample_threads.value(), CHECK); } + jobject sample_object_allocations = NULL; + if (_sample_object_allocations.is_set()) { + sample_object_allocations = JfrJavaSupport::new_java_lang_Boolean(_sample_object_allocations.value(), CHECK); + } + + jobject object_allocations_sampling_interval = NULL; + if (_object_allocations_sampling_interval.is_set()) { + object_allocations_sampling_interval = JfrJavaSupport::new_java_lang_Long(_object_allocations_sampling_interval.value(), CHECK); + } + static const char klass[] = "jdk/jfr/internal/dcmd/DCmdConfigure"; static const char method[] = "execute"; - static const char signature[] = "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;" + static const char signature[] = "(Ljava/lang/Boolean;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;" "Ljava/lang/Long;Ljava/lang/Long;Ljava/lang/Long;Ljava/lang/Long;" - "Ljava/lang/Long;Ljava/lang/Boolean;)Ljava/lang/String;"; + "Ljava/lang/Long;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Long;)Ljava/lang/String;"; JfrJavaArguments execute_args(&result, klass, method, signature, CHECK); execute_args.set_receiver(h_dcmd_instance); // params + execute_args.push_jobject(on_vm_start); execute_args.push_jobject(repository_path); execute_args.push_jobject(dump_path); execute_args.push_jobject(stack_depth); @@ -654,6 +672,8 @@ void JfrConfigureFlightRecorderDCmd::execute(DCmdSource source, TRAPS) { execute_args.push_jobject(memory_size); execute_args.push_jobject(max_chunk_size); execute_args.push_jobject(sample_threads); + execute_args.push_jobject(sample_object_allocations); + execute_args.push_jobject(object_allocations_sampling_interval); JfrJavaSupport::call_virtual(&execute_args, THREAD); handle_dcmd_result(output(), (oop)result.get_jobject(), source, THREAD); diff --git a/src/hotspot/share/jfr/dcmd/jfrDcmds.hpp b/src/hotspot/share/jfr/dcmd/jfrDcmds.hpp index 15d8091a792f95d633e2e7616a45e2b20ca4e5e2..a9c6eddf11e488ae209236ecf265ce4eed016dc1 100644 --- a/src/hotspot/share/jfr/dcmd/jfrDcmds.hpp +++ b/src/hotspot/share/jfr/dcmd/jfrDcmds.hpp @@ -140,6 +140,11 @@ class JfrRuntimeOptions; class JfrConfigureFlightRecorderDCmd : public DCmdWithParser { friend class JfrOptionSet; + private: + bool _on_vm_start; + void set_on_vm_start(bool on_vm_start) { + _on_vm_start = on_vm_start; + } protected: DCmdArgument _repository_path; DCmdArgument _dump_path; @@ -150,6 +155,8 @@ class JfrConfigureFlightRecorderDCmd : public DCmdWithParser { DCmdArgument _memory_size; DCmdArgument _max_chunk_size; DCmdArgument _sample_threads; + DCmdArgument _sample_object_allocations; + DCmdArgument _object_allocations_sampling_interval; public: JfrConfigureFlightRecorderDCmd(outputStream* output, bool heap); @@ -168,6 +175,9 @@ class JfrConfigureFlightRecorderDCmd : public DCmdWithParser { } static int num_arguments(); virtual void execute(DCmdSource source, TRAPS); + bool on_vm_start() const { + return _on_vm_start; + } }; bool register_jfr_dcmds(); diff --git a/src/hotspot/share/jfr/jni/jfrJniMethod.cpp b/src/hotspot/share/jfr/jni/jfrJniMethod.cpp index b1b2e13626e133bc23c4cc33335ca6634acdf1ac..c60131db8fdbbcffb7154233d424805e200caf58 100644 --- a/src/hotspot/share/jfr/jni/jfrJniMethod.cpp +++ b/src/hotspot/share/jfr/jni/jfrJniMethod.cpp @@ -44,6 +44,7 @@ #include "jfr/instrumentation/jfrEventClassTransformer.hpp" #include "jfr/instrumentation/jfrJvmtiAgent.hpp" #include "jfr/leakprofiler/leakProfiler.hpp" +#include "jfr/objectprofiler/objectProfiler.hpp" #include "jfr/utilities/jfrJavaLog.hpp" #include "jfr/utilities/jfrTimeConverter.hpp" #include "jfr/utilities/jfrTime.hpp" @@ -111,6 +112,15 @@ NO_TRANSITION(void, jfr_set_enabled(JNIEnv* env, jobject jvm, jlong event_type_i LeakProfiler::stop(); } } + if (EventOptoInstanceObjectAllocation::eventId == event_type_id || + EventOptoArrayObjectAllocation::eventId == event_type_id) { + ThreadInVMfromNative transition(JavaThread::thread_from_jni_environment(env)); + if (JNI_TRUE == enabled) { + ObjectProfiler::start(event_type_id); + } else { + ObjectProfiler::stop(event_type_id); + } + } NO_TRANSITION_END NO_TRANSITION(void, jfr_set_file_notification(JNIEnv* env, jobject jvm, jlong threshold)) @@ -145,6 +155,14 @@ NO_TRANSITION(void, jfr_set_memory_size(JNIEnv* env, jobject jvm, jlong size)) JfrOptionSet::set_memory_size(size); NO_TRANSITION_END +NO_TRANSITION(void, jfr_set_sample_object_allocations(JNIEnv* env, jobject jvm, jboolean sampleAllocations)) + JfrOptionSet::set_sample_object_allocations(sampleAllocations); +NO_TRANSITION_END + +NO_TRANSITION(void, jfr_set_object_allocations_sampling_interval(JNIEnv* env, jobject jvm, jlong interval)) + JfrOptionSet::set_object_allocations_sampling_interval(interval); +NO_TRANSITION_END + NO_TRANSITION(jboolean, jfr_set_threshold(JNIEnv* env, jobject jvm, jlong event_type_id, jlong thresholdTicks)) return JfrEventSetting::set_threshold(event_type_id, thresholdTicks) ? JNI_TRUE : JNI_FALSE; NO_TRANSITION_END @@ -232,7 +250,7 @@ JVM_ENTRY_NO_ENV(jlong, jfr_class_id(JNIEnv* env, jclass jvm, jclass jc)) JVM_END JVM_ENTRY_NO_ENV(jlong, jfr_stacktrace_id(JNIEnv* env, jobject jvm, jint skip)) - return JfrStackTraceRepository::record(thread, skip); + return JfrStackTraceRepository::record(thread, skip, WALK_BY_DEFAULT); JVM_END JVM_ENTRY_NO_ENV(void, jfr_log(JNIEnv* env, jobject jvm, jint tag_set, jint level, jstring message)) diff --git a/src/hotspot/share/jfr/jni/jfrJniMethod.hpp b/src/hotspot/share/jfr/jni/jfrJniMethod.hpp index 7b5394a2c582d909793057bf2634de27d471ab81..4a9a3eb5f582dce7a91457e9f01db9116160d448 100644 --- a/src/hotspot/share/jfr/jni/jfrJniMethod.hpp +++ b/src/hotspot/share/jfr/jni/jfrJniMethod.hpp @@ -93,6 +93,10 @@ void JNICALL jfr_set_memory_size(JNIEnv* env, jobject jvm, jlong size); jboolean JNICALL jfr_set_threshold(JNIEnv* env, jobject jvm, jlong event_type_id, jlong thresholdTicks); +void JNICALL jfr_set_sample_object_allocations(JNIEnv* env, jobject jvm, jboolean sampleAllocations); + +void JNICALL jfr_set_object_allocations_sampling_interval(JNIEnv* env, jobject jvm, jlong interval); + void JNICALL jfr_store_metadata_descriptor(JNIEnv* env, jobject jvm, jbyteArray descriptor); jlong JNICALL jfr_id_for_thread(JNIEnv* env, jobject jvm, jobject t); diff --git a/src/hotspot/share/jfr/jni/jfrJniMethodRegistration.cpp b/src/hotspot/share/jfr/jni/jfrJniMethodRegistration.cpp index 174126034eecf3e4cb96454a5fc1562e7106e130..95ed2441123e7438d1f90fa4ea096f98a57eb018 100644 --- a/src/hotspot/share/jfr/jni/jfrJniMethodRegistration.cpp +++ b/src/hotspot/share/jfr/jni/jfrJniMethodRegistration.cpp @@ -62,6 +62,8 @@ JfrJniMethodRegistration::JfrJniMethodRegistration(JNIEnv* env) { (char*)"setThreadBufferSize", (char*)"(J)V", (void*)jfr_set_thread_buffer_size, (char*)"setMemorySize", (char*)"(J)V", (void*)jfr_set_memory_size, (char*)"setThreshold", (char*)"(JJ)Z", (void*)jfr_set_threshold, + (char*)"setSampleObjectAllocations", (char*)"(Z)V",(void*)jfr_set_sample_object_allocations, + (char*)"setObjectAllocationsSamplingInterval", (char*)"(J)V",(void*)jfr_set_object_allocations_sampling_interval, (char*)"storeMetadataDescriptor", (char*)"([B)V", (void*)jfr_store_metadata_descriptor, (char*)"getAllowedToDoEventRetransforms", (char*)"()Z", (void*)jfr_allow_event_retransforms, (char*)"isAvailable", (char*)"()Z", (void*)jfr_is_available, diff --git a/src/hotspot/share/jfr/leakprofiler/sampling/objectSampler.cpp b/src/hotspot/share/jfr/leakprofiler/sampling/objectSampler.cpp index f07d034135be2791c63162152c3b9ee092efc9b4..f86f40f2301d603bb8e4b6e75aca0b528fee0315 100644 --- a/src/hotspot/share/jfr/leakprofiler/sampling/objectSampler.cpp +++ b/src/hotspot/share/jfr/leakprofiler/sampling/objectSampler.cpp @@ -70,7 +70,7 @@ void ObjectSampler::add(HeapWord* obj, size_t allocated, JavaThread* thread) { traceid stack_trace_id = 0; unsigned int stack_trace_hash = 0; if (JfrEventSetting::has_stacktrace(EventOldObjectSample::eventId)) { - stack_trace_id = JfrStackTraceRepository::record(thread, 0, &stack_trace_hash); + stack_trace_id = JfrStackTraceRepository::record(thread, 0, WALK_BY_DEFAULT, &stack_trace_hash); thread->jfr_thread_local()->set_cached_stack_trace_id(stack_trace_id, stack_trace_hash); } diff --git a/src/hotspot/share/jfr/metadata/metadata.xml b/src/hotspot/share/jfr/metadata/metadata.xml index 290b87ebf58a0caa3a805872f9c1488315d836b0..7c98a4d0741531128fcf577cd5912b610dcc0157 100644 --- a/src/hotspot/share/jfr/metadata/metadata.xml +++ b/src/hotspot/share/jfr/metadata/metadata.xml @@ -918,6 +918,17 @@ + + + + + + + + + + + @@ -948,6 +959,7 @@ + diff --git a/src/hotspot/share/jfr/objectprofiler/objectProfiler.cpp b/src/hotspot/share/jfr/objectprofiler/objectProfiler.cpp new file mode 100644 index 0000000000000000000000000000000000000000..87692beadb76012517718ba4aee3d4ff1710e76f --- /dev/null +++ b/src/hotspot/share/jfr/objectprofiler/objectProfiler.cpp @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2019 Alibaba Group Holding Limited. 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. Alibaba 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. + * + */ +#include "runtime/vmThread.hpp" +#include "jfr/objectprofiler/objectProfiler.hpp" +#include "jfr/utilities/jfrTryLock.hpp" +#include "jfr/jfrEvents.hpp" + +volatile jint ObjectProfiler::_enabled = JNI_FALSE; +bool ObjectProfiler::_sample_instance_obj_alloc = false; +bool ObjectProfiler::_sample_array_obj_alloc = false; +#ifndef PRODUCT +volatile int ObjectProfiler::_try_lock = 0; +#endif + +void ObjectProfiler::start(jlong event_id) { +#ifndef PRODUCT + JfrTryLock try_lock(&_try_lock); + assert(try_lock.has_lock(), "Not allow contention"); +#endif + if (EventOptoInstanceObjectAllocation::eventId == event_id) { + if (!_sample_instance_obj_alloc) { + _sample_instance_obj_alloc = true; + } + } else if (EventOptoArrayObjectAllocation::eventId == event_id) { + if (!_sample_array_obj_alloc) { + _sample_array_obj_alloc = true; + } + } else { + ShouldNotReachHere(); + } + if (enabled() == JNI_TRUE) { + return; + } + OrderAccess::release_store((volatile jint*)&_enabled, JNI_TRUE); +} + +void ObjectProfiler::stop(jlong event_id) { +#ifndef PRODUCT + JfrTryLock try_lock(&_try_lock); + assert(try_lock.has_lock(), "Not allow contention"); +#endif + if (enabled() == JNI_FALSE) { + assert(!_sample_array_obj_alloc && !_sample_instance_obj_alloc, "Invariant"); + return; + } + if (EventOptoInstanceObjectAllocation::eventId == event_id) { + if (_sample_instance_obj_alloc) { + _sample_instance_obj_alloc = false; + } + } else if (EventOptoArrayObjectAllocation::eventId == event_id) { + if (_sample_array_obj_alloc) { + _sample_array_obj_alloc = false; + } + } else { + ShouldNotReachHere(); + } + bool should_enable = _sample_array_obj_alloc || _sample_instance_obj_alloc; + if (should_enable) { + return; + } + OrderAccess::release_store(&_enabled, JNI_FALSE); +} + +jint ObjectProfiler::enabled() { + return OrderAccess::load_acquire((volatile jint*)&_enabled); +} + +void* ObjectProfiler::enabled_flag_address() { + return (void*)&_enabled; +} + diff --git a/src/hotspot/share/jfr/objectprofiler/objectProfiler.hpp b/src/hotspot/share/jfr/objectprofiler/objectProfiler.hpp new file mode 100644 index 0000000000000000000000000000000000000000..b2779f09eac1850916ef045f1dadc74223503de8 --- /dev/null +++ b/src/hotspot/share/jfr/objectprofiler/objectProfiler.hpp @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2019 Alibaba Group Holding Limited. 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. Alibaba 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. + * + */ +#ifndef SHARE_VM_JFR_OBJECTROFILER_OBJECTPROFILER_HPP +#define SHARE_VM_JFR_OBJECTROFILER_OBJECTPROFILER_HPP + +#include "jni.h" + +#define ARRAY_OBJECT_SIZE_PLACE_HOLDER 0x1111baba + +#if INCLUDE_JFR +#define TRACE_OPTO_SLOW_ALLOCATION_ENTER(is_array, thread) \ + AllocTracer::opto_slow_allocation_enter(is_array, thread) + +#define TRACE_OPTO_SLOW_ALLOCATION_LEAVE(is_array, thread) \ + AllocTracer::opto_slow_allocation_leave(is_array, thread) + +#define TRACE_SLOW_ALLOCATION(klass, obj, alloc_size, thread) \ + AllocTracer::send_slow_allocation_event(klass, obj, alloc_size, thread) + +#define TRACE_DEFINE_THREAD_ALLOC_COUNT_OFFSET \ + static ByteSize alloc_count_offset() { return in_ByteSize(offset_of(JfrThreadLocal, _alloc_count)); } +#define TRACE_THREAD_ALLOC_COUNT_OFFSET \ + (JfrThreadLocal::alloc_count_offset() + THREAD_LOCAL_OFFSET_JFR) +#define TRACE_DEFINE_THREAD_ALLOC_COUNT_UNTIL_SAMPLE_OFFSET \ + static ByteSize alloc_count_until_sample_offset() { return in_ByteSize(offset_of(JfrThreadLocal, _alloc_count_until_sample)); } +#define TRACE_THREAD_ALLOC_COUNT_UNTIL_SAMPLE_OFFSET \ + (JfrThreadLocal::alloc_count_until_sample_offset() + THREAD_LOCAL_OFFSET_JFR) + +#else +#define TRACE_OPTO_SLOW_ALLOCATION_ENTER(is_array, thread) +#define TRACE_OPTO_SLOW_ALLOCATION_LEAVE(is_array, thread) +#define TRACE_SLOW_ALLOCATION(klass, obj, alloc_size, thread) +#define TRACE_DEFINE_THREAD_ALLOC_COUNT_UNTIL_SAMPLE_OFFSET +#define TRACE_THREAD_ALLOC_COUNT_UNTIL_SAMPLE_OFFSET +#define TRACE_DEFINE_THREAD_ALLOC_COUNT_OFFSET +#define TRACE_THREAD_ALLOC_COUNT_OFFSET +#endif + +class ObjectProfiler : public AllStatic { + private: + static volatile jint _enabled; + static bool _sample_instance_obj_alloc; + static bool _sample_array_obj_alloc; +#ifndef PRODUCT + static volatile int _try_lock; +#endif + + public: + static void start(jlong event_id); + static void stop(jlong event_id); + static jint enabled(); + static void* enabled_flag_address(); +}; + +#endif // SHARE_VM_JFR_OBJECTROFILER_OBJECTPROFILER_HPP diff --git a/src/hotspot/share/jfr/recorder/checkpoint/types/jfrTypeSet.cpp b/src/hotspot/share/jfr/recorder/checkpoint/types/jfrTypeSet.cpp index 336d0d0cd3b19969c0e13191b6aef44141cbb571..4987122c571cc7421716a76f2c69dbc9e2e079d8 100644 --- a/src/hotspot/share/jfr/recorder/checkpoint/types/jfrTypeSet.cpp +++ b/src/hotspot/share/jfr/recorder/checkpoint/types/jfrTypeSet.cpp @@ -39,6 +39,7 @@ #include "jfr/recorder/storage/jfrBuffer.hpp" #include "jfr/utilities/jfrHashtable.hpp" #include "jfr/utilities/jfrTypes.hpp" +#include "jfr/objectprofiler/objectProfiler.hpp" #include "memory/iterator.hpp" #include "memory/resourceArea.hpp" #include "oops/instanceKlass.hpp" @@ -146,6 +147,16 @@ int write__artifact__klass(JfrCheckpointWriter* writer, JfrArtifactSet* artifact writer->write((traceid)CREATE_SYMBOL_ID(symbol_id)); writer->write(pkg_id); writer->write((s4)klass->access_flags().get_flags()); + if (klass->is_array_klass()) { + // The object array size can not be determined statically from klass. + // It is determined by the elements length in object layout. + // So we put a place holder here to make the event parser ignore it. + writer->write((s4)ARRAY_OBJECT_SIZE_PLACE_HOLDER); + } else { + assert(klass->is_instance_klass(), "invariant"); + jint instanceSize = ((InstanceKlass*) klass)->size_helper() * HeapWordSize; + writer->write((s4)instanceSize); + } return 1; } diff --git a/src/hotspot/share/jfr/recorder/jfrEventSetting.cpp b/src/hotspot/share/jfr/recorder/jfrEventSetting.cpp index b7ed48b07422529b7589912e82b4c7a588975471..c64463ede7fc8d23432574d746cbcee0e44327cb 100644 --- a/src/hotspot/share/jfr/recorder/jfrEventSetting.cpp +++ b/src/hotspot/share/jfr/recorder/jfrEventSetting.cpp @@ -23,7 +23,9 @@ */ #include "precompiled.hpp" +#include "jfrfiles/jfrEventIds.hpp" #include "jfr/recorder/jfrEventSetting.inline.hpp" +#include "jfr/recorder/stacktrace/jfrStackTraceRepository.hpp" JfrNativeSettings JfrEventSetting::_jvm_event_settings; @@ -53,6 +55,14 @@ void JfrEventSetting::set_enabled(jlong id, bool enabled) { setting(event_id).enabled = enabled; } +StackWalkMode JfrEventSetting::stack_walk_mode(JfrEventId event_id) { + if (event_id == JfrOptoArrayObjectAllocationEvent || + event_id == JfrOptoInstanceObjectAllocationEvent) { + return WALK_BY_CURRENT_FRAME; + } + return WALK_BY_DEFAULT; +} + #ifdef ASSERT bool JfrEventSetting::bounds_check_event(jlong id) { if ((unsigned)id < NUM_RESERVED_EVENTS || (unsigned)id >= MaxJfrEventId) { diff --git a/src/hotspot/share/jfr/recorder/jfrEventSetting.hpp b/src/hotspot/share/jfr/recorder/jfrEventSetting.hpp index a8e507cace6df4b172a32892b3c8899744de7d43..d835e6962f6d7de6e7ce2bb2c698e75a90cfc82a 100644 --- a/src/hotspot/share/jfr/recorder/jfrEventSetting.hpp +++ b/src/hotspot/share/jfr/recorder/jfrEventSetting.hpp @@ -27,6 +27,7 @@ #include "jni.h" #include "jfr/utilities/jfrAllocation.hpp" +#include "jfr/recorder/stacktrace/jfrStackTraceRepository.hpp" #include "jfrfiles/jfrEventControl.hpp" // @@ -46,6 +47,7 @@ class JfrEventSetting : AllStatic { static jlong threshold(JfrEventId event_id); static bool set_cutoff(jlong event_id, jlong cutoff_ticks); static jlong cutoff(JfrEventId event_id); + static StackWalkMode stack_walk_mode(JfrEventId event_id); DEBUG_ONLY(static bool bounds_check_event(jlong id);) }; diff --git a/src/hotspot/share/jfr/recorder/service/jfrEvent.hpp b/src/hotspot/share/jfr/recorder/service/jfrEvent.hpp index 080c8484bbc6f4d7a4ddc6d72587fee5d19bf61b..ea4532cf651edb1957534b072ed5649629d36e53 100644 --- a/src/hotspot/share/jfr/recorder/service/jfrEvent.hpp +++ b/src/hotspot/share/jfr/recorder/service/jfrEvent.hpp @@ -26,6 +26,7 @@ #define SHARE_VM_JFR_RECORDER_SERVICE_JFREVENT_HPP #include "jfr/recorder/jfrEventSetting.inline.hpp" +#include "jfr/recorder/jfrEventSetting.hpp" #include "jfr/recorder/stacktrace/jfrStackTraceRepository.hpp" #include "jfr/utilities/jfrTime.hpp" #include "jfr/utilities/jfrTypes.hpp" @@ -176,7 +177,7 @@ class JfrEvent { if (tl->has_cached_stack_trace()) { writer.write(tl->cached_stack_trace_id()); } else { - writer.write(JfrStackTraceRepository::record(event_thread)); + writer.write(JfrStackTraceRepository::record(event_thread, 0, JfrEventSetting::stack_walk_mode(T::eventId))); } } else { writer.write(0); diff --git a/src/hotspot/share/jfr/recorder/service/jfrOptionSet.cpp b/src/hotspot/share/jfr/recorder/service/jfrOptionSet.cpp index 42c1e252e535af7f2607f732398b9c6d26902375..b0d632a9c8fb5a97b27d1e05c968ff4bfbc02686 100644 --- a/src/hotspot/share/jfr/recorder/service/jfrOptionSet.cpp +++ b/src/hotspot/share/jfr/recorder/service/jfrOptionSet.cpp @@ -37,6 +37,7 @@ #include "services/diagnosticFramework.hpp" #include "utilities/growableArray.hpp" #include "utilities/ostream.hpp" +#include "utilities/debug.hpp" struct ObsoleteOption { const char* name; @@ -160,6 +161,22 @@ bool JfrOptionSet::allow_event_retransforms() { return allow_retransforms() && (DumpSharedSpaces || can_retransform()); } +bool JfrOptionSet::sample_object_allocations() { + return _sample_object_allocations == JNI_TRUE; +} + +void JfrOptionSet::set_sample_object_allocations(jboolean value) { + _sample_object_allocations = value; +} + +jlong JfrOptionSet::object_allocations_sampling_interval() { + return _object_allocations_sampling_interval; +} + +void JfrOptionSet::set_object_allocations_sampling_interval(jlong value) { + _object_allocations_sampling_interval = value; +} + // default options for the dcmd parser const char* const default_repository = NULL; const char* const default_global_buffer_size = "512k"; @@ -172,6 +189,9 @@ const char* const default_stack_depth = "64"; const char* const default_retransform = "true"; const char* const default_old_object_queue_size = "256"; DEBUG_ONLY(const char* const default_sample_protection = "false";) +const char* const default_sample_object_allocations = "false"; +// the unit of this value is not time but quantity +const char* const default_object_allocations_sampling_interval = "1024"; // statics static DCmdArgument _dcmd_repository( @@ -253,6 +273,20 @@ static DCmdArgument _dcmd_retransform( true, default_retransform); +static DCmdArgument _dcmd_sampleobjectallocations( + "sampleobjectallocations", + "If object allocations should be sampled (by default false)", + "BOOLEAN", + false, + default_sample_object_allocations); + +static DCmdArgument _dcmd_objectallocationssamplinginterval( + "objectallocationssamplinginterval", + "object allocations sampling interval (by default 1024)", + "JLONG", + false, + default_object_allocations_sampling_interval); + static DCmdParser _parser; static void register_parser_options() { @@ -267,6 +301,8 @@ static void register_parser_options() { _parser.add_dcmd_option(&_dcmd_retransform); _parser.add_dcmd_option(&_dcmd_old_object_queue_size); DEBUG_ONLY(_parser.add_dcmd_option(&_dcmd_sample_protection);) + _parser.add_dcmd_option(&_dcmd_sampleobjectallocations); + _parser.add_dcmd_option(&_dcmd_objectallocationssamplinginterval); } static bool parse_flight_recorder_options_internal(TRAPS) { @@ -312,6 +348,9 @@ jboolean JfrOptionSet::_sample_protection = JNI_FALSE; #else jboolean JfrOptionSet::_sample_protection = JNI_TRUE; #endif +volatile jboolean JfrOptionSet::_sample_object_allocations = JNI_FALSE; +// the unit of this value is not time but quantity +volatile jlong JfrOptionSet::_object_allocations_sampling_interval = 1024; bool JfrOptionSet::initialize(Thread* thread) { register_parser_options(); @@ -333,6 +372,7 @@ bool JfrOptionSet::configure(TRAPS) { bufferedStream st; // delegate to DCmd execution JfrConfigureFlightRecorderDCmd configure(&st, false); + configure.set_on_vm_start(true); configure._repository_path.set_is_set(_dcmd_repository.is_set()); char* repo = _dcmd_repository.value(); if (repo != NULL) { @@ -366,6 +406,12 @@ bool JfrOptionSet::configure(TRAPS) { configure._sample_threads.set_is_set(_dcmd_sample_threads.is_set()); configure._sample_threads.set_value(_dcmd_sample_threads.value()); + configure._sample_object_allocations.set_is_set(_dcmd_sampleobjectallocations.is_set()); + configure._sample_object_allocations.set_value(_dcmd_sampleobjectallocations.value()); + + configure._object_allocations_sampling_interval.set_is_set(_dcmd_objectallocationssamplinginterval.is_set()); + configure._object_allocations_sampling_interval.set_value(_dcmd_objectallocationssamplinginterval.value()); + configure.execute(DCmd_Source_Internal, THREAD); if (HAS_PENDING_EXCEPTION) { diff --git a/src/hotspot/share/jfr/recorder/service/jfrOptionSet.hpp b/src/hotspot/share/jfr/recorder/service/jfrOptionSet.hpp index d52c565a01047cef4a981193699dfdffd786cd2f..0d414d9e9b1eb9c7b1bf182497acfc5982cbc13c 100644 --- a/src/hotspot/share/jfr/recorder/service/jfrOptionSet.hpp +++ b/src/hotspot/share/jfr/recorder/service/jfrOptionSet.hpp @@ -48,6 +48,8 @@ class JfrOptionSet : public AllStatic { static jboolean _sample_threads; static jboolean _retransform; static jboolean _sample_protection; + static volatile jboolean _sample_object_allocations; + static volatile jlong _object_allocations_sampling_interval; static bool initialize(Thread* thread); static bool configure(TRAPS); @@ -77,6 +79,10 @@ class JfrOptionSet : public AllStatic { static bool allow_event_retransforms(); static bool sample_protection(); DEBUG_ONLY(static void set_sample_protection(jboolean protection);) + static bool sample_object_allocations(); + static void set_sample_object_allocations(jboolean value); + static jlong object_allocations_sampling_interval(); + static void set_object_allocations_sampling_interval(jlong value); static bool parse_flight_recorder_option(const JavaVMOption** option, char* delimiter); static bool parse_start_flight_recording_option(const JavaVMOption** option, char* delimiter); diff --git a/src/hotspot/share/jfr/recorder/stacktrace/jfrStackTraceRepository.cpp b/src/hotspot/share/jfr/recorder/stacktrace/jfrStackTraceRepository.cpp index 2830a9390ea444e54c98238fe0af0676f86404ce..20df6e63590f0adf87d54681c791ef5d14963069 100644 --- a/src/hotspot/share/jfr/recorder/stacktrace/jfrStackTraceRepository.cpp +++ b/src/hotspot/share/jfr/recorder/stacktrace/jfrStackTraceRepository.cpp @@ -167,7 +167,7 @@ traceid JfrStackTraceRepository::add(const JfrStackTrace& stacktrace) { return instance().add_trace(stacktrace); } -traceid JfrStackTraceRepository::record(Thread* thread, int skip /* 0 */) { +traceid JfrStackTraceRepository::record(Thread* thread, int skip, StackWalkMode mode) { assert(thread == Thread::current(), "invariant"); JfrThreadLocal* const tl = thread->jfr_thread_local(); assert(tl != NULL, "invariant"); @@ -184,10 +184,10 @@ traceid JfrStackTraceRepository::record(Thread* thread, int skip /* 0 */) { } assert(frames != NULL, "invariant"); assert(tl->stackframes() == frames, "invariant"); - return instance().record_for((JavaThread*)thread, skip,frames, tl->stackdepth()); + return instance().record_for((JavaThread*)thread, skip, mode, frames, tl->stackdepth()); } -traceid JfrStackTraceRepository::record(Thread* thread, int skip, unsigned int* hash) { +traceid JfrStackTraceRepository::record(Thread* thread, int skip, StackWalkMode mode, unsigned int* hash) { assert(thread == Thread::current(), "invariant"); JfrThreadLocal* const tl = thread->jfr_thread_local(); assert(tl != NULL, "invariant"); @@ -206,12 +206,12 @@ traceid JfrStackTraceRepository::record(Thread* thread, int skip, unsigned int* } assert(frames != NULL, "invariant"); assert(tl->stackframes() == frames, "invariant"); - return instance().record_for((JavaThread*)thread, skip, frames, tl->stackdepth(), hash); + return instance().record_for((JavaThread*)thread, skip, mode, frames, tl->stackdepth(), hash); } -traceid JfrStackTraceRepository::record_for(JavaThread* thread, int skip, JfrStackFrame *frames, u4 max_frames) { +traceid JfrStackTraceRepository::record_for(JavaThread* thread, int skip, StackWalkMode mode, JfrStackFrame *frames, u4 max_frames) { JfrStackTrace stacktrace(frames, max_frames); - if (!stacktrace.record_safe(thread, skip)) { + if (!stacktrace.record_safe(thread, skip, false, mode)) { return 0; } traceid tid = add(stacktrace); @@ -222,10 +222,10 @@ traceid JfrStackTraceRepository::record_for(JavaThread* thread, int skip, JfrSta return tid; } -traceid JfrStackTraceRepository::record_for(JavaThread* thread, int skip, JfrStackFrame *frames, u4 max_frames, unsigned int* hash) { +traceid JfrStackTraceRepository::record_for(JavaThread* thread, int skip, StackWalkMode mode, JfrStackFrame *frames, u4 max_frames, unsigned int* hash) { assert(hash != NULL && *hash == 0, "invariant"); JfrStackTrace stacktrace(frames, max_frames); - if (!stacktrace.record_safe(thread, skip, true)) { + if (!stacktrace.record_safe(thread, skip, true, mode)) { return 0; } traceid tid = add(stacktrace); @@ -382,16 +382,45 @@ void JfrStackTrace::resolve_linenos() { _lineno = true; } -bool JfrStackTrace::record_safe(JavaThread* thread, int skip, bool leakp /* false */) { +bool JfrStackTrace::record_safe(JavaThread* thread, int skip, bool leakp, StackWalkMode mode) { assert(SafepointSynchronize::safepoint_safe(thread, thread->thread_state()) || thread == Thread::current(), "Thread stack needs to be walkable"); - vframeStream vfs(thread); + + bool success = false; + switch(mode) { + case WALK_BY_DEFAULT: + { + vframeStream vfs(thread); + success = fill_in(vfs, skip, leakp, mode); + break; + } + case WALK_BY_CURRENT_FRAME: + { + vframeStream vfs(thread, os::current_frame()); + success = fill_in(vfs, skip, leakp, mode); + break; + } + default: + ShouldNotReachHere(); + } + return success; +} + +bool JfrStackTrace::fill_in(vframeStream& vfs, int skip, bool leakp, StackWalkMode mode) { u4 count = 0; _reached_root = true; + // Indicates whether the top frame is visited in this frames iteration. + // Top frame bci may be invalid and fill_in() will fix the top frame bci in a conservative way. + bool top_frame_visited = false; for(int i = 0; i < skip; i++) { if (vfs.at_end()) { break; } + // The top frame is in skip list. + // Mark top_frame_visited to avoid unnecessary top frame bci fixing. + if (!top_frame_visited) { + top_frame_visited = true; + } vfs.next(); } @@ -406,8 +435,25 @@ bool JfrStackTrace::record_safe(JavaThread* thread, int skip, bool leakp /* fals int bci = 0; if (method->is_native()) { type = JfrStackFrame::FRAME_NATIVE; + // The top frame is in native. + // Mark top_frame_visited to avoid unnecessary top frame bci fixing. + if (!top_frame_visited) { + top_frame_visited = true; + } } else { bci = vfs.bci(); + // Hit the top frame and fix bci here. + if (!top_frame_visited) { + if (mode == WALK_BY_CURRENT_FRAME) { + // Only fix opto fast path allocation. + // All fast path allocations do not have cached event id. + if (!vfs.thread_ref()->jfr_thread_local()->has_cached_event_id()) { + assert(vfs.thread_ref()->jfr_thread_local()->has_cached_top_frame_bci(), "Invariant"); + bci = vfs.thread_ref()->jfr_thread_local()->cached_top_frame_bci(); + } + } + top_frame_visited = true; + } } // Can we determine if it's inlined? _hash = (_hash << 2) + (unsigned int)(((size_t)mid >> 2) + (bci << 4) + type); diff --git a/src/hotspot/share/jfr/recorder/stacktrace/jfrStackTraceRepository.hpp b/src/hotspot/share/jfr/recorder/stacktrace/jfrStackTraceRepository.hpp index dc0f2ef069696145864df051d2bb7b8d5f51ec96..c1728b1a8d362dafd3e4396c2b854a2ffa9e3087 100644 --- a/src/hotspot/share/jfr/recorder/stacktrace/jfrStackTraceRepository.hpp +++ b/src/hotspot/share/jfr/recorder/stacktrace/jfrStackTraceRepository.hpp @@ -33,6 +33,7 @@ class JavaThread; class JfrCheckpointWriter; class JfrChunkWriter; class Method; +class vframeStream; class JfrStackFrame { private: @@ -61,6 +62,15 @@ class JfrStackFrame { void resolve_lineno(); }; +enum StackWalkMode { + // walk stack by vframeStream vfs(thread). + WALK_BY_DEFAULT = 0, + // walk stack by vframeStream vfs(thread, os::current_frame()). + // It is only used in JIT runtime leaf call. In JIT runtime leaf call, + // last_java_sp is not maintained and WALK_BY_DEFAULT can not walk stack. + WALK_BY_CURRENT_FRAME +}; + class JfrStackTrace : public StackObj { friend class JfrStackTraceRepository; private: @@ -72,6 +82,8 @@ class JfrStackTrace : public StackObj { bool _reached_root; bool _lineno; + bool fill_in(vframeStream& vfs, int skip, bool leakp, StackWalkMode mode); + public: JfrStackTrace(JfrStackFrame* frames, u4 max_frames) : _frames(frames), _id(0), @@ -81,7 +93,7 @@ class JfrStackTrace : public StackObj { _max_frames(max_frames), _lineno(false) {} bool record_thread(JavaThread& thread, frame& frame); - bool record_safe(JavaThread* thread, int skip, bool leakp = false); + bool record_safe(JavaThread* thread, int skip, bool leakp, StackWalkMode stack_walk_mode); void resolve_linenos(); void set_nr_of_frames(u4 nr_of_frames) { _nr_of_frames = nr_of_frames; } void set_hash(unsigned int hash) { _hash = hash; } @@ -129,8 +141,8 @@ class JfrStackTraceRepository : public JfrCHeapObj { u4 _entries; size_t write_impl(JfrChunkWriter& cw, bool clear); - traceid record_for(JavaThread* thread, int skip, JfrStackFrame* frames, u4 max_frames); - traceid record_for(JavaThread* thread, int skip, JfrStackFrame* frames, u4 max_frames, unsigned int* hash); + traceid record_for(JavaThread* thread, int skip, StackWalkMode mode, JfrStackFrame* frames, u4 max_frames); + traceid record_for(JavaThread* thread, int skip, StackWalkMode mode, JfrStackFrame* frames, u4 max_frames, unsigned int* hash); traceid add_trace(const JfrStackTrace& stacktrace); const StackTrace* resolve_entry(unsigned int hash, traceid id) const; @@ -143,8 +155,8 @@ class JfrStackTraceRepository : public JfrCHeapObj { bool initialize(); static void destroy(); static traceid add(const JfrStackTrace& stacktrace); - static traceid record(Thread* thread, int skip = 0); - static traceid record(Thread* thread, int skip, unsigned int* hash); + static traceid record(Thread* thread, int skip, StackWalkMode mode); + static traceid record(Thread* thread, int skip, StackWalkMode mode, unsigned int* hash); traceid write(JfrCheckpointWriter& cpw, traceid id, unsigned int hash); size_t write(JfrChunkWriter& cw, bool clear); size_t clear(); diff --git a/src/hotspot/share/jfr/support/jfrFlush.cpp b/src/hotspot/share/jfr/support/jfrFlush.cpp index ab5e289a17484b8dfb4f41461e0c6f71e9fd00ef..9684607bb477a1bd3012184cfc59ce6db07a550d 100644 --- a/src/hotspot/share/jfr/support/jfrFlush.cpp +++ b/src/hotspot/share/jfr/support/jfrFlush.cpp @@ -71,12 +71,12 @@ void jfr_conditional_flush(JfrEventId id, size_t size, Thread* t) { } } -bool jfr_save_stacktrace(Thread* t) { +bool jfr_save_stacktrace(Thread* t, StackWalkMode mode) { JfrThreadLocal* const tl = t->jfr_thread_local(); if (tl->has_cached_stack_trace()) { return false; // no ownership } - tl->set_cached_stack_trace_id(JfrStackTraceRepository::record(t)); + tl->set_cached_stack_trace_id(JfrStackTraceRepository::record(t, 0, mode)); return true; } diff --git a/src/hotspot/share/jfr/support/jfrFlush.hpp b/src/hotspot/share/jfr/support/jfrFlush.hpp index f8ce8bec84b92f2039820a0d8418e7ad2103f66c..3e23d91f31a0449158fbd6f19c9688af3b06442c 100644 --- a/src/hotspot/share/jfr/support/jfrFlush.hpp +++ b/src/hotspot/share/jfr/support/jfrFlush.hpp @@ -26,6 +26,8 @@ #define SHARE_VM_JFR_SUPPORT_JFRFLUSH_HPP #include "jfr/recorder/storage/jfrBuffer.hpp" +#include "jfr/recorder/jfrEventSetting.hpp" +#include "jfr/recorder/stacktrace/jfrStackTraceRepository.hpp" #include "jfr/utilities/jfrTypes.hpp" #include "memory/allocation.hpp" @@ -43,7 +45,7 @@ class JfrFlush : public StackObj { void jfr_conditional_flush(JfrEventId id, size_t size, Thread* t); bool jfr_is_event_enabled(JfrEventId id); bool jfr_has_stacktrace_enabled(JfrEventId id); -bool jfr_save_stacktrace(Thread* t); +bool jfr_save_stacktrace(Thread* t, StackWalkMode mode); void jfr_clear_stacktrace(Thread* t); template @@ -64,7 +66,7 @@ class JfrConditionalFlushWithStacktrace : public JfrConditionalFlush { public: JfrConditionalFlushWithStacktrace(Thread* t) : JfrConditionalFlush(t), _t(t), _owner(false) { if (Event::has_stacktrace() && jfr_has_stacktrace_enabled(Event::eventId)) { - _owner = jfr_save_stacktrace(t); + _owner = jfr_save_stacktrace(t, JfrEventSetting::stack_walk_mode(Event::eventId)); } } ~JfrConditionalFlushWithStacktrace() { diff --git a/src/hotspot/share/jfr/support/jfrStackTraceMark.cpp b/src/hotspot/share/jfr/support/jfrStackTraceMark.cpp index b7a2f1d641047452052229a23f49d9586e501406..2fa3610e7675b80b073608bddb31df82ac6ea885 100644 --- a/src/hotspot/share/jfr/support/jfrStackTraceMark.cpp +++ b/src/hotspot/share/jfr/support/jfrStackTraceMark.cpp @@ -24,6 +24,7 @@ #include "precompiled.hpp" #include "jfr/recorder/jfrEventSetting.inline.hpp" +#include "jfr/recorder/jfrEventSetting.hpp" #include "jfr/recorder/stacktrace/jfrStackTraceRepository.hpp" #include "jfr/support/jfrStackTraceMark.hpp" #include "jfr/support/jfrThreadLocal.hpp" @@ -35,7 +36,7 @@ JfrStackTraceMark::JfrStackTraceMark() : _t(Thread::current()), _previous_id(0), _previous_id = tl->cached_stack_trace_id(); _previous_hash = tl->cached_stack_trace_hash(); } - tl->set_cached_stack_trace_id(JfrStackTraceRepository::record(Thread::current())); + tl->set_cached_stack_trace_id(JfrStackTraceRepository::record(Thread::current(), 0, WALK_BY_DEFAULT)); } JfrStackTraceMark::JfrStackTraceMark(Thread* t) : _t(t), _previous_id(0), _previous_hash(0) { @@ -44,7 +45,7 @@ JfrStackTraceMark::JfrStackTraceMark(Thread* t) : _t(t), _previous_id(0), _previ _previous_id = tl->cached_stack_trace_id(); _previous_hash = tl->cached_stack_trace_hash(); } - tl->set_cached_stack_trace_id(JfrStackTraceRepository::record(t)); + tl->set_cached_stack_trace_id(JfrStackTraceRepository::record(t, 0, WALK_BY_DEFAULT)); } JfrStackTraceMark::JfrStackTraceMark(JfrEventId eventId) : _t(NULL), _previous_id(0), _previous_hash(0) { @@ -55,7 +56,8 @@ JfrStackTraceMark::JfrStackTraceMark(JfrEventId eventId) : _t(NULL), _previous_i _previous_id = tl->cached_stack_trace_id(); _previous_hash = tl->cached_stack_trace_hash(); } - tl->set_cached_stack_trace_id(JfrStackTraceRepository::record(_t)); + StackWalkMode mode = JfrEventSetting::stack_walk_mode(eventId); + tl->set_cached_stack_trace_id(JfrStackTraceRepository::record(_t, 0, mode)); } } @@ -67,7 +69,8 @@ JfrStackTraceMark::JfrStackTraceMark(JfrEventId eventId, Thread* t) : _t(NULL), _previous_id = tl->cached_stack_trace_id(); _previous_hash = tl->cached_stack_trace_hash(); } - tl->set_cached_stack_trace_id(JfrStackTraceRepository::record(_t)); + StackWalkMode mode = JfrEventSetting::stack_walk_mode(eventId); + tl->set_cached_stack_trace_id(JfrStackTraceRepository::record(_t, 0, mode)); } } diff --git a/src/hotspot/share/jfr/support/jfrThreadLocal.cpp b/src/hotspot/share/jfr/support/jfrThreadLocal.cpp index 58fcad6cd3619b2e84a4c7aaf332d9c88983322d..669ffbfb30c50534db530c54d8877153cd45b7a8 100644 --- a/src/hotspot/share/jfr/support/jfrThreadLocal.cpp +++ b/src/hotspot/share/jfr/support/jfrThreadLocal.cpp @@ -53,7 +53,11 @@ JfrThreadLocal::JfrThreadLocal() : _stack_trace_hash(0), _stackdepth(0), _entering_suspend_flag(0), - _dead(false) {} + _dead(false), + _cached_top_frame_bci(max_jint), + _alloc_count(0), + _alloc_count_until_sample(1), + _cached_event_id(MaxJfrEventId) {} u8 JfrThreadLocal::add_data_lost(u8 value) { _data_lost += value; diff --git a/src/hotspot/share/jfr/support/jfrThreadLocal.hpp b/src/hotspot/share/jfr/support/jfrThreadLocal.hpp index 5c0d21a8fc986c5621d8ba071ce980bf7c1d5ee3..d54f305d350beb6b560af2466c5cc2ccac635095 100644 --- a/src/hotspot/share/jfr/support/jfrThreadLocal.hpp +++ b/src/hotspot/share/jfr/support/jfrThreadLocal.hpp @@ -26,6 +26,7 @@ #define SHARE_VM_JFR_SUPPORT_JFRTHREADLOCAL_HPP #include "jfr/recorder/checkpoint/jfrCheckpointBlob.hpp" +#include "jfr/objectprofiler/objectProfiler.hpp" #include "jfr/utilities/jfrTypes.hpp" #include "utilities/sizes.hpp" @@ -51,6 +52,27 @@ class JfrThreadLocal { mutable u4 _stackdepth; volatile jint _entering_suspend_flag; bool _dead; + // Jfr callstack collection relies on vframeStream. + // But the bci of top frame can not be determined by vframeStream in some scenarios. + // For example, in the opto CallLeafNode runtime call of + // OptoRuntime::jfr_fast_object_alloc_C, the top frame bci + // returned by vframeStream is always invalid. This is largely due to the oopmap that + // is not correctly granted ( refer to PhaseMacroExpand::expand_allocate_common to get more details ). + // The opto fast path object allocation tracing occurs in the opto CallLeafNode, + // which has been broken by invalid top frame bci. + // To fix this, we get the top frame bci in opto compilation phase + // and pass it as parameter to runtime call. Our implementation will replace the invalid top + // frame bci with cached_top_frame_bci. + jint _cached_top_frame_bci; + jlong _alloc_count; + jlong _alloc_count_until_sample; + // This field is used to help to distinguish the object allocation request source + // For example, for object allocation slow path, we trace it in CollectedHeap::obj_allocate. + // But in CollectedHeap::obj_allocate, it is impossible to determine where the allocation request + // is from, which could be from c1, opto, or even interpreter. + // We save this infomation in _event_id, which later can be retrieved in + // CollecetedHeap::obj_allocate to identify the real allocation request source. + JfrEventId _cached_event_id; JfrBuffer* install_native_buffer() const; JfrBuffer* install_java_buffer() const; @@ -193,6 +215,54 @@ class JfrThreadLocal { _wallclock_time = wallclock_time; } + void set_cached_top_frame_bci(jint bci) { + _cached_top_frame_bci = bci; + } + + bool has_cached_top_frame_bci() const { + return _cached_top_frame_bci != max_jint; + } + + jint cached_top_frame_bci() const { + return _cached_top_frame_bci; + } + + void clear_cached_top_frame_bci() { + _cached_top_frame_bci = max_jint; + } + + jlong alloc_count() const { + return _alloc_count; + } + + void incr_alloc_count(jlong delta) { + _alloc_count += delta; + } + + jlong alloc_count_until_sample() const { + return _alloc_count_until_sample; + } + + void incr_alloc_count_until_sample(jlong delta) { + _alloc_count_until_sample += delta; + } + + void set_cached_event_id(JfrEventId event_id) { + _cached_event_id = event_id; + } + + JfrEventId cached_event_id() const { + return _cached_event_id; + } + + bool has_cached_event_id() const { + return _cached_event_id != MaxJfrEventId; + } + + void clear_cached_event_id() { + _cached_event_id = MaxJfrEventId; + } + traceid trace_id() const { return _trace_id; } @@ -227,6 +297,9 @@ class JfrThreadLocal { static ByteSize java_event_writer_offset() { return in_ByteSize(offset_of(JfrThreadLocal, _java_event_writer)); } + + TRACE_DEFINE_THREAD_ALLOC_COUNT_UNTIL_SAMPLE_OFFSET; + TRACE_DEFINE_THREAD_ALLOC_COUNT_OFFSET; }; #endif // SHARE_VM_JFR_SUPPORT_JFRTHREADLOCAL_HPP diff --git a/src/hotspot/share/opto/macro.cpp b/src/hotspot/share/opto/macro.cpp index 164213148f83485cdb223af0b85e097fffe3c3ca..859a49afe72345a3f516a4b3d817f6fa445f8c73 100644 --- a/src/hotspot/share/opto/macro.cpp +++ b/src/hotspot/share/opto/macro.cpp @@ -27,6 +27,7 @@ #include "gc/shared/collectedHeap.inline.hpp" #include "libadt/vectset.hpp" #include "opto/addnode.hpp" +#include "opto/divnode.hpp" #include "opto/arraycopynode.hpp" #include "opto/callnode.hpp" #include "opto/castnode.hpp" @@ -50,6 +51,7 @@ #if INCLUDE_G1GC #include "gc/g1/g1ThreadLocalData.hpp" #endif // INCLUDE_G1GC +#include "jfr/objectprofiler/objectProfiler.hpp" // @@ -1161,14 +1163,21 @@ void PhaseMacroExpand::set_eden_pointers(Node* &eden_top_adr, Node* &eden_end_ad } -Node* PhaseMacroExpand::make_load(Node* ctl, Node* mem, Node* base, int offset, const Type* value_type, BasicType bt) { +Node* PhaseMacroExpand::load(Node* ctl, Node* mem, Node* base, int offset, const Type* value_type, BasicType bt, MemNode::MemOrd mo) { Node* adr = basic_plus_adr(base, offset); const TypePtr* adr_type = adr->bottom_type()->is_ptr(); - Node* value = LoadNode::make(_igvn, ctl, mem, adr, adr_type, value_type, bt, MemNode::unordered); + Node* value = LoadNode::make(_igvn, ctl, mem, adr, adr_type, value_type, bt, mo); transform_later(value); return value; } +Node* PhaseMacroExpand::make_load(Node* ctl, Node* mem, Node* base, int offset, const Type* value_type, BasicType bt) { + return load(ctl, mem, base, offset, value_type, bt, MemNode::unordered); +} + +Node* PhaseMacroExpand::make_load_acquire(Node* ctl, Node* mem, Node* base, int offset, const Type* value_type, BasicType bt) { + return load(ctl, mem, base, offset, value_type, bt, MemNode::acquire); +} Node* PhaseMacroExpand::make_store(Node* ctl, Node* mem, Node* base, int offset, Node* value, BasicType bt) { Node* adr = basic_plus_adr(base, offset); @@ -1520,6 +1529,10 @@ void PhaseMacroExpand::expand_allocate_common( } } + if (JfrOptionSet::sample_object_allocations()) { + jfr_sample_fast_object_allocation(alloc, fast_oop, fast_oop_ctrl, fast_oop_rawmem); + } + if (C->env()->dtrace_extended_probes()) { // Slow-path call int size = TypeFunc::Parms + 2; @@ -1705,6 +1718,115 @@ void PhaseMacroExpand::expand_allocate_common( // This completes all paths into the result merge point } +static jint bottom_java_frame_bci(JVMState* state) { + assert(state != NULL, "Invariant"); + + JVMState* last = NULL; + JVMState* current = state; + while (current != NULL) { + last = current; + current = current->caller(); + } + return last->bci(); +} + +// +// Pseudo code: +// +// int alloc_sample_enabled = *(int *)ObjectProfiler::enabled_flag_address(); +// if (alloc_sample_enabled) { +// long alloc_count = thread->trace_data()->alloc_count(); +// long alloc_count_new = alloc_count + 1; +// thread->trace_data()->set_alloc_count(alloc_count_new); +// long alloc_count_until_sample = thread->trace_data()->alloc_count_until_sample(); +// if (alloc_count_until_sample == alloc_count_new) { +// jfr_fast_object_alloc_C(obj, thread); +// } +// } +void PhaseMacroExpand::jfr_sample_fast_object_allocation( + AllocateNode* alloc, Node* fast_oop, + Node*& fast_oop_ctrl, Node*& fast_oop_rawmem) { + Node* tls = transform_later(new ThreadLocalNode()); + + Node* alloc_sample_enabled_addr = transform_later(ConPNode::make((address) ObjectProfiler::enabled_flag_address())); + Node* alloc_sample_enabled = make_load_acquire(fast_oop_ctrl, fast_oop_rawmem, alloc_sample_enabled_addr, 0, TypeInt::INT, T_INT); + Node* alloc_sample_enabled_cmp = transform_later(new CmpINode(alloc_sample_enabled, intcon(1))); + Node* alloc_sample_enabled_bool = transform_later(new BoolNode(alloc_sample_enabled_cmp, BoolTest::eq)); + IfNode* alloc_sample_enabled_if = (IfNode*)transform_later(new IfNode(fast_oop_ctrl, alloc_sample_enabled_bool, PROB_MIN, COUNT_UNKNOWN)); + Node* alloc_sample_enabled_ctrl = transform_later(new IfTrueNode(alloc_sample_enabled_if)); + Node* alloc_sample_enabled_mem = fast_oop_rawmem; + Node* alloc_sample_disabled_ctrl = transform_later(new IfFalseNode(alloc_sample_enabled_if)); + Node* alloc_sample_disabled_mem = fast_oop_rawmem; + Node* alloc_sample_enabled_region = transform_later(new RegionNode(3)); + Node* alloc_sample_enabled_region_phi_mem = transform_later(new PhiNode(alloc_sample_enabled_region, Type::MEMORY, TypeRawPtr::BOTTOM)); + enum { enabled_idx = 1, disabled_idx = 2 }; + + // if _enabled then + { + const int alloc_count_offset = in_bytes(TRACE_THREAD_ALLOC_COUNT_OFFSET); + Node* alloc_count = make_load(alloc_sample_enabled_ctrl, alloc_sample_enabled_mem, tls, alloc_count_offset, TypeLong::LONG, T_LONG); + Node* alloc_count_new = transform_later(new AddLNode(alloc_count, longcon(1))); + alloc_sample_enabled_mem = make_store(alloc_sample_enabled_ctrl, alloc_sample_enabled_mem, tls, alloc_count_offset, alloc_count_new, T_LONG); + const int alloc_count_until_sample_offset = in_bytes(TRACE_THREAD_ALLOC_COUNT_UNTIL_SAMPLE_OFFSET); + Node* alloc_count_until_sample = make_load(alloc_sample_enabled_ctrl, alloc_sample_enabled_mem, tls, alloc_count_until_sample_offset, TypeLong::LONG, T_LONG); + Node* alloc_count_until_sample_cmp = transform_later(new CmpLNode(alloc_count_until_sample, alloc_count_new)); + Node* alloc_sample_hit_bool = transform_later(new BoolNode(alloc_count_until_sample_cmp, BoolTest::eq)); + IfNode* alloc_sample_hit_if = (IfNode*)transform_later(new IfNode(alloc_sample_enabled_ctrl, alloc_sample_hit_bool, PROB_MIN, COUNT_UNKNOWN)); + Node* alloc_sample_hit_ctrl = transform_later(new IfTrueNode(alloc_sample_hit_if)); + Node* alloc_sample_hit_mem = alloc_sample_enabled_mem; + Node* alloc_sample_miss_ctrl = transform_later(new IfFalseNode(alloc_sample_hit_if)); + Node* alloc_sample_miss_mem = alloc_sample_enabled_mem; + Node* alloc_sample_hit_region = transform_later(new RegionNode(3)); + Node* alloc_sample_hit_region_phi_mem = transform_later(new PhiNode(alloc_sample_hit_region, Type::MEMORY, TypeRawPtr::BOTTOM)); + + // if sample_hit then + { + CallLeafNode *call = new CallLeafNode(OptoRuntime::jfr_fast_object_alloc_Type(), + CAST_FROM_FN_PTR(address, OptoRuntime::jfr_fast_object_alloc_C), + "jfr_fast_object_alloc_C", + TypeRawPtr::BOTTOM); + call->init_req(TypeFunc::Parms+0, fast_oop); + call->init_req(TypeFunc::Parms+1, intcon(bottom_java_frame_bci(alloc->jvms()))); + call->init_req(TypeFunc::Parms+2, tls); + call->init_req(TypeFunc::Control, alloc_sample_hit_ctrl); + call->init_req(TypeFunc::I_O , top()); + call->init_req(TypeFunc::Memory , alloc_sample_hit_mem); + call->init_req(TypeFunc::ReturnAdr, alloc->in(TypeFunc::ReturnAdr)); + call->init_req(TypeFunc::FramePtr, alloc->in(TypeFunc::FramePtr)); + transform_later(call); + alloc_sample_hit_ctrl = new ProjNode(call,TypeFunc::Control); + transform_later(alloc_sample_hit_ctrl); + alloc_sample_hit_mem = new ProjNode(call,TypeFunc::Memory); + transform_later(alloc_sample_hit_mem); + + alloc_sample_hit_region->init_req(enabled_idx, alloc_sample_hit_ctrl); + alloc_sample_hit_region_phi_mem->init_req(enabled_idx, alloc_sample_hit_mem); + } + + { + alloc_sample_hit_region->init_req(disabled_idx, alloc_sample_miss_ctrl); + alloc_sample_hit_region_phi_mem->init_req(disabled_idx, alloc_sample_miss_mem); + } + + { + alloc_sample_enabled_ctrl = alloc_sample_hit_region; + alloc_sample_enabled_mem = alloc_sample_hit_region_phi_mem; + } + alloc_sample_enabled_region->init_req(enabled_idx, alloc_sample_enabled_ctrl); + alloc_sample_enabled_region_phi_mem->init_req(enabled_idx, alloc_sample_enabled_mem); + } + + { + alloc_sample_enabled_region->init_req(disabled_idx, alloc_sample_disabled_ctrl); + alloc_sample_enabled_region_phi_mem->init_req(disabled_idx, alloc_sample_disabled_mem); + } + + { + fast_oop_ctrl = alloc_sample_enabled_region; + fast_oop_rawmem = alloc_sample_enabled_region_phi_mem; + } +} + // Helper for PhaseMacroExpand::expand_allocate_common. // Initializes the newly-allocated storage. diff --git a/src/hotspot/share/opto/macro.hpp b/src/hotspot/share/opto/macro.hpp index b100f3c5508012fa7ee7bc03fb345e646998c2a3..e65ac7a2e87b5232f8dce619a19c380fe1fdd19d 100644 --- a/src/hotspot/share/opto/macro.hpp +++ b/src/hotspot/share/opto/macro.hpp @@ -58,8 +58,12 @@ public: return n; } void set_eden_pointers(Node* &eden_top_adr, Node* &eden_end_adr); + Node* load( Node* ctl, Node* mem, Node* base, int offset, + const Type* value_type, BasicType bt, MemNode::MemOrd mo); Node* make_load( Node* ctl, Node* mem, Node* base, int offset, const Type* value_type, BasicType bt); + Node* make_load_acquire( Node* ctl, Node* mem, Node* base, int offset, + const Type* value_type, BasicType bt); Node* make_store(Node* ctl, Node* mem, Node* base, int offset, Node* value, BasicType bt); @@ -198,6 +202,10 @@ private: Node* old_eden_top, Node* new_eden_top, Node* length); + //JFR tracing + void jfr_sample_fast_object_allocation(AllocateNode* alloc, Node* fast_oop, + Node*& fast_oop_ctrl, Node*& fast_oop_rawmem); + Node* make_arraycopy_load(ArrayCopyNode* ac, intptr_t offset, Node* ctl, Node* mem, BasicType ft, const Type *ftype, AllocateNode *alloc); public: diff --git a/src/hotspot/share/opto/runtime.cpp b/src/hotspot/share/opto/runtime.cpp index 24fb4e6f0add5cb159c9a15946afd460c6f8c40b..93fe240950dd8a363ebc330c2fa7f49dcb045caa 100644 --- a/src/hotspot/share/opto/runtime.cpp +++ b/src/hotspot/share/opto/runtime.cpp @@ -73,6 +73,7 @@ #include "runtime/vframe_hp.hpp" #include "utilities/copy.hpp" #include "utilities/preserveException.hpp" +#include "gc/shared/allocTracer.inline.hpp" // For debugging purposes: @@ -212,8 +213,11 @@ JRT_BLOCK_ENTRY(void, OptoRuntime::new_instance_C(Klass* klass, JavaThread* thre if (!HAS_PENDING_EXCEPTION) { // Scavenge and allocate an instance. Handle holder(THREAD, klass->klass_holder()); // keep the klass alive + + TRACE_OPTO_SLOW_ALLOCATION_ENTER(false, THREAD); oop result = InstanceKlass::cast(klass)->allocate_instance(THREAD); thread->set_vm_result(result); + TRACE_OPTO_SLOW_ALLOCATION_LEAVE(false, THREAD); // Pass oops back through thread local storage. Our apparent type to Java // is that we return an oop, but we can block on exit from this routine and @@ -240,6 +244,7 @@ JRT_BLOCK_ENTRY(void, OptoRuntime::new_array_C(Klass* array_type, int len, JavaT // Scavenge and allocate an instance. oop result; + TRACE_OPTO_SLOW_ALLOCATION_ENTER(true, THREAD); if (array_type->is_typeArray_klass()) { // The oopFactory likes to work with the element type. // (We could bypass the oopFactory, since it doesn't add much value.) @@ -253,6 +258,7 @@ JRT_BLOCK_ENTRY(void, OptoRuntime::new_array_C(Klass* array_type, int len, JavaT Klass* elem_type = ObjArrayKlass::cast(array_type)->element_klass(); result = oopFactory::new_objArray(elem_type, len, THREAD); } + TRACE_OPTO_SLOW_ALLOCATION_LEAVE(true, THREAD); // Pass oops back through thread local storage. Our apparent type to Java // is that we return an oop, but we can block on exit from this routine and @@ -277,10 +283,12 @@ JRT_BLOCK_ENTRY(void, OptoRuntime::new_array_nozero_C(Klass* array_type, int len // Scavenge and allocate an instance. oop result; + TRACE_OPTO_SLOW_ALLOCATION_ENTER(true, THREAD); assert(array_type->is_typeArray_klass(), "should be called only for type array"); // The oopFactory likes to work with the element type. BasicType elem_type = TypeArrayKlass::cast(array_type)->element_type(); result = oopFactory::new_typeArray_nozero(elem_type, len, THREAD); + TRACE_OPTO_SLOW_ALLOCATION_LEAVE(true, THREAD); // Pass oops back through thread local storage. Our apparent type to Java // is that we return an oop, but we can block on exit from this routine and @@ -326,7 +334,9 @@ JRT_ENTRY(void, OptoRuntime::multianewarray2_C(Klass* elem_type, int len1, int l dims[0] = len1; dims[1] = len2; Handle holder(THREAD, elem_type->klass_holder()); // keep the klass alive + TRACE_OPTO_SLOW_ALLOCATION_ENTER(true, THREAD); oop obj = ArrayKlass::cast(elem_type)->multi_allocate(2, dims, THREAD); + TRACE_OPTO_SLOW_ALLOCATION_LEAVE(true, THREAD); deoptimize_caller_frame(thread, HAS_PENDING_EXCEPTION); thread->set_vm_result(obj); JRT_END @@ -343,7 +353,9 @@ JRT_ENTRY(void, OptoRuntime::multianewarray3_C(Klass* elem_type, int len1, int l dims[1] = len2; dims[2] = len3; Handle holder(THREAD, elem_type->klass_holder()); // keep the klass alive + TRACE_OPTO_SLOW_ALLOCATION_ENTER(true, THREAD); oop obj = ArrayKlass::cast(elem_type)->multi_allocate(3, dims, THREAD); + TRACE_OPTO_SLOW_ALLOCATION_LEAVE(true, THREAD); deoptimize_caller_frame(thread, HAS_PENDING_EXCEPTION); thread->set_vm_result(obj); JRT_END @@ -361,7 +373,9 @@ JRT_ENTRY(void, OptoRuntime::multianewarray4_C(Klass* elem_type, int len1, int l dims[2] = len3; dims[3] = len4; Handle holder(THREAD, elem_type->klass_holder()); // keep the klass alive + TRACE_OPTO_SLOW_ALLOCATION_ENTER(true, THREAD); oop obj = ArrayKlass::cast(elem_type)->multi_allocate(4, dims, THREAD); + TRACE_OPTO_SLOW_ALLOCATION_LEAVE(true, THREAD); deoptimize_caller_frame(thread, HAS_PENDING_EXCEPTION); thread->set_vm_result(obj); JRT_END @@ -380,7 +394,9 @@ JRT_ENTRY(void, OptoRuntime::multianewarray5_C(Klass* elem_type, int len1, int l dims[3] = len4; dims[4] = len5; Handle holder(THREAD, elem_type->klass_holder()); // keep the klass alive + TRACE_OPTO_SLOW_ALLOCATION_ENTER(true, THREAD); oop obj = ArrayKlass::cast(elem_type)->multi_allocate(5, dims, THREAD); + TRACE_OPTO_SLOW_ALLOCATION_LEAVE(true, THREAD); deoptimize_caller_frame(thread, HAS_PENDING_EXCEPTION); thread->set_vm_result(obj); JRT_END @@ -398,7 +414,9 @@ JRT_ENTRY(void, OptoRuntime::multianewarrayN_C(Klass* elem_type, arrayOopDesc* d c_dims, len); Handle holder(THREAD, elem_type->klass_holder()); // keep the klass alive + TRACE_OPTO_SLOW_ALLOCATION_ENTER(true, THREAD); oop obj = ArrayKlass::cast(elem_type)->multi_allocate(len, c_dims, THREAD); + TRACE_OPTO_SLOW_ALLOCATION_LEAVE(true, THREAD); deoptimize_caller_frame(thread, HAS_PENDING_EXCEPTION); thread->set_vm_result(obj); JRT_END @@ -1658,3 +1676,30 @@ static void trace_exception(outputStream* st, oop exception_oop, address excepti st->print_raw_cr(tempst.as_string()); } + +// JFR support. +const TypeFunc *OptoRuntime::jfr_fast_object_alloc_Type() { + const Type **fields = TypeTuple::fields(3); + fields[TypeFunc::Parms+0] = TypeRawPtr::BOTTOM; // newly allocated object + fields[TypeFunc::Parms+1] = TypeInt::INT; // bci + fields[TypeFunc::Parms+2] = TypeRawPtr::BOTTOM; // tls + + const TypeTuple *domain = TypeTuple::make(TypeFunc::Parms+3, fields); + + // create result type (range) + fields = TypeTuple::fields(1); + fields[TypeFunc::Parms+0] = TypeRawPtr::BOTTOM; // returned oop + + const TypeTuple *range = TypeTuple::make(TypeFunc::Parms+1, fields); + + return TypeFunc::make(domain, range); +} + +void OptoRuntime::jfr_fast_object_alloc_C(oopDesc* obj, jint top_frame_bci, JavaThread* thread) { + assert(obj != NULL, "invariant"); + assert(obj->klass() != NULL, "invariant"); + thread->jfr_thread_local()->set_cached_top_frame_bci(top_frame_bci); + AllocTracer::send_opto_fast_allocation_event(obj->klass(), obj, obj->size() * HeapWordSize, thread); + thread->jfr_thread_local()->clear_cached_top_frame_bci(); + thread->set_vm_result(obj); +} diff --git a/src/hotspot/share/opto/runtime.hpp b/src/hotspot/share/opto/runtime.hpp index 44aca4bc07b98b3d426c83702f2da54655281b2b..442a13508b0640d4885ff63a72f193269db92525 100644 --- a/src/hotspot/share/opto/runtime.hpp +++ b/src/hotspot/share/opto/runtime.hpp @@ -177,6 +177,8 @@ public: static void monitor_notify_C(oopDesc* obj, JavaThread* thread); static void monitor_notifyAll_C(oopDesc* obj, JavaThread* thread); + // JFR support + static void jfr_fast_object_alloc_C(oopDesc* obj, jint bci, JavaThread* thread); private: // Implicit exception support @@ -318,6 +320,9 @@ private: static const TypeFunc* dtrace_method_entry_exit_Type(); static const TypeFunc* dtrace_object_alloc_Type(); + // JFR support + static const TypeFunc* jfr_fast_object_alloc_Type(); + private: static NamedCounter * volatile _named_counters; diff --git a/src/hotspot/share/precompiled/precompiled.hpp b/src/hotspot/share/precompiled/precompiled.hpp index a0a99028b9590260bdff448cf7bb5b5a59e0ed2d..6ddc7c607a9456bca2ab86e358e78670e246cd6a 100644 --- a/src/hotspot/share/precompiled/precompiled.hpp +++ b/src/hotspot/share/precompiled/precompiled.hpp @@ -93,6 +93,8 @@ # include "gc/shared/collectorCounters.hpp" # include "gc/shared/collectorPolicy.hpp" # include "gc/shared/gcCause.hpp" +# include "gc/shared/allocTracer.hpp" +# include "gc/shared/allocTracer.inline.hpp" # include "gc/shared/gcLocker.hpp" # include "gc/shared/gcStats.hpp" # include "gc/shared/gcUtil.hpp" diff --git a/src/hotspot/share/runtime/vframe.hpp b/src/hotspot/share/runtime/vframe.hpp index 2ee93d7b948b142b6bc03aef00bba963cb21d251..e6db474b3ebfe074bb953147ba78a6091e3ffea0 100644 --- a/src/hotspot/share/runtime/vframe.hpp +++ b/src/hotspot/share/runtime/vframe.hpp @@ -346,6 +346,10 @@ class vframeStream : public vframeStreamCommon { // top_frame may not be at safepoint, start with sender vframeStream(JavaThread* thread, frame top_frame, bool stop_at_java_call_stub = false); + + Thread *& thread_ref() { + return (Thread *&)_thread; + } }; #endif // SHARE_VM_RUNTIME_VFRAME_HPP diff --git a/src/jdk.jfr/share/classes/jdk/jfr/EventNames.java b/src/jdk.jfr/share/classes/jdk/jfr/EventNames.java new file mode 100644 index 0000000000000000000000000000000000000000..8f1ac349aa0219acc1edc08e94b3eff2922db8b9 --- /dev/null +++ b/src/jdk.jfr/share/classes/jdk/jfr/EventNames.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2019 Alibaba Group Holding Limited. 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. Alibaba 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. + * + */ +package jdk.jfr; + +/** + * A constant class for JFR Event, contains the names of some events used by jdk. + */ +public class EventNames { + private final static String PREFIX = "jdk."; + + public final static String OptoInstanceObjectAllocation = PREFIX + "OptoInstanceObjectAllocation"; + public final static String OptoArrayObjectAllocation = PREFIX + "OptoArrayObjectAllocation"; +} diff --git a/src/jdk.jfr/share/classes/jdk/jfr/Recording.java b/src/jdk.jfr/share/classes/jdk/jfr/Recording.java index 26db88cf80c851fe2b6aa8067ff5acfb60d11c30..2605a095e9d630b5b243d9c8cf0af9c3277cbea5 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/Recording.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/Recording.java @@ -673,4 +673,7 @@ public final class Recording implements Closeable { internal.setSetting(id, value); } + public boolean isRecorderEnabled(String eventName) { + return internal.isRecorderEnabled(eventName); + } } diff --git a/src/jdk.jfr/share/classes/jdk/jfr/consumer/RecordedClass.java b/src/jdk.jfr/share/classes/jdk/jfr/consumer/RecordedClass.java index 73031f4a88d239cf1e39487f849e50087f078e24..2be679fd197bbf70991fbe06cbebfbc0ee795dfb 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/consumer/RecordedClass.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/consumer/RecordedClass.java @@ -100,4 +100,27 @@ public final class RecordedClass extends RecordedObject { public long getId() { return uniqueId; } + + /** + * Returns the object size for the class. + *

+ * The object size for instance class is accurate. But for the array class, it + * is a magic code 0x1111baba. The array object size can not be determined statically + * from the JVM klass information because array object size is affected by + * actual element length. + * + * @return the object size (instance object), or magic code 0x1111baba (array object) + */ + public int getObjectSize() { + return getTyped("objectSize", Integer.class, -1); + } + + /** + * Checks whether the class is for instance or array. + * + * @return true or false + */ + public boolean isArray() { + return getName().startsWith("["); + } } diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/JVM.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/JVM.java index e9f6d2a9a60deb4c7bbd032a3d00232cf3ba7ac3..1cc76344bc6d6fecdacc41b18b95201e354b1120 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/JVM.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/JVM.java @@ -292,6 +292,20 @@ public final class JVM { */ public native void setSampleThreads(boolean sampleThreads) throws IllegalStateException; + /** + * Turn on/off objects allocations sampling. + * + * @param sampleAllocations true if object allocations should be sampled, false otherwise. + */ + public native void setSampleObjectAllocations(boolean sampleAllocations); + + /** + * Set object allocations sampling interval. + * + * @param interval + */ + public native void setObjectAllocationsSamplingInterval(long interval) throws IllegalArgumentException; + /** * Turn on/off compressed integers. * diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/Options.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/Options.java index 8fde26d1f99e3aafb95c19fc5c6d902ff17db859..1802dd404de75e3c39f65dff2af230e2f61662e6 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/Options.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/Options.java @@ -49,6 +49,8 @@ public final class Options { private static final boolean DEFAULT_SAMPLE_THREADS = true; private static final long DEFAULT_MAX_CHUNK_SIZE = 12 * 1024 * 1024; private static final SafePath DEFAULT_DUMP_PATH = SecuritySupport.USER_HOME; + private static final boolean DEFAULT_SAMPLE_OBJECT_ALLOCATIONS = false; + private static final long DEFAULT_OBJECT_ALLOCATIONS_SAMPLING_INTERVAL = 1024; private static long memorySize; private static long globalBufferSize; @@ -58,6 +60,8 @@ public final class Options { private static boolean sampleThreads; private static long maxChunkSize; private static SafePath dumpPath; + private static boolean sampleObjectAllocations; + private static long objectAllocationsSamplingInterval; static { final long pageSize = Unsafe.getUnsafe().pageSize(); @@ -139,6 +143,27 @@ public final class Options { return sampleThreads; } + public static synchronized void setSampleObjectAllocations(Boolean sample) { + jvm.setSampleObjectAllocations(sample); + sampleObjectAllocations = sample; + } + + public static synchronized boolean getSampleObjectAllocations() { + return sampleObjectAllocations; + } + + public static synchronized void setObjectAllocationsSamplingInterval(Long interval) { + if (interval <= 0) { + throw new IllegalArgumentException("interval should be greater than 0"); + } + jvm.setObjectAllocationsSamplingInterval(interval); + objectAllocationsSamplingInterval = interval; + } + + public static synchronized long getObjectAllocationsSamplingInterval() { + return objectAllocationsSamplingInterval; + } + private static synchronized void reset() { setMaxChunkSize(DEFAULT_MAX_CHUNK_SIZE); setMemorySize(DEFAULT_MEMORY_SIZE); @@ -148,6 +173,8 @@ public final class Options { setSampleThreads(DEFAULT_SAMPLE_THREADS); setStackDepth(DEFAULT_STACK_DEPTH); setThreadBufferSize(DEFAULT_THREAD_BUFFER_SIZE); + setSampleObjectAllocations(DEFAULT_SAMPLE_OBJECT_ALLOCATIONS); + setObjectAllocationsSamplingInterval(DEFAULT_OBJECT_ALLOCATIONS_SAMPLING_INTERVAL); } static synchronized long getWaitInterval() { diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/PlatformRecorder.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/PlatformRecorder.java index 7c48fc2662420920f97598d33e53cac786c0f4b9..58e6acac573cf546c742d6bf641f3d7998f3a1db 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/PlatformRecorder.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/PlatformRecorder.java @@ -550,4 +550,8 @@ public final class PlatformRecorder { target.setStopTime(endTime); target.setInternalDuration(Duration.between(startTime, endTime)); } + + public boolean isEnabled(String eventName) { + return MetadataRepository.getInstance().isEnabled(eventName); + } } diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/PlatformRecording.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/PlatformRecording.java index e460430b45959c500a1d7320c04b627de97df612..f06dc0c1df2018c92e14cb6ca42b53c4cd910ba1 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/PlatformRecording.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/PlatformRecording.java @@ -774,4 +774,8 @@ public final class PlatformRecording implements AutoCloseable { public SafePath getDumpOnExitDirectory() { return this.dumpOnExitDirectory; } + + public boolean isRecorderEnabled(String eventName) { + return recorder.isEnabled(eventName); + } } diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/dcmd/DCmdConfigure.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/dcmd/DCmdConfigure.java index f15053a7caafd02106fd65f288589c68d068da43..5091fea09e248461c243e55bfeaf4032f752cfbb 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/dcmd/DCmdConfigure.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/dcmd/DCmdConfigure.java @@ -43,6 +43,7 @@ final class DCmdConfigure extends AbstractDCmd { /** * Execute JFR.configure. * + * @param onVMStart if cmd is invoked at the moment when vm is started * @param repositoryPath the path * @param dumpPath path to dump to on fatal error (oom) * @param stackDepth depth of stack traces @@ -51,6 +52,8 @@ final class DCmdConfigure extends AbstractDCmd { * @param threadBufferSize size of thread buffer for events * @param maxChunkSize threshold at which a new chunk is created in the disk repository * @param sampleThreads if thread sampling should be enabled + * @param sampleObjectAllocations if object allocations sampling should be enbaled + * @param objectAllocationsSamplingInterval interval of object allocations samplings * * @return result @@ -59,6 +62,7 @@ final class DCmdConfigure extends AbstractDCmd { */ public String execute ( + Boolean onVMStart, String repositoryPath, String dumpPath, Integer stackDepth, @@ -67,9 +71,18 @@ final class DCmdConfigure extends AbstractDCmd { Long threadBufferSize, Long memorySize, Long maxChunkSize, - Boolean sampleThreads - + Boolean sampleThreads, + Boolean sampleObjectAllocations, + Long objectAllocationsSamplingInterval ) throws DCmdException { + // Check parameters correctness + if (!onVMStart) { + if (sampleObjectAllocations != null || objectAllocationsSamplingInterval != null) { + Logger.log(LogTag.JFR, LogLevel.ERROR, "Could not change sampleObjectAllocations and objectAllocationsSamplingInterval during application's running"); + return getResult(); + } + } + if (Logger.shouldLog(LogTag.JFR_DCMD, LogLevel.DEBUG)) { Logger.log(LogTag.JFR_DCMD, LogLevel.DEBUG, "Executing DCmdConfigure: repositorypath=" + repositoryPath + ", dumppath=" + dumpPath + @@ -79,7 +92,9 @@ final class DCmdConfigure extends AbstractDCmd { ", thread_buffer_size" + threadBufferSize + ", memorysize" + memorySize + ", maxchunksize=" + maxChunkSize + - ", samplethreads" + sampleThreads); + ", samplethreads" + sampleThreads + + ", sampleObjectAllocations=" + sampleObjectAllocations + + ", objectAllocationsSamplingInterval=" + objectAllocationsSamplingInterval); } @@ -152,6 +167,20 @@ final class DCmdConfigure extends AbstractDCmd { updated = true; } + if (objectAllocationsSamplingInterval != null) { + Options.setObjectAllocationsSamplingInterval(objectAllocationsSamplingInterval); + Logger.log(LogTag.JFR, LogLevel.INFO, "object allocations sampling interval set to " + objectAllocationsSamplingInterval); + printObjectAllocationsSamplingInterval(); + updated = true; + } + + if (sampleObjectAllocations != null) { + Options.setSampleObjectAllocations(sampleObjectAllocations); + Logger.log(LogTag.JFR, LogLevel.INFO, "Sample object allocations set to " + sampleObjectAllocations); + printSampleObjectAllocations(); + updated = true; + } + if (!updated) { println("Current configuration:"); println(); @@ -163,6 +192,8 @@ final class DCmdConfigure extends AbstractDCmd { printMemorySize(); printMaxChunkSize(); printSampleThreads(); + printSampleObjectAllocations(); + printObjectAllocationsSamplingInterval(); } return getResult(); } @@ -214,4 +245,12 @@ final class DCmdConfigure extends AbstractDCmd { printBytes(Options.getMaxChunkSize()); println(); } + + private void printSampleObjectAllocations() { + println("Sample object allocations: " + Options.getSampleObjectAllocations()); + } + + private void printObjectAllocationsSamplingInterval() { + println("objects allocations sampling interval: " + Options.getObjectAllocationsSamplingInterval()); + } } diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/dcmd/DCmdStart.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/dcmd/DCmdStart.java index 0be1165b8af24492dab4457bd80d29584d9dddbc..91d188a87d7cf59f505223ceda5c6a6b0484c5e4 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/dcmd/DCmdStart.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/dcmd/DCmdStart.java @@ -35,9 +35,11 @@ import java.util.Arrays; import java.util.HashMap; import java.util.Map; +import jdk.jfr.EventNames; import jdk.jfr.FlightRecorder; import jdk.jfr.Recording; import jdk.jfr.internal.JVM; +import jdk.jfr.internal.Options; import jdk.jfr.internal.LogLevel; import jdk.jfr.internal.LogTag; import jdk.jfr.internal.Logger; @@ -111,7 +113,7 @@ final class DCmdStart extends AbstractDCmd { try { s.putAll(JFC.createKnown(configName).getSettings()); } catch (IOException | ParseException e) { - throw new DCmdException("Could not parse setting " + settings[0], e); + throw new DCmdException("Could not parse setting " + configName, e); } } @@ -146,6 +148,14 @@ final class DCmdStart extends AbstractDCmd { recording.setSettings(s); SafePath safePath = null; + if (recording.isRecorderEnabled(EventNames.OptoInstanceObjectAllocation) || + recording.isRecorderEnabled(EventNames.OptoArrayObjectAllocation)) { + if (!Options.getSampleObjectAllocations()) { + println("Please add -XX:FlightRecorderOptions=sampleobjectallocations=true in JVM options."); + return getResult(); + } + } + if (path != null) { try { if (dumpOnExit == null) { diff --git a/test/jdk/jdk/jfr/event/objectsprofiling/TestOptoObjectAllocationsSampling.java b/test/jdk/jdk/jfr/event/objectsprofiling/TestOptoObjectAllocationsSampling.java new file mode 100644 index 0000000000000000000000000000000000000000..d7300863e488304a6f7cc74c29679ea30c8267de --- /dev/null +++ b/test/jdk/jdk/jfr/event/objectsprofiling/TestOptoObjectAllocationsSampling.java @@ -0,0 +1,190 @@ +/* + * Copyright (c) 2019 Alibaba Group Holding Limited. 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. Alibaba 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. + * + */ +package jfr.event.objectsprofiling; + +import java.lang.reflect.Method; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import jdk.jfr.Recording; +import jdk.jfr.consumer.RecordedEvent; +import jdk.jfr.consumer.RecordedClass; +import jdk.jfr.consumer.RecordedStackTrace; +import jdk.jfr.consumer.RecordedFrame; + +import sun.hotspot.WhiteBox; + +import jdk.test.lib.Asserts; +import jdk.test.lib.jfr.EventNames; +import jdk.test.lib.jfr.Events; +import jdk.test.lib.Utils; + +/* + * @test TestOptoObjectAllocationsSampling + * @library /test/lib + * + * @build sun.hotspot.WhiteBox + * @build ClassFileInstaller + * + * @compile -g TestOptoObjectAllocationsSampling.java + * + * @run driver ClassFileInstaller sun.hotspot.WhiteBox + * sun.hotspot.WhiteBox$WhiteBoxPermission + * + * @run main/othervm -Xbootclasspath/a:. + * -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -XX:-TieredCompilation + * -XX:FlightRecorderOptions=sampleobjectallocations=true,objectallocationssamplinginterval=1 + * jfr.event.objectsprofiling.TestOptoObjectAllocationsSampling + */ +public class TestOptoObjectAllocationsSampling { + private static final WhiteBox WB; + private static final Method OPTO_METHOD; + private static final int OPTO_METHOD_INVOKE_COUNT = 10; + private static final String OPTO_METHOD_NAME = "fireOptoAllocations"; + private static final int COMP_LEVEL_FULL_OPTIMIZATION = 4; + private static final int RECORDED_ARRAY_CLASS_OBJECT_SIZE_MAGIC_CODE = 0x1111baba; + + static { + try { + WB = WhiteBox.getWhiteBox(); + Class thisClass = TestOptoObjectAllocationsSampling.class; + OPTO_METHOD = thisClass.getMethod(OPTO_METHOD_NAME, Integer.TYPE, Byte.TYPE); + } catch (Exception e) { + Asserts.fail(e.getMessage()); + throw new ExceptionInInitializerError(e); + } + } + + private static class InstanceObject { + private byte content; + + InstanceObject(byte content) { + this.content = content; + } + + public String toString() { + return "InstanceObject ( " + content + " )"; + } + } + + public static Object array; + public static Object instance; + + public static void fireOptoAllocations(int arrayLength, byte b) { + array = new InstanceObject[arrayLength]; + instance = new InstanceObject(b); + } + + private static void ensureOptoMethod() throws Exception { + int initialCompLevel = WB.getMethodCompilationLevel(OPTO_METHOD); + if (initialCompLevel != COMP_LEVEL_FULL_OPTIMIZATION) { + WB.enqueueMethodForCompilation(OPTO_METHOD, COMP_LEVEL_FULL_OPTIMIZATION); + } + Utils.waitForCondition(() -> COMP_LEVEL_FULL_OPTIMIZATION == WB.getMethodCompilationLevel(OPTO_METHOD), 30_000L); + System.out.format("%s is already compiled at full optimization compile level\n", OPTO_METHOD_NAME); + } + + private static void assertOptoMethod() throws Exception { + int compLevel = WB.getMethodCompilationLevel(OPTO_METHOD); + Asserts.assertTrue(compLevel == COMP_LEVEL_FULL_OPTIMIZATION); + } + + private static void runForLong(int arg1, byte arg2) { + for (int i = 0; i < 1000_000; i++) { + try { + ensureOptoMethod(); + fireOptoAllocations(arg1, arg2); + TimeUnit.SECONDS.sleep(1); + } catch (Exception e) { + } + } + } + + public static void main(String[] args) throws Exception { + int arg1 = 3*1024; + byte arg2 = (byte)0x66; + + // Run warm code to prevent deoptimizaiton. + // It will deoptimize if it runs without this warmup step. + fireOptoAllocations(arg1, arg2); + ensureOptoMethod(); + assertOptoMethod(); + + try (Recording recording = new Recording()) { + recording.enable(EventNames.OptoArrayObjectAllocation); + recording.enable(EventNames.OptoInstanceObjectAllocation); + + recording.start(); + + // fireOptoAllocationsMethod would be deoptimized after jfr recording is started. + // The root cause is not clear. + // Invoke ensureOptoMethod blindly to enforce it in level 4 again. + ensureOptoMethod(); + + for (int i = 0; i < OPTO_METHOD_INVOKE_COUNT; i++) { + assertOptoMethod(); + fireOptoAllocations(arg1, arg2); + WB.youngGC(); + } + + recording.stop(); + + List events = Events.fromRecording(recording); + int countOfInstanceObject = 0; + int countOfArrayObject = 0; + final String instanceObjectClassName = InstanceObject.class.getName(); + final String arrayObjectClassName = InstanceObject[].class.getName(); + + for (RecordedEvent event : events) { + RecordedStackTrace stackTrace = event.getStackTrace(); + Asserts.assertTrue(stackTrace != null); + List frames = stackTrace.getFrames(); + Asserts.assertTrue(frames != null && frames.size() > 0); + RecordedFrame topFrame = frames.get(0); + Asserts.assertTrue(event.hasField("objectClass")); + RecordedClass clazz = event.getValue("objectClass"); + String className = clazz.getName(); + Asserts.assertTrue(event.getStackTrace().getFrames().size() > 0); + int objectSize = clazz.getObjectSize(); + System.out.format("Allocation Object Class Name: %s, Object Size: %x, topFrame: %s\n", className, objectSize, topFrame.getMethod()); + if (className.equals(instanceObjectClassName)) { + Asserts.assertTrue(!clazz.isArray()); + Asserts.assertTrue(objectSize > 0); + Asserts.assertTrue(topFrame.getLineNumber() > 0); + Asserts.assertTrue(topFrame.getBytecodeIndex() > 0); + countOfInstanceObject++; + } else if (className.equals(arrayObjectClassName)) { + Asserts.assertTrue(clazz.isArray()); + Asserts.assertTrue(objectSize == RECORDED_ARRAY_CLASS_OBJECT_SIZE_MAGIC_CODE); + countOfArrayObject++; + Asserts.assertTrue(topFrame.getLineNumber() > 0); + Asserts.assertTrue(topFrame.getBytecodeIndex() > 0); + } + } + System.out.format("Total Event Count: %d, EventOptoInstanceObjectAllocaiton Count: %d, EventOptoArrayObjectAllocation Count: %d\n", events.size(), countOfInstanceObject, countOfArrayObject); + + Asserts.assertTrue(countOfArrayObject == countOfInstanceObject); + Asserts.assertTrue(countOfArrayObject == OPTO_METHOD_INVOKE_COUNT); + Asserts.assertTrue(events.size() >= (countOfInstanceObject + countOfArrayObject)); + } + } +} diff --git a/test/lib/jdk/test/lib/jfr/EventNames.java b/test/lib/jdk/test/lib/jfr/EventNames.java index 74c185d25149e19719949ad95bb5df5701b100aa..bb80e6b29f24e2be9d9983764f107a5645e6765c 100644 --- a/test/lib/jdk/test/lib/jfr/EventNames.java +++ b/test/lib/jdk/test/lib/jfr/EventNames.java @@ -179,6 +179,8 @@ public class EventNames { public final static String CPUTimeStampCounter = PREFIX + "CPUTimeStampCounter"; public final static String ActiveRecording = PREFIX + "ActiveRecording"; public final static String ActiveSetting = PREFIX + "ActiveSetting"; + public final static String OptoInstanceObjectAllocation = PREFIX + "OptoInstanceObjectAllocation"; + public final static String OptoArrayObjectAllocation = PREFIX + "OptoArrayObjectAllocation"; public static boolean isGcEvent(EventType et) { return et.getCategoryNames().contains(GC_CATEGORY);