8203394: Implementation of JEP 331: Low-Overhead Heap Profiling
Implement Low-Overhead Heap Profiling Reviewed-by: eosterlund, gthornbr, rehn, sspitsyn, tschatzl
This commit is contained in:
parent
724e41cbb6
commit
6129ed590c
@ -6153,6 +6153,9 @@
|
||||
<df name="IsModifiableModule">
|
||||
<in>libIsModifiableModuleTest.c</in>
|
||||
</df>
|
||||
<df name="HeapMonitorModule">
|
||||
<in>libHeapMonitorTest.c</in>
|
||||
</df>
|
||||
<df name="ModuleAwareAgents">
|
||||
<df name="ClassFileLoadHook">
|
||||
<in>libMAAClassFileLoadHook.c</in>
|
||||
@ -40154,6 +40157,11 @@
|
||||
tool="0"
|
||||
flavor2="0">
|
||||
</item>
|
||||
<item path="../../test/hotspot/jtreg/serviceability/jvmti/HeapMonitor/libHeapMonitorTest.c"
|
||||
ex="false"
|
||||
tool="0"
|
||||
flavor2="0">
|
||||
</item>
|
||||
<item path="../../test/hotspot/jtreg/serviceability/jvmti/ModuleAwareAgents/ClassFileLoadHook/libMAAClassFileLoadHook.c"
|
||||
ex="false"
|
||||
tool="0"
|
||||
|
@ -378,6 +378,27 @@ HeapWord* CollectedHeap::obj_allocate_raw(Klass* klass, size_t size,
|
||||
}
|
||||
|
||||
HeapWord* CollectedHeap::allocate_from_tlab_slow(Klass* klass, size_t size, TRAPS) {
|
||||
HeapWord* obj = NULL;
|
||||
|
||||
// In assertion mode, check that there was a sampling collector present
|
||||
// in the stack. This enforces checking that no path is without a sampling
|
||||
// collector.
|
||||
// Only check if the sampler could actually sample something in this call path.
|
||||
assert(!JvmtiExport::should_post_sampled_object_alloc()
|
||||
|| !JvmtiSampledObjectAllocEventCollector::object_alloc_is_safe_to_sample()
|
||||
|| THREAD->heap_sampler().sampling_collector_present(),
|
||||
"Sampling collector not present.");
|
||||
|
||||
if (ThreadHeapSampler::enabled()) {
|
||||
// Try to allocate the sampled object from TLAB, it is possible a sample
|
||||
// point was put and the TLAB still has space.
|
||||
obj = THREAD->tlab().allocate_sampled_object(size);
|
||||
|
||||
if (obj != NULL) {
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
|
||||
ThreadLocalAllocBuffer& tlab = THREAD->tlab();
|
||||
|
||||
// Retain tlab and allocate object in shared space if
|
||||
@ -401,7 +422,7 @@ HeapWord* CollectedHeap::allocate_from_tlab_slow(Klass* klass, size_t size, TRAP
|
||||
// between minimal and new_tlab_size is accepted.
|
||||
size_t actual_tlab_size = 0;
|
||||
size_t min_tlab_size = ThreadLocalAllocBuffer::compute_min_size(size);
|
||||
HeapWord* obj = Universe::heap()->allocate_new_tlab(min_tlab_size, new_tlab_size, &actual_tlab_size);
|
||||
obj = Universe::heap()->allocate_new_tlab(min_tlab_size, new_tlab_size, &actual_tlab_size);
|
||||
if (obj == NULL) {
|
||||
assert(actual_tlab_size == 0, "Allocation failed, but actual size was updated. min: " SIZE_FORMAT ", desired: " SIZE_FORMAT ", actual: " SIZE_FORMAT,
|
||||
min_tlab_size, new_tlab_size, actual_tlab_size);
|
||||
@ -425,6 +446,14 @@ HeapWord* CollectedHeap::allocate_from_tlab_slow(Klass* klass, size_t size, TRAP
|
||||
Copy::fill_to_words(obj + hdr_size, actual_tlab_size - hdr_size, badHeapWordVal);
|
||||
#endif // ASSERT
|
||||
}
|
||||
|
||||
// Send the thread information about this allocation in case a sample is
|
||||
// requested.
|
||||
if (ThreadHeapSampler::enabled()) {
|
||||
size_t tlab_bytes_since_last_sample = THREAD->tlab().bytes_since_last_sample_point();
|
||||
THREAD->heap_sampler().check_for_sampling(obj, size, tlab_bytes_since_last_sample);
|
||||
}
|
||||
|
||||
tlab.fill(obj, obj + size, actual_tlab_size);
|
||||
return obj;
|
||||
}
|
||||
|
@ -194,6 +194,18 @@ class CollectedHeap : public CHeapObj<mtInternal> {
|
||||
|
||||
virtual void trace_heap(GCWhen::Type when, const GCTracer* tracer);
|
||||
|
||||
// Internal allocation methods.
|
||||
inline static HeapWord* common_allocate_memory(Klass* klass, int size,
|
||||
void (*post_setup)(Klass*, HeapWord*, int),
|
||||
int size_for_post, bool init_memory,
|
||||
TRAPS);
|
||||
|
||||
// Internal allocation method for common obj/class/array allocations.
|
||||
inline static HeapWord* allocate_memory(Klass* klass, int size,
|
||||
void (*post_setup)(Klass*, HeapWord*, int),
|
||||
int size_for_post, bool init_memory,
|
||||
TRAPS);
|
||||
|
||||
// Verification functions
|
||||
virtual void check_for_bad_heap_word_value(HeapWord* addr, size_t size)
|
||||
PRODUCT_RETURN;
|
||||
|
@ -34,6 +34,7 @@
|
||||
#include "oops/oop.inline.hpp"
|
||||
#include "prims/jvmtiExport.hpp"
|
||||
#include "runtime/sharedRuntime.hpp"
|
||||
#include "runtime/handles.inline.hpp"
|
||||
#include "runtime/thread.inline.hpp"
|
||||
#include "services/lowMemoryDetector.hpp"
|
||||
#include "utilities/align.hpp"
|
||||
@ -200,9 +201,15 @@ HeapWord* CollectedHeap::allocate_outside_tlab(Klass* klass, size_t size,
|
||||
NOT_PRODUCT(Universe::heap()->check_for_non_bad_heap_word_value(result, size));
|
||||
assert(!HAS_PENDING_EXCEPTION,
|
||||
"Unexpected exception, will result in uninitialized storage");
|
||||
THREAD->incr_allocated_bytes(size * HeapWordSize);
|
||||
size_t size_in_bytes = size * HeapWordSize;
|
||||
THREAD->incr_allocated_bytes(size_in_bytes);
|
||||
|
||||
AllocTracer::send_allocation_outside_tlab(klass, result, size_in_bytes, THREAD);
|
||||
|
||||
if (ThreadHeapSampler::enabled()) {
|
||||
THREAD->heap_sampler().check_for_sampling(result, size_in_bytes);
|
||||
}
|
||||
|
||||
AllocTracer::send_allocation_outside_tlab(klass, result, size * HeapWordSize, THREAD);
|
||||
return result;
|
||||
}
|
||||
|
||||
@ -214,12 +221,58 @@ void CollectedHeap::init_obj(HeapWord* obj, size_t size) {
|
||||
Copy::fill_to_aligned_words(obj + hs, size - hs);
|
||||
}
|
||||
|
||||
HeapWord* CollectedHeap::common_allocate_memory(Klass* klass, int size,
|
||||
void (*post_setup)(Klass*, HeapWord*, int),
|
||||
int size_for_post, bool init_memory,
|
||||
TRAPS) {
|
||||
HeapWord* obj;
|
||||
if (init_memory) {
|
||||
obj = common_mem_allocate_init(klass, size, CHECK_NULL);
|
||||
} else {
|
||||
obj = common_mem_allocate_noinit(klass, size, CHECK_NULL);
|
||||
}
|
||||
post_setup(klass, obj, size_for_post);
|
||||
return obj;
|
||||
}
|
||||
|
||||
HeapWord* CollectedHeap::allocate_memory(Klass* klass, int size,
|
||||
void (*post_setup)(Klass*, HeapWord*, int),
|
||||
int size_for_post, bool init_memory,
|
||||
TRAPS) {
|
||||
HeapWord* obj;
|
||||
|
||||
assert(JavaThread::current()->heap_sampler().add_sampling_collector(),
|
||||
"Should never return false.");
|
||||
|
||||
if (JvmtiExport::should_post_sampled_object_alloc()) {
|
||||
HandleMark hm(THREAD);
|
||||
Handle obj_h;
|
||||
{
|
||||
JvmtiSampledObjectAllocEventCollector collector;
|
||||
obj = common_allocate_memory(klass, size, post_setup, size_for_post,
|
||||
init_memory, CHECK_NULL);
|
||||
// If we want to be sampling, protect the allocated object with a Handle
|
||||
// before doing the callback. The callback is done in the destructor of
|
||||
// the JvmtiSampledObjectAllocEventCollector.
|
||||
obj_h = Handle(THREAD, (oop) obj);
|
||||
}
|
||||
obj = (HeapWord*) obj_h();
|
||||
} else {
|
||||
obj = common_allocate_memory(klass, size, post_setup, size_for_post,
|
||||
init_memory, CHECK_NULL);
|
||||
}
|
||||
|
||||
assert(JavaThread::current()->heap_sampler().remove_sampling_collector(),
|
||||
"Should never return false.");
|
||||
return obj;
|
||||
}
|
||||
|
||||
oop CollectedHeap::obj_allocate(Klass* klass, int size, TRAPS) {
|
||||
debug_only(check_for_valid_allocation_state());
|
||||
assert(!Universe::heap()->is_gc_active(), "Allocation during gc not allowed");
|
||||
assert(size >= 0, "int won't convert to size_t");
|
||||
HeapWord* obj = common_mem_allocate_init(klass, size, CHECK_NULL);
|
||||
post_allocation_setup_obj(klass, obj, size);
|
||||
HeapWord* obj = allocate_memory(klass, size, post_allocation_setup_obj,
|
||||
size, true, CHECK_NULL);
|
||||
NOT_PRODUCT(Universe::heap()->check_for_bad_heap_word_value(obj, size));
|
||||
return (oop)obj;
|
||||
}
|
||||
@ -228,8 +281,8 @@ oop CollectedHeap::class_allocate(Klass* klass, int size, TRAPS) {
|
||||
debug_only(check_for_valid_allocation_state());
|
||||
assert(!Universe::heap()->is_gc_active(), "Allocation during gc not allowed");
|
||||
assert(size >= 0, "int won't convert to size_t");
|
||||
HeapWord* obj = common_mem_allocate_init(klass, size, CHECK_NULL);
|
||||
post_allocation_setup_class(klass, obj, size); // set oop_size
|
||||
HeapWord* obj = allocate_memory(klass, size, post_allocation_setup_class,
|
||||
size, true, CHECK_NULL);
|
||||
NOT_PRODUCT(Universe::heap()->check_for_bad_heap_word_value(obj, size));
|
||||
return (oop)obj;
|
||||
}
|
||||
@ -241,8 +294,8 @@ oop CollectedHeap::array_allocate(Klass* klass,
|
||||
debug_only(check_for_valid_allocation_state());
|
||||
assert(!Universe::heap()->is_gc_active(), "Allocation during gc not allowed");
|
||||
assert(size >= 0, "int won't convert to size_t");
|
||||
HeapWord* obj = common_mem_allocate_init(klass, size, CHECK_NULL);
|
||||
post_allocation_setup_array(klass, obj, length);
|
||||
HeapWord* obj = allocate_memory(klass, size, post_allocation_setup_array,
|
||||
length, true, CHECK_NULL);
|
||||
NOT_PRODUCT(Universe::heap()->check_for_bad_heap_word_value(obj, size));
|
||||
return (oop)obj;
|
||||
}
|
||||
@ -254,9 +307,9 @@ oop CollectedHeap::array_allocate_nozero(Klass* klass,
|
||||
debug_only(check_for_valid_allocation_state());
|
||||
assert(!Universe::heap()->is_gc_active(), "Allocation during gc not allowed");
|
||||
assert(size >= 0, "int won't convert to size_t");
|
||||
HeapWord* obj = common_mem_allocate_noinit(klass, size, CHECK_NULL);
|
||||
((oop)obj)->set_klass_gap(0);
|
||||
post_allocation_setup_array(klass, obj, length);
|
||||
|
||||
HeapWord* obj = allocate_memory(klass, size, post_allocation_setup_array,
|
||||
length, false, CHECK_NULL);
|
||||
#ifndef PRODUCT
|
||||
const size_t hs = oopDesc::header_size()+1;
|
||||
Universe::heap()->check_for_non_bad_heap_word_value(obj+hs, size-hs);
|
||||
|
@ -45,6 +45,14 @@ void ThreadLocalAllocBuffer::clear_before_allocation() {
|
||||
make_parsable(true); // also retire the TLAB
|
||||
}
|
||||
|
||||
size_t ThreadLocalAllocBuffer::remaining() {
|
||||
if (end() == NULL) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return pointer_delta(hard_end(), top());
|
||||
}
|
||||
|
||||
void ThreadLocalAllocBuffer::accumulate_statistics_before_gc() {
|
||||
global_stats()->initialize();
|
||||
|
||||
@ -121,10 +129,12 @@ void ThreadLocalAllocBuffer::make_parsable(bool retire, bool zap) {
|
||||
set_top(NULL);
|
||||
set_pf_top(NULL);
|
||||
set_end(NULL);
|
||||
set_allocation_end(NULL);
|
||||
}
|
||||
}
|
||||
assert(!(retire || ZeroTLAB) ||
|
||||
(start() == NULL && end() == NULL && top() == NULL),
|
||||
(start() == NULL && end() == NULL && top() == NULL &&
|
||||
_allocation_end == NULL),
|
||||
"TLAB must be reset");
|
||||
}
|
||||
|
||||
@ -172,8 +182,13 @@ void ThreadLocalAllocBuffer::fill(HeapWord* start,
|
||||
_allocated_size += new_size;
|
||||
print_stats("fill");
|
||||
assert(top <= start + new_size - alignment_reserve(), "size too small");
|
||||
|
||||
initialize(start, top, start + new_size - alignment_reserve());
|
||||
|
||||
if (ThreadHeapSampler::enabled()) {
|
||||
set_sample_end();
|
||||
}
|
||||
|
||||
// Reset amount of internal fragmentation
|
||||
set_refill_waste_limit(initial_refill_waste_limit());
|
||||
}
|
||||
@ -185,6 +200,7 @@ void ThreadLocalAllocBuffer::initialize(HeapWord* start,
|
||||
set_top(top);
|
||||
set_pf_top(top);
|
||||
set_end(end);
|
||||
set_allocation_end(end);
|
||||
invariants();
|
||||
}
|
||||
|
||||
@ -306,12 +322,45 @@ void ThreadLocalAllocBuffer::verify() {
|
||||
guarantee(p == top(), "end of last object must match end of space");
|
||||
}
|
||||
|
||||
void ThreadLocalAllocBuffer::set_sample_end() {
|
||||
size_t heap_words_remaining = pointer_delta(_end, _top);
|
||||
size_t bytes_until_sample = myThread()->heap_sampler().bytes_until_sample();
|
||||
size_t words_until_sample = bytes_until_sample / HeapWordSize;;
|
||||
|
||||
if (heap_words_remaining > words_until_sample) {
|
||||
HeapWord* new_end = _top + words_until_sample;
|
||||
set_end(new_end);
|
||||
_bytes_since_last_sample_point = bytes_until_sample;
|
||||
} else {
|
||||
_bytes_since_last_sample_point = heap_words_remaining * HeapWordSize;;
|
||||
}
|
||||
}
|
||||
|
||||
Thread* ThreadLocalAllocBuffer::myThread() {
|
||||
return (Thread*)(((char *)this) +
|
||||
in_bytes(start_offset()) -
|
||||
in_bytes(Thread::tlab_start_offset()));
|
||||
}
|
||||
|
||||
void ThreadLocalAllocBuffer::set_back_allocation_end() {
|
||||
_end = _allocation_end;
|
||||
}
|
||||
|
||||
HeapWord* ThreadLocalAllocBuffer::allocate_sampled_object(size_t size) {
|
||||
set_back_allocation_end();
|
||||
HeapWord* result = allocate(size);
|
||||
|
||||
if (result) {
|
||||
myThread()->heap_sampler().check_for_sampling(result, size * HeapWordSize, _bytes_since_last_sample_point);
|
||||
set_sample_end();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
HeapWord* ThreadLocalAllocBuffer::hard_end() {
|
||||
return _allocation_end + alignment_reserve();
|
||||
}
|
||||
|
||||
GlobalTLABStats::GlobalTLABStats() :
|
||||
_allocating_threads_avg(TLABAllocationWeight) {
|
||||
|
@ -37,6 +37,12 @@ class GlobalTLABStats;
|
||||
// It is thread-private at any time, but maybe multiplexed over
|
||||
// time across multiple threads. The park()/unpark() pair is
|
||||
// used to make it available for such multiplexing.
|
||||
//
|
||||
// Heap sampling is performed via the end and allocation_end
|
||||
// fields.
|
||||
// allocation_end contains the real end of the tlab allocation,
|
||||
// whereas end can be set to an arbitrary spot in the tlab to
|
||||
// trip the return and sample the allocation.
|
||||
class ThreadLocalAllocBuffer: public CHeapObj<mtThread> {
|
||||
friend class VMStructs;
|
||||
friend class JVMCIVMStructs;
|
||||
@ -44,10 +50,13 @@ private:
|
||||
HeapWord* _start; // address of TLAB
|
||||
HeapWord* _top; // address after last allocation
|
||||
HeapWord* _pf_top; // allocation prefetch watermark
|
||||
HeapWord* _end; // allocation end (excluding alignment_reserve)
|
||||
HeapWord* _end; // allocation end (can be the sampling end point or _allocation_end)
|
||||
HeapWord* _allocation_end; // end for allocations (actual TLAB end, excluding alignment_reserve)
|
||||
|
||||
size_t _desired_size; // desired size (including alignment_reserve)
|
||||
size_t _refill_waste_limit; // hold onto tlab if free() is larger than this
|
||||
size_t _allocated_before_last_gc; // total bytes allocated up until the last gc
|
||||
size_t _bytes_since_last_sample_point; // bytes since last sample point.
|
||||
|
||||
static size_t _max_size; // maximum size of any TLAB
|
||||
static int _reserve_for_allocation_prefetch; // Reserve at the end of the TLAB
|
||||
@ -67,6 +76,7 @@ private:
|
||||
|
||||
void set_start(HeapWord* start) { _start = start; }
|
||||
void set_end(HeapWord* end) { _end = end; }
|
||||
void set_allocation_end(HeapWord* ptr) { _allocation_end = ptr; }
|
||||
void set_top(HeapWord* top) { _top = top; }
|
||||
void set_pf_top(HeapWord* pf_top) { _pf_top = pf_top; }
|
||||
void set_desired_size(size_t desired_size) { _desired_size = desired_size; }
|
||||
@ -77,7 +87,7 @@ private:
|
||||
static int target_refills() { return _target_refills; }
|
||||
size_t initial_desired_size();
|
||||
|
||||
size_t remaining() const { return end() == NULL ? 0 : pointer_delta(hard_end(), top()); }
|
||||
size_t remaining();
|
||||
|
||||
bool is_last_allocation(HeapWord* obj, size_t size) { return pointer_delta(top(), obj) == size; }
|
||||
|
||||
@ -118,8 +128,8 @@ public:
|
||||
|
||||
HeapWord* start() const { return _start; }
|
||||
HeapWord* end() const { return _end; }
|
||||
HeapWord* hard_end() const { return _end + alignment_reserve(); }
|
||||
HeapWord* top() const { return _top; }
|
||||
HeapWord* hard_end();
|
||||
HeapWord* pf_top() const { return _pf_top; }
|
||||
size_t desired_size() const { return _desired_size; }
|
||||
size_t used() const { return pointer_delta(top(), start()); }
|
||||
@ -127,9 +137,11 @@ public:
|
||||
size_t free() const { return pointer_delta(end(), top()); }
|
||||
// Don't discard tlab if remaining space is larger than this.
|
||||
size_t refill_waste_limit() const { return _refill_waste_limit; }
|
||||
size_t bytes_since_last_sample_point() const { return _bytes_since_last_sample_point; }
|
||||
|
||||
// Allocate size HeapWords. The memory is NOT initialized to zero.
|
||||
inline HeapWord* allocate(size_t size);
|
||||
HeapWord* allocate_sampled_object(size_t size);
|
||||
|
||||
// Undo last allocation.
|
||||
inline bool undo_allocate(HeapWord* obj, size_t size);
|
||||
@ -171,6 +183,9 @@ public:
|
||||
void fill(HeapWord* start, HeapWord* top, size_t new_size);
|
||||
void initialize();
|
||||
|
||||
void set_back_allocation_end();
|
||||
void set_sample_end();
|
||||
|
||||
static size_t refill_waste_limit_increment() { return TLABWasteIncrement; }
|
||||
|
||||
template <typename T> void addresses_do(T f) {
|
||||
@ -178,6 +193,7 @@ public:
|
||||
f(&_top);
|
||||
f(&_pf_top);
|
||||
f(&_end);
|
||||
f(&_allocation_end);
|
||||
}
|
||||
|
||||
// Code generation support
|
||||
|
@ -10353,6 +10353,14 @@ myInit() {
|
||||
See <eventlink id="ClassFileLoadHook"/>.
|
||||
</description>
|
||||
</capabilityfield>
|
||||
<capabilityfield id="can_generate_sampled_object_alloc_events" since="11">
|
||||
<description>
|
||||
Can generate sampled allocation events.
|
||||
If this capability is enabled then the heap sampling method
|
||||
<functionlink id="SetHeapSamplingRate"></functionlink> can be
|
||||
called and <eventlink id="SampledObjectAlloc"></eventlink> events can be generated.
|
||||
</description>
|
||||
</capabilityfield>
|
||||
</capabilitiestypedef>
|
||||
|
||||
<function id="GetPotentialCapabilities" jkernel="yes" phase="onload" num="140">
|
||||
@ -11531,6 +11539,47 @@ myInit() {
|
||||
|
||||
</category>
|
||||
|
||||
<category id="heap_monitoring" label="Heap Monitoring">
|
||||
<function id="SetHeapSamplingRate" phase="onload" num="156" since="11">
|
||||
<synopsis>Set Heap Sampling Rate</synopsis>
|
||||
<description>
|
||||
Generate a <eventlink id="SampledObjectAlloc"/> event when objects are allocated.
|
||||
Each thread keeps a counter of bytes allocated. The event will only be generated
|
||||
when that counter exceeds an average of <paramlink id="sampling_rate"></paramlink>
|
||||
since the last sample.
|
||||
<p/>
|
||||
Setting <paramlink id="sampling_rate"></paramlink> to 0 will cause an event to be
|
||||
generated by each allocation supported by the system.
|
||||
</description>
|
||||
<origin>new</origin>
|
||||
<capabilities>
|
||||
<required id="can_generate_sampled_object_alloc_events"></required>
|
||||
</capabilities>
|
||||
<parameters>
|
||||
<param id="sampling_rate">
|
||||
<jint/>
|
||||
<description>
|
||||
The sampling rate in bytes. The sampler uses a statistical approach to
|
||||
generate an event, on average, once for every <paramlink id="sampling_rate"/> bytes of
|
||||
memory allocated by a given thread.
|
||||
<p/>
|
||||
Passing 0 as a sampling rate generates a sample for every allocation.
|
||||
<p/>
|
||||
Note: The overhead of this feature is directly correlated with the sampling rate.
|
||||
A high sampling rate, such as 1024 bytes, will incur a high overhead.
|
||||
A lower rate, such as 1024KB, will have a much lower overhead. Sampling should only
|
||||
be used with an understanding that it may impact performance.
|
||||
</description>
|
||||
</param>
|
||||
</parameters>
|
||||
<errors>
|
||||
<error id="JVMTI_ERROR_ILLEGAL_ARGUMENT">
|
||||
<paramlink id="sampling_rate"></paramlink> is less than zero.
|
||||
</error>
|
||||
</errors>
|
||||
</function>
|
||||
</category>
|
||||
|
||||
</functionsection>
|
||||
|
||||
<errorsection label="Error Reference">
|
||||
@ -13495,13 +13544,13 @@ myInit() {
|
||||
<param id="object">
|
||||
<jobject/>
|
||||
<description>
|
||||
JNI local reference to the object that was allocated
|
||||
JNI local reference to the object that was allocated.
|
||||
</description>
|
||||
</param>
|
||||
<param id="object_klass">
|
||||
<jclass/>
|
||||
<description>
|
||||
JNI local reference to the class of the object
|
||||
JNI local reference to the class of the object.
|
||||
</description>
|
||||
</param>
|
||||
<param id="size">
|
||||
@ -13513,8 +13562,75 @@ myInit() {
|
||||
</parameters>
|
||||
</event>
|
||||
|
||||
<event label="Sampled Object Allocation"
|
||||
id="SampledObjectAlloc" const="JVMTI_EVENT_SAMPLED_OBJECT_ALLOC" num="86" since="11">
|
||||
<description>
|
||||
Sent when an allocated object is sampled.
|
||||
By default, the sampling rate is a geometric variable with a 512KB mean.
|
||||
Each thread tracks how many bytes it has allocated since it sent the last event.
|
||||
When the number of bytes exceeds the sampling rate, it will send another event.
|
||||
This implies that, on average, one object will be sampled every time a thread has
|
||||
allocated 512KB bytes since the last sample.
|
||||
<p/>
|
||||
Note that this is a geometric variable: it will not sample every 512KB precisely.
|
||||
The goal of this is to ensure high quality sampling even if allocation is
|
||||
happening in a fixed pattern (i.e., the same set of objects are being allocated
|
||||
every 512KB).
|
||||
<p/>
|
||||
If another sampling rate is required, the user can call
|
||||
<functionlink id="SetHeapSamplingRate"></functionlink> with a strictly positive integer value, representing
|
||||
the new sampling rate.
|
||||
<p/>
|
||||
This event is sent once the sampled allocation has been performed. It provides the object, stack trace
|
||||
of the allocation, the thread allocating, the size of allocation, and the object's class.
|
||||
<p/>
|
||||
A typical use case of this system is to determine where heap allocations originate.
|
||||
In conjunction with weak references and the function
|
||||
<functionlink id="GetStackTrace"></functionlink>, a user can track which objects were allocated from which
|
||||
stack trace, and which are still live during the execution of the program.
|
||||
</description>
|
||||
<origin>new</origin>
|
||||
<capabilities>
|
||||
<required id="can_generate_sampled_object_alloc_events"></required>
|
||||
</capabilities>
|
||||
<parameters>
|
||||
<param id="jni_env">
|
||||
<outptr>
|
||||
<struct>JNIEnv</struct>
|
||||
</outptr>
|
||||
<description>
|
||||
The JNI environment of the event (current) thread.
|
||||
</description>
|
||||
</param>
|
||||
<param id="thread">
|
||||
<jthread/>
|
||||
<description>
|
||||
Thread allocating the object.
|
||||
</description>
|
||||
</param>
|
||||
<param id="object">
|
||||
<jobject/>
|
||||
<description>
|
||||
JNI local reference to the object that was allocated.
|
||||
</description>
|
||||
</param>
|
||||
<param id="object_klass">
|
||||
<jclass/>
|
||||
<description>
|
||||
JNI local reference to the class of the object
|
||||
</description>
|
||||
</param>
|
||||
<param id="size">
|
||||
<jlong/>
|
||||
<description>
|
||||
Size of the object (in bytes). See <functionlink id="GetObjectSize"/>.
|
||||
</description>
|
||||
</param>
|
||||
</parameters>
|
||||
</event>
|
||||
|
||||
<event label="Object Free"
|
||||
id="ObjectFree" const="JVMTI_EVENT_OBJECT_FREE" num="83">
|
||||
id="ObjectFree" const="JVMTI_EVENT_OBJECT_FREE" num="83">
|
||||
<description>
|
||||
An Object Free event is sent when the garbage collector frees an object.
|
||||
Events are only sent for tagged objects--see
|
||||
@ -13534,7 +13650,7 @@ myInit() {
|
||||
<jlong/>
|
||||
<description>
|
||||
The freed object's tag
|
||||
</description>
|
||||
</description>
|
||||
</param>
|
||||
</parameters>
|
||||
</event>
|
||||
|
@ -64,6 +64,7 @@
|
||||
#include "runtime/reflectionUtils.hpp"
|
||||
#include "runtime/signature.hpp"
|
||||
#include "runtime/thread.inline.hpp"
|
||||
#include "runtime/threadHeapSampler.hpp"
|
||||
#include "runtime/threadSMR.hpp"
|
||||
#include "runtime/timerTrace.hpp"
|
||||
#include "runtime/vframe.inline.hpp"
|
||||
@ -537,10 +538,17 @@ JvmtiEnv::SetEventNotificationMode(jvmtiEventMode mode, jvmtiEvent event_type, j
|
||||
if (event_type == JVMTI_EVENT_CLASS_FILE_LOAD_HOOK && enabled) {
|
||||
record_class_file_load_hook_enabled();
|
||||
}
|
||||
|
||||
if (event_type == JVMTI_EVENT_SAMPLED_OBJECT_ALLOC) {
|
||||
if (enabled) {
|
||||
ThreadHeapSampler::enable();
|
||||
} else {
|
||||
ThreadHeapSampler::disable();
|
||||
}
|
||||
}
|
||||
JvmtiEventController::set_user_enabled(this, (JavaThread*) NULL, event_type, enabled);
|
||||
} else {
|
||||
// We have a specified event_thread.
|
||||
|
||||
JavaThread* java_thread = NULL;
|
||||
ThreadsListHandle tlh;
|
||||
jvmtiError err = JvmtiExport::cv_external_thread_to_JavaThread(tlh.list(), event_thread, &java_thread, NULL);
|
||||
@ -3631,6 +3639,15 @@ JvmtiEnv::GetAvailableProcessors(jint* processor_count_ptr) {
|
||||
return JVMTI_ERROR_NONE;
|
||||
} /* end GetAvailableProcessors */
|
||||
|
||||
jvmtiError
|
||||
JvmtiEnv::SetHeapSamplingRate(jint sampling_rate) {
|
||||
if (sampling_rate < 0) {
|
||||
return JVMTI_ERROR_ILLEGAL_ARGUMENT;
|
||||
}
|
||||
ThreadHeapSampler::set_sampling_rate(sampling_rate);
|
||||
return JVMTI_ERROR_NONE;
|
||||
} /* end SetHeapSamplingRate */
|
||||
|
||||
//
|
||||
// System Properties functions
|
||||
//
|
||||
|
@ -84,6 +84,7 @@ static const jlong GARBAGE_COLLECTION_FINISH_BIT = (((jlong)1) << (JVMTI_EVENT_
|
||||
static const jlong OBJECT_FREE_BIT = (((jlong)1) << (JVMTI_EVENT_OBJECT_FREE - TOTAL_MIN_EVENT_TYPE_VAL));
|
||||
static const jlong RESOURCE_EXHAUSTED_BIT = (((jlong)1) << (JVMTI_EVENT_RESOURCE_EXHAUSTED - TOTAL_MIN_EVENT_TYPE_VAL));
|
||||
static const jlong VM_OBJECT_ALLOC_BIT = (((jlong)1) << (JVMTI_EVENT_VM_OBJECT_ALLOC - TOTAL_MIN_EVENT_TYPE_VAL));
|
||||
static const jlong SAMPLED_OBJECT_ALLOC_BIT = (((jlong)1) << (JVMTI_EVENT_SAMPLED_OBJECT_ALLOC - TOTAL_MIN_EVENT_TYPE_VAL));
|
||||
|
||||
// bits for extension events
|
||||
static const jlong CLASS_UNLOAD_BIT = (((jlong)1) << (EXT_EVENT_CLASS_UNLOAD - TOTAL_MIN_EVENT_TYPE_VAL));
|
||||
@ -620,6 +621,7 @@ JvmtiEventControllerPrivate::recompute_enabled() {
|
||||
JvmtiExport::set_should_post_compiled_method_load((any_env_thread_enabled & COMPILED_METHOD_LOAD_BIT) != 0);
|
||||
JvmtiExport::set_should_post_compiled_method_unload((any_env_thread_enabled & COMPILED_METHOD_UNLOAD_BIT) != 0);
|
||||
JvmtiExport::set_should_post_vm_object_alloc((any_env_thread_enabled & VM_OBJECT_ALLOC_BIT) != 0);
|
||||
JvmtiExport::set_should_post_sampled_object_alloc((any_env_thread_enabled & SAMPLED_OBJECT_ALLOC_BIT) != 0);
|
||||
|
||||
// need this if we want thread events or we need them to init data
|
||||
JvmtiExport::set_should_post_thread_life((any_env_thread_enabled & NEED_THREAD_LIFE_EVENTS) != 0);
|
||||
|
@ -1028,12 +1028,12 @@ static inline Klass* oop_to_klass(oop obj) {
|
||||
return k;
|
||||
}
|
||||
|
||||
class JvmtiVMObjectAllocEventMark : public JvmtiClassEventMark {
|
||||
class JvmtiObjectAllocEventMark : public JvmtiClassEventMark {
|
||||
private:
|
||||
jobject _jobj;
|
||||
jlong _size;
|
||||
public:
|
||||
JvmtiVMObjectAllocEventMark(JavaThread *thread, oop obj) : JvmtiClassEventMark(thread, oop_to_klass(obj)) {
|
||||
JvmtiObjectAllocEventMark(JavaThread *thread, oop obj) : JvmtiClassEventMark(thread, oop_to_klass(obj)) {
|
||||
_jobj = (jobject)to_jobject(obj);
|
||||
_size = obj->size() * wordSize;
|
||||
};
|
||||
@ -1198,6 +1198,7 @@ bool JvmtiExport::_should_post_garbage_collection_finish = fals
|
||||
bool JvmtiExport::_should_post_object_free = false;
|
||||
bool JvmtiExport::_should_post_resource_exhausted = false;
|
||||
bool JvmtiExport::_should_post_vm_object_alloc = false;
|
||||
bool JvmtiExport::_should_post_sampled_object_alloc = false;
|
||||
bool JvmtiExport::_should_post_on_exceptions = false;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
@ -2280,7 +2281,7 @@ void JvmtiExport::record_vm_internal_object_allocation(oop obj) {
|
||||
// Can not take safepoint here so can not use state_for to get
|
||||
// jvmti thread state.
|
||||
JvmtiThreadState *state = ((JavaThread*)thread)->jvmti_thread_state();
|
||||
if (state != NULL ) {
|
||||
if (state != NULL) {
|
||||
// state is non NULL when VMObjectAllocEventCollector is enabled.
|
||||
JvmtiVMObjectAllocEventCollector *collector;
|
||||
collector = state->get_vm_object_alloc_event_collector();
|
||||
@ -2295,6 +2296,27 @@ void JvmtiExport::record_vm_internal_object_allocation(oop obj) {
|
||||
}
|
||||
}
|
||||
|
||||
// Collect all the sampled allocated objects.
|
||||
void JvmtiExport::record_sampled_internal_object_allocation(oop obj) {
|
||||
Thread* thread = Thread::current_or_null();
|
||||
if (thread != NULL && thread->is_Java_thread()) {
|
||||
// Can not take safepoint here.
|
||||
NoSafepointVerifier no_sfpt;
|
||||
// Can not take safepoint here so can not use state_for to get
|
||||
// jvmti thread state.
|
||||
JvmtiThreadState *state = ((JavaThread*)thread)->jvmti_thread_state();
|
||||
if (state != NULL) {
|
||||
// state is non NULL when SampledObjectAllocEventCollector is enabled.
|
||||
JvmtiSampledObjectAllocEventCollector *collector;
|
||||
collector = state->get_sampled_object_alloc_event_collector();
|
||||
|
||||
if (collector != NULL && collector->is_enabled()) {
|
||||
collector->record_allocation(obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void JvmtiExport::post_garbage_collection_finish() {
|
||||
Thread *thread = Thread::current(); // this event is posted from VM-Thread.
|
||||
EVT_TRIG_TRACE(JVMTI_EVENT_GARBAGE_COLLECTION_FINISH,
|
||||
@ -2484,8 +2506,7 @@ void JvmtiExport::post_monitor_waited(JavaThread *thread, ObjectMonitor *obj_mnt
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void JvmtiExport::post_vm_object_alloc(JavaThread *thread, oop object) {
|
||||
void JvmtiExport::post_vm_object_alloc(JavaThread *thread, oop object) {
|
||||
EVT_TRIG_TRACE(JVMTI_EVENT_VM_OBJECT_ALLOC, ("[%s] Trg vm object alloc triggered",
|
||||
JvmtiTrace::safe_get_thread_name(thread)));
|
||||
if (object == NULL) {
|
||||
@ -2500,7 +2521,7 @@ void JvmtiExport::post_vm_object_alloc(JavaThread *thread, oop object) {
|
||||
JvmtiTrace::safe_get_thread_name(thread),
|
||||
object==NULL? "NULL" : object->klass()->external_name()));
|
||||
|
||||
JvmtiVMObjectAllocEventMark jem(thread, h());
|
||||
JvmtiObjectAllocEventMark jem(thread, h());
|
||||
JvmtiJavaThreadEventTransition jet(thread);
|
||||
jvmtiEventVMObjectAlloc callback = env->callbacks()->VMObjectAlloc;
|
||||
if (callback != NULL) {
|
||||
@ -2511,6 +2532,34 @@ void JvmtiExport::post_vm_object_alloc(JavaThread *thread, oop object) {
|
||||
}
|
||||
}
|
||||
|
||||
void JvmtiExport::post_sampled_object_alloc(JavaThread *thread, oop object) {
|
||||
EVT_TRIG_TRACE(JVMTI_EVENT_SAMPLED_OBJECT_ALLOC,
|
||||
("[%s] Trg sampled object alloc triggered",
|
||||
JvmtiTrace::safe_get_thread_name(thread)));
|
||||
if (object == NULL) {
|
||||
return;
|
||||
}
|
||||
HandleMark hm(thread);
|
||||
Handle h(thread, object);
|
||||
JvmtiEnvIterator it;
|
||||
for (JvmtiEnv* env = it.first(); env != NULL; env = it.next(env)) {
|
||||
if (env->is_enabled(JVMTI_EVENT_SAMPLED_OBJECT_ALLOC)) {
|
||||
EVT_TRACE(JVMTI_EVENT_SAMPLED_OBJECT_ALLOC,
|
||||
("[%s] Evt sampled object alloc sent %s",
|
||||
JvmtiTrace::safe_get_thread_name(thread),
|
||||
object == NULL ? "NULL" : object->klass()->external_name()));
|
||||
|
||||
JvmtiObjectAllocEventMark jem(thread, h());
|
||||
JvmtiJavaThreadEventTransition jet(thread);
|
||||
jvmtiEventSampledObjectAlloc callback = env->callbacks()->SampledObjectAlloc;
|
||||
if (callback != NULL) {
|
||||
(*callback)(env->jvmti_external(), jem.jni_env(), jem.jni_thread(),
|
||||
jem.jni_jobject(), jem.jni_class(), jem.size());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void JvmtiExport::cleanup_thread(JavaThread* thread) {
|
||||
@ -2536,7 +2585,7 @@ void JvmtiExport::clear_detected_exception(JavaThread* thread) {
|
||||
|
||||
void JvmtiExport::oops_do(OopClosure* f) {
|
||||
JvmtiCurrentBreakpoints::oops_do(f);
|
||||
JvmtiVMObjectAllocEventCollector::oops_do_for_all_threads(f);
|
||||
JvmtiObjectAllocEventCollector::oops_do_for_all_threads(f);
|
||||
}
|
||||
|
||||
void JvmtiExport::weak_oops_do(BoolObjectClosure* is_alive, OopClosure* f) {
|
||||
@ -2669,12 +2718,28 @@ void JvmtiEventCollector::setup_jvmti_thread_state() {
|
||||
} else if (is_dynamic_code_event()) {
|
||||
_prev = state->get_dynamic_code_event_collector();
|
||||
state->set_dynamic_code_event_collector((JvmtiDynamicCodeEventCollector *)this);
|
||||
} else if (is_sampled_object_alloc_event()) {
|
||||
JvmtiSampledObjectAllocEventCollector *prev = state->get_sampled_object_alloc_event_collector();
|
||||
|
||||
if (prev) {
|
||||
// JvmtiSampledObjectAllocEventCollector wants only one active collector
|
||||
// enabled. This allows to have a collector detect a user code requiring
|
||||
// a sample in the callback.
|
||||
return;
|
||||
}
|
||||
state->set_sampled_object_alloc_event_collector((JvmtiSampledObjectAllocEventCollector*) this);
|
||||
}
|
||||
|
||||
_unset_jvmti_thread_state = true;
|
||||
}
|
||||
|
||||
// Unset current event collection in this thread and reset it with previous
|
||||
// collector.
|
||||
void JvmtiEventCollector::unset_jvmti_thread_state() {
|
||||
if (!_unset_jvmti_thread_state) {
|
||||
return;
|
||||
}
|
||||
|
||||
JvmtiThreadState* state = JavaThread::current()->jvmti_thread_state();
|
||||
if (state != NULL) {
|
||||
// restore the previous event collector (if any)
|
||||
@ -2685,14 +2750,19 @@ void JvmtiEventCollector::unset_jvmti_thread_state() {
|
||||
// this thread's jvmti state was created during the scope of
|
||||
// the event collector.
|
||||
}
|
||||
} else {
|
||||
if (is_dynamic_code_event()) {
|
||||
if (state->get_dynamic_code_event_collector() == this) {
|
||||
state->set_dynamic_code_event_collector((JvmtiDynamicCodeEventCollector *)_prev);
|
||||
} else {
|
||||
// this thread's jvmti state was created during the scope of
|
||||
// the event collector.
|
||||
}
|
||||
} else if (is_dynamic_code_event()) {
|
||||
if (state->get_dynamic_code_event_collector() == this) {
|
||||
state->set_dynamic_code_event_collector((JvmtiDynamicCodeEventCollector *)_prev);
|
||||
} else {
|
||||
// this thread's jvmti state was created during the scope of
|
||||
// the event collector.
|
||||
}
|
||||
} else if (is_sampled_object_alloc_event()) {
|
||||
if (state->get_sampled_object_alloc_event_collector() == this) {
|
||||
state->set_sampled_object_alloc_event_collector((JvmtiSampledObjectAllocEventCollector*)_prev);
|
||||
} else {
|
||||
// this thread's jvmti state was created during the scope of
|
||||
// the event collector.
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2730,31 +2800,25 @@ void JvmtiDynamicCodeEventCollector::register_stub(const char* name, address sta
|
||||
}
|
||||
|
||||
// Setup current thread to record vm allocated objects.
|
||||
JvmtiVMObjectAllocEventCollector::JvmtiVMObjectAllocEventCollector() : _allocated(NULL) {
|
||||
if (JvmtiExport::should_post_vm_object_alloc()) {
|
||||
_enable = true;
|
||||
setup_jvmti_thread_state();
|
||||
} else {
|
||||
_enable = false;
|
||||
}
|
||||
JvmtiObjectAllocEventCollector::JvmtiObjectAllocEventCollector() :
|
||||
_allocated(NULL), _enable(false), _post_callback(NULL) {
|
||||
}
|
||||
|
||||
// Post vm_object_alloc event for vm allocated objects visible to java
|
||||
// world.
|
||||
JvmtiVMObjectAllocEventCollector::~JvmtiVMObjectAllocEventCollector() {
|
||||
if (_allocated != NULL) {
|
||||
void JvmtiObjectAllocEventCollector::generate_call_for_allocated() {
|
||||
if (_allocated) {
|
||||
set_enabled(false);
|
||||
for (int i = 0; i < _allocated->length(); i++) {
|
||||
oop obj = _allocated->at(i);
|
||||
JvmtiExport::post_vm_object_alloc(JavaThread::current(), obj);
|
||||
_post_callback(JavaThread::current(), obj);
|
||||
}
|
||||
delete _allocated;
|
||||
delete _allocated, _allocated = NULL;
|
||||
}
|
||||
unset_jvmti_thread_state();
|
||||
}
|
||||
|
||||
void JvmtiVMObjectAllocEventCollector::record_allocation(oop obj) {
|
||||
assert(is_enabled(), "VM object alloc event collector is not enabled");
|
||||
void JvmtiObjectAllocEventCollector::record_allocation(oop obj) {
|
||||
assert(is_enabled(), "Object alloc event collector is not enabled");
|
||||
if (_allocated == NULL) {
|
||||
_allocated = new (ResourceObj::C_HEAP, mtInternal) GrowableArray<oop>(1, true);
|
||||
}
|
||||
@ -2762,9 +2826,9 @@ void JvmtiVMObjectAllocEventCollector::record_allocation(oop obj) {
|
||||
}
|
||||
|
||||
// GC support.
|
||||
void JvmtiVMObjectAllocEventCollector::oops_do(OopClosure* f) {
|
||||
if (_allocated != NULL) {
|
||||
for(int i=_allocated->length() - 1; i >= 0; i--) {
|
||||
void JvmtiObjectAllocEventCollector::oops_do(OopClosure* f) {
|
||||
if (_allocated) {
|
||||
for(int i = _allocated->length() - 1; i >= 0; i--) {
|
||||
if (_allocated->at(i) != NULL) {
|
||||
f->do_oop(_allocated->adr_at(i));
|
||||
}
|
||||
@ -2772,7 +2836,7 @@ void JvmtiVMObjectAllocEventCollector::oops_do(OopClosure* f) {
|
||||
}
|
||||
}
|
||||
|
||||
void JvmtiVMObjectAllocEventCollector::oops_do_for_all_threads(OopClosure* f) {
|
||||
void JvmtiObjectAllocEventCollector::oops_do_for_all_threads(OopClosure* f) {
|
||||
// no-op if jvmti not enabled
|
||||
if (!JvmtiEnv::environments_might_exist()) {
|
||||
return;
|
||||
@ -2781,11 +2845,17 @@ void JvmtiVMObjectAllocEventCollector::oops_do_for_all_threads(OopClosure* f) {
|
||||
for (JavaThreadIteratorWithHandle jtiwh; JavaThread *jthr = jtiwh.next(); ) {
|
||||
JvmtiThreadState *state = jthr->jvmti_thread_state();
|
||||
if (state != NULL) {
|
||||
JvmtiVMObjectAllocEventCollector *collector;
|
||||
JvmtiObjectAllocEventCollector *collector;
|
||||
collector = state->get_vm_object_alloc_event_collector();
|
||||
while (collector != NULL) {
|
||||
collector->oops_do(f);
|
||||
collector = (JvmtiVMObjectAllocEventCollector *)collector->get_prev();
|
||||
collector = (JvmtiObjectAllocEventCollector*) collector->get_prev();
|
||||
}
|
||||
|
||||
collector = state->get_sampled_object_alloc_event_collector();
|
||||
while (collector != NULL) {
|
||||
collector->oops_do(f);
|
||||
collector = (JvmtiObjectAllocEventCollector*) collector->get_prev();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2820,6 +2890,63 @@ NoJvmtiVMObjectAllocMark::~NoJvmtiVMObjectAllocMark() {
|
||||
}
|
||||
};
|
||||
|
||||
// Setup current thread to record vm allocated objects.
|
||||
JvmtiVMObjectAllocEventCollector::JvmtiVMObjectAllocEventCollector() {
|
||||
if (JvmtiExport::should_post_vm_object_alloc()) {
|
||||
_enable = true;
|
||||
setup_jvmti_thread_state();
|
||||
_post_callback = JvmtiExport::post_vm_object_alloc;
|
||||
}
|
||||
}
|
||||
|
||||
JvmtiVMObjectAllocEventCollector::~JvmtiVMObjectAllocEventCollector() {
|
||||
if (_enable) {
|
||||
generate_call_for_allocated();
|
||||
}
|
||||
unset_jvmti_thread_state();
|
||||
}
|
||||
|
||||
bool JvmtiSampledObjectAllocEventCollector::object_alloc_is_safe_to_sample() {
|
||||
Thread* thread = Thread::current();
|
||||
// Really only sample allocations if this is a JavaThread and not the compiler
|
||||
// thread.
|
||||
if (!thread->is_Java_thread() || thread->is_Compiler_thread()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Compile_lock->owner() == thread ||
|
||||
MultiArray_lock->owner() == thread) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Setup current thread to record sampled allocated objects.
|
||||
JvmtiSampledObjectAllocEventCollector::JvmtiSampledObjectAllocEventCollector() {
|
||||
if (JvmtiExport::should_post_sampled_object_alloc()) {
|
||||
if (!object_alloc_is_safe_to_sample()) {
|
||||
return;
|
||||
}
|
||||
|
||||
_enable = true;
|
||||
setup_jvmti_thread_state();
|
||||
_post_callback = JvmtiExport::post_sampled_object_alloc;
|
||||
}
|
||||
}
|
||||
|
||||
JvmtiSampledObjectAllocEventCollector::~JvmtiSampledObjectAllocEventCollector() {
|
||||
if (!_enable) {
|
||||
return;
|
||||
}
|
||||
|
||||
generate_call_for_allocated();
|
||||
unset_jvmti_thread_state();
|
||||
|
||||
// Unset the sampling collector as present in assertion mode only.
|
||||
assert(Thread::current()->is_Java_thread(),
|
||||
"Should always be in a Java thread");
|
||||
}
|
||||
|
||||
JvmtiGCMarker::JvmtiGCMarker() {
|
||||
// if there aren't any JVMTI environments then nothing to do
|
||||
if (!JvmtiEnv::environments_might_exist()) {
|
||||
|
@ -123,6 +123,7 @@ class JvmtiExport : public AllStatic {
|
||||
// breakpoint info
|
||||
JVMTI_SUPPORT_FLAG(should_clean_up_heap_objects)
|
||||
JVMTI_SUPPORT_FLAG(should_post_vm_object_alloc)
|
||||
JVMTI_SUPPORT_FLAG(should_post_sampled_object_alloc)
|
||||
|
||||
// If flag cannot be implemented, give an error if on=true
|
||||
static void report_unsupported(bool on);
|
||||
@ -363,6 +364,18 @@ class JvmtiExport : public AllStatic {
|
||||
record_vm_internal_object_allocation(object);
|
||||
}
|
||||
}
|
||||
|
||||
static void record_sampled_internal_object_allocation(oop object) NOT_JVMTI_RETURN;
|
||||
// Post objects collected by sampled_object_alloc_event_collector.
|
||||
static void post_sampled_object_alloc(JavaThread *thread, oop object) NOT_JVMTI_RETURN;
|
||||
|
||||
// Collects vm internal objects for later event posting.
|
||||
inline static void sampled_object_alloc_event_collector(oop object) {
|
||||
if (should_post_sampled_object_alloc()) {
|
||||
record_sampled_internal_object_allocation(object);
|
||||
}
|
||||
}
|
||||
|
||||
inline static void post_array_size_exhausted() {
|
||||
if (should_post_resource_exhausted()) {
|
||||
post_resource_exhausted(JVMTI_RESOURCE_EXHAUSTED_OOM_ERROR,
|
||||
@ -422,12 +435,16 @@ class JvmtiCodeBlobDesc : public CHeapObj<mtInternal> {
|
||||
class JvmtiEventCollector : public StackObj {
|
||||
private:
|
||||
JvmtiEventCollector* _prev; // Save previous one to support nested event collector.
|
||||
bool _unset_jvmti_thread_state;
|
||||
|
||||
public:
|
||||
void setup_jvmti_thread_state(); // Set this collector in current thread.
|
||||
JvmtiEventCollector() : _prev(NULL), _unset_jvmti_thread_state(false) {}
|
||||
|
||||
void setup_jvmti_thread_state(); // Set this collector in current thread, returns if success.
|
||||
void unset_jvmti_thread_state(); // Reset previous collector in current thread.
|
||||
virtual bool is_dynamic_code_event() { return false; }
|
||||
virtual bool is_vm_object_alloc_event(){ return false; }
|
||||
virtual bool is_sampled_object_alloc_event(){ return false; }
|
||||
JvmtiEventCollector *get_prev() { return _prev; }
|
||||
};
|
||||
|
||||
@ -462,42 +479,67 @@ class JvmtiDynamicCodeEventCollector : public JvmtiEventCollector {
|
||||
|
||||
};
|
||||
|
||||
// Used to record vm internally allocated object oops and post
|
||||
// vm object alloc event for objects visible to java world.
|
||||
// Constructor enables JvmtiThreadState flag and all vm allocated
|
||||
// objects are recorded in a growable array. When destructor is
|
||||
// called the vm object alloc event is posted for each objects
|
||||
// visible to java world.
|
||||
// See jvm.cpp file for its usage.
|
||||
// Used as a base class for object allocation collection and then posting
|
||||
// the allocations to any event notification callbacks.
|
||||
//
|
||||
class JvmtiVMObjectAllocEventCollector : public JvmtiEventCollector {
|
||||
private:
|
||||
GrowableArray<oop>* _allocated; // field to record vm internally allocated object oop.
|
||||
bool _enable; // This flag is enabled in constructor and disabled
|
||||
// in destructor before posting event. To avoid
|
||||
class JvmtiObjectAllocEventCollector : public JvmtiEventCollector {
|
||||
protected:
|
||||
GrowableArray<oop>* _allocated; // field to record collected allocated object oop.
|
||||
bool _enable; // This flag is enabled in constructor if set up in the thread state
|
||||
// and disabled in destructor before posting event. To avoid
|
||||
// collection of objects allocated while running java code inside
|
||||
// agent post_vm_object_alloc() event handler.
|
||||
// agent post_X_object_alloc() event handler.
|
||||
void (*_post_callback)(JavaThread*, oop); // what callback to use when destroying the collector.
|
||||
|
||||
//GC support
|
||||
void oops_do(OopClosure* f);
|
||||
|
||||
friend class JvmtiExport;
|
||||
// Record vm allocated object oop.
|
||||
|
||||
// Record allocated object oop.
|
||||
inline void record_allocation(oop obj);
|
||||
|
||||
//GC support
|
||||
static void oops_do_for_all_threads(OopClosure* f);
|
||||
|
||||
public:
|
||||
JvmtiVMObjectAllocEventCollector() NOT_JVMTI_RETURN;
|
||||
~JvmtiVMObjectAllocEventCollector() NOT_JVMTI_RETURN;
|
||||
bool is_vm_object_alloc_event() { return true; }
|
||||
JvmtiObjectAllocEventCollector() NOT_JVMTI_RETURN;
|
||||
|
||||
void generate_call_for_allocated();
|
||||
|
||||
bool is_enabled() { return _enable; }
|
||||
void set_enabled(bool on) { _enable = on; }
|
||||
};
|
||||
|
||||
// Used to record vm internally allocated object oops and post
|
||||
// vm object alloc event for objects visible to java world.
|
||||
// Constructor enables JvmtiThreadState flag and all vm allocated
|
||||
// objects are recorded in a growable array. When destructor is
|
||||
// called the vm object alloc event is posted for each object
|
||||
// visible to java world.
|
||||
// See jvm.cpp file for its usage.
|
||||
//
|
||||
class JvmtiVMObjectAllocEventCollector : public JvmtiObjectAllocEventCollector {
|
||||
public:
|
||||
JvmtiVMObjectAllocEventCollector() NOT_JVMTI_RETURN;
|
||||
~JvmtiVMObjectAllocEventCollector() NOT_JVMTI_RETURN;
|
||||
virtual bool is_vm_object_alloc_event() { return true; }
|
||||
};
|
||||
|
||||
// Used to record sampled allocated object oops and post
|
||||
// sampled object alloc event.
|
||||
// Constructor enables JvmtiThreadState flag and all sampled allocated
|
||||
// objects are recorded in a growable array. When destructor is
|
||||
// called the sampled object alloc event is posted for each sampled object.
|
||||
// See jvm.cpp file for its usage.
|
||||
//
|
||||
class JvmtiSampledObjectAllocEventCollector : public JvmtiObjectAllocEventCollector {
|
||||
public:
|
||||
JvmtiSampledObjectAllocEventCollector() NOT_JVMTI_RETURN;
|
||||
~JvmtiSampledObjectAllocEventCollector() NOT_JVMTI_RETURN;
|
||||
bool is_sampled_object_alloc_event() { return true; }
|
||||
static bool object_alloc_is_safe_to_sample();
|
||||
};
|
||||
|
||||
// Marker class to disable the posting of VMObjectAlloc events
|
||||
// within its scope.
|
||||
|
@ -130,6 +130,7 @@ jvmtiCapabilities JvmtiManageCapabilities::init_always_solo_capabilities() {
|
||||
|
||||
memset(&jc, 0, sizeof(jc));
|
||||
jc.can_suspend = 1;
|
||||
jc.can_generate_sampled_object_alloc_events = 1;
|
||||
return jc;
|
||||
}
|
||||
|
||||
@ -410,6 +411,8 @@ void JvmtiManageCapabilities:: print(const jvmtiCapabilities* cap) {
|
||||
log_trace(jvmti)("can_generate_frame_pop_events");
|
||||
if (cap->can_generate_breakpoint_events)
|
||||
log_trace(jvmti)("can_generate_breakpoint_events");
|
||||
if (cap->can_generate_sampled_object_alloc_events)
|
||||
log_trace(jvmti)("can_generate_sampled_object_alloc_events");
|
||||
if (cap->can_suspend)
|
||||
log_trace(jvmti)("can_suspend");
|
||||
if (cap->can_redefine_any_class )
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2003, 2015, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2003, 2018, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -60,6 +60,7 @@ JvmtiThreadState::JvmtiThreadState(JavaThread* thread)
|
||||
_head_env_thread_state = NULL;
|
||||
_dynamic_code_event_collector = NULL;
|
||||
_vm_object_alloc_event_collector = NULL;
|
||||
_sampled_object_alloc_event_collector = NULL;
|
||||
_the_class_for_redefinition_verification = NULL;
|
||||
_scratch_class_for_redefinition_verification = NULL;
|
||||
_cur_stack_depth = UNKNOWN_STACK_DEPTH;
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2003, 2017, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2003, 2018 Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -113,6 +113,8 @@ class JvmtiThreadState : public CHeapObj<mtInternal> {
|
||||
JvmtiDynamicCodeEventCollector* _dynamic_code_event_collector;
|
||||
// holds the current vm object alloc event collector, NULL if no event collector in use
|
||||
JvmtiVMObjectAllocEventCollector* _vm_object_alloc_event_collector;
|
||||
// holds the current sampled object alloc event collector, NULL if no event collector in use
|
||||
JvmtiSampledObjectAllocEventCollector* _sampled_object_alloc_event_collector;
|
||||
|
||||
// Should only be created by factory methods
|
||||
JvmtiThreadState(JavaThread *thread);
|
||||
@ -314,12 +316,18 @@ class JvmtiThreadState : public CHeapObj<mtInternal> {
|
||||
JvmtiVMObjectAllocEventCollector* get_vm_object_alloc_event_collector() {
|
||||
return _vm_object_alloc_event_collector;
|
||||
}
|
||||
JvmtiSampledObjectAllocEventCollector* get_sampled_object_alloc_event_collector() {
|
||||
return _sampled_object_alloc_event_collector;
|
||||
}
|
||||
void set_dynamic_code_event_collector(JvmtiDynamicCodeEventCollector* collector) {
|
||||
_dynamic_code_event_collector = collector;
|
||||
}
|
||||
void set_vm_object_alloc_event_collector(JvmtiVMObjectAllocEventCollector* collector) {
|
||||
_vm_object_alloc_event_collector = collector;
|
||||
}
|
||||
void set_sampled_object_alloc_event_collector(JvmtiSampledObjectAllocEventCollector* collector) {
|
||||
_sampled_object_alloc_event_collector = collector;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
|
@ -131,6 +131,8 @@ Monitor* Service_lock = NULL;
|
||||
Monitor* PeriodicTask_lock = NULL;
|
||||
Monitor* RedefineClasses_lock = NULL;
|
||||
|
||||
Mutex* ThreadHeapSampler_lock = NULL;
|
||||
|
||||
#if INCLUDE_JFR
|
||||
Mutex* JfrStacktrace_lock = NULL;
|
||||
Monitor* JfrMsg_lock = NULL;
|
||||
@ -296,6 +298,9 @@ void mutex_init() {
|
||||
def(CompileThread_lock , PaddedMonitor, nonleaf+5, false, Monitor::_safepoint_check_always);
|
||||
def(PeriodicTask_lock , PaddedMonitor, nonleaf+5, true, Monitor::_safepoint_check_sometimes);
|
||||
def(RedefineClasses_lock , PaddedMonitor, nonleaf+5, true, Monitor::_safepoint_check_always);
|
||||
|
||||
def(ThreadHeapSampler_lock , PaddedMutex, nonleaf, false, Monitor::_safepoint_check_never);
|
||||
|
||||
if (WhiteBoxAPI) {
|
||||
def(Compilation_lock , PaddedMonitor, leaf, false, Monitor::_safepoint_check_never);
|
||||
}
|
||||
|
@ -130,6 +130,7 @@ extern Mutex* Management_lock; // a lock used to serialize JVM
|
||||
extern Monitor* Service_lock; // a lock used for service thread operation
|
||||
extern Monitor* PeriodicTask_lock; // protects the periodic task structure
|
||||
extern Monitor* RedefineClasses_lock; // locks classes from parallel redefinition
|
||||
extern Mutex* ThreadHeapSampler_lock; // protects the static data for initialization.
|
||||
|
||||
#if INCLUDE_JFR
|
||||
extern Mutex* JfrStacktrace_lock; // used to guard access to the JFR stacktrace table
|
||||
|
@ -42,6 +42,7 @@
|
||||
#include "runtime/park.hpp"
|
||||
#include "runtime/safepoint.hpp"
|
||||
#include "runtime/stubRoutines.hpp"
|
||||
#include "runtime/threadHeapSampler.hpp"
|
||||
#include "runtime/threadLocalStorage.hpp"
|
||||
#include "runtime/unhandledOops.hpp"
|
||||
#include "utilities/align.hpp"
|
||||
@ -338,6 +339,7 @@ class Thread: public ThreadShadow {
|
||||
ThreadLocalAllocBuffer _tlab; // Thread-local eden
|
||||
jlong _allocated_bytes; // Cumulative number of bytes allocated on
|
||||
// the Java heap
|
||||
ThreadHeapSampler _heap_sampler; // For use when sampling the memory.
|
||||
|
||||
JFR_ONLY(DEFINE_THREAD_LOCAL_FIELD_JFR;) // Thread-local data for jfr
|
||||
|
||||
@ -517,6 +519,8 @@ class Thread: public ThreadShadow {
|
||||
void incr_allocated_bytes(jlong size) { _allocated_bytes += size; }
|
||||
inline jlong cooked_allocated_bytes();
|
||||
|
||||
ThreadHeapSampler& heap_sampler() { return _heap_sampler; }
|
||||
|
||||
JFR_ONLY(DEFINE_THREAD_LOCAL_ACCESSOR_JFR;)
|
||||
|
||||
bool is_trace_suspend() { return (_suspend_flags & _trace_flag) != 0; }
|
||||
|
Loading…
Reference in New Issue
Block a user