# HG changeset patch # User jmanson # Date 1528155811 25200 # Mon Jun 04 16:43:31 2018 -0700 # Node ID f59e09d33b1fa64123dac3a4bf7f69db91434f0f # Parent 2bea53d7a27bfa37b15907c9b9ef24d139542b28 JEP 331 diff --git a/make/nb_native/nbproject/configurations.xml b/make/nb_native/nbproject/configurations.xml --- a/make/nb_native/nbproject/configurations.xml +++ b/make/nb_native/nbproject/configurations.xml @@ -6153,6 +6153,9 @@ libIsModifiableModuleTest.c + + libHeapMonitorTest.c + libMAAClassFileLoadHook.c @@ -40154,6 +40157,11 @@ tool="0" flavor2="0"> + + 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; + } + } // Retain tlab and allocate object in shared space if // the amount free in the tlab is too large to discard. @@ -388,7 +408,7 @@ // 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); @@ -412,6 +432,14 @@ 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); + } + thread->tlab().fill(obj, obj + size, actual_tlab_size); return obj; } diff --git a/src/hotspot/share/gc/shared/collectedHeap.hpp b/src/hotspot/share/gc/shared/collectedHeap.hpp --- a/src/hotspot/share/gc/shared/collectedHeap.hpp +++ b/src/hotspot/share/gc/shared/collectedHeap.hpp @@ -183,6 +183,18 @@ 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; diff --git a/src/hotspot/share/gc/shared/collectedHeap.inline.hpp b/src/hotspot/share/gc/shared/collectedHeap.inline.hpp --- a/src/hotspot/share/gc/shared/collectedHeap.inline.hpp +++ b/src/hotspot/share/gc/shared/collectedHeap.inline.hpp @@ -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" @@ -154,10 +155,14 @@ 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 * HeapWordSize, THREAD); + AllocTracer::send_allocation_outside_tlab(klass, result, size_in_bytes, THREAD); + if (ThreadHeapSampler::enabled()) { + THREAD->heap_sampler().check_for_sampling(result, size_in_bytes); + } return result; } @@ -212,12 +217,58 @@ 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; } @@ -226,8 +277,8 @@ 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; } @@ -239,8 +290,8 @@ 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; } @@ -252,9 +303,9 @@ 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); diff --git a/src/hotspot/share/gc/shared/threadLocalAllocBuffer.cpp b/src/hotspot/share/gc/shared/threadLocalAllocBuffer.cpp --- a/src/hotspot/share/gc/shared/threadLocalAllocBuffer.cpp +++ b/src/hotspot/share/gc/shared/threadLocalAllocBuffer.cpp @@ -45,6 +45,14 @@ 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 @@ 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 @@ _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 @@ set_top(top); set_pf_top(top); set_end(end); + set_allocation_end(end); invariants(); } @@ -306,12 +322,45 @@ 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) { diff --git a/src/hotspot/share/gc/shared/threadLocalAllocBuffer.hpp b/src/hotspot/share/gc/shared/threadLocalAllocBuffer.hpp --- a/src/hotspot/share/gc/shared/threadLocalAllocBuffer.hpp +++ b/src/hotspot/share/gc/shared/threadLocalAllocBuffer.hpp @@ -37,6 +37,12 @@ // 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 { friend class VMStructs; friend class JVMCIVMStructs; @@ -44,10 +50,13 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 void addresses_do(T f) { diff --git a/src/hotspot/share/prims/jvmti.xml b/src/hotspot/share/prims/jvmti.xml --- a/src/hotspot/share/prims/jvmti.xml +++ b/src/hotspot/share/prims/jvmti.xml @@ -10353,6 +10353,14 @@ See . + + + Can generate sampled allocation events. + If this capability is enabled then the heap sampling method + can be + called and events can be generated. + + @@ -11531,6 +11539,47 @@ + + + Set Heap Sampling Rate + + Generate a 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 + since the last sample. +

+ Setting to 0 will cause an event to be + generated by each allocation supported by the system. + + new + + + + + + + + The sampling rate in bytes. The sampler uses a statistical approach to + generate an event, on average, once for every bytes of + memory allocated by a given thread. +

+ Passing 0 as a sampling rate generates a sample for every allocation. +

+ 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. + + + + + + is less than zero. + + + + + @@ -13495,13 +13544,13 @@ - JNI local reference to the object that was allocated + JNI local reference to the object that was allocated. - JNI local reference to the class of the object + JNI local reference to the class of the object. @@ -13513,8 +13562,75 @@ + + + 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. +

+ 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). +

+ If another sampling rate is required, the user can call + with a strictly positive integer value, representing + the new sampling rate. +

+ 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. +

+ A typical use case of this system is to determine where heap allocations originate. + In conjunction with weak references and the function + , a user can track which objects were allocated from which + stack trace, and which are still live during the execution of the program. + + new + + + + + + + JNIEnv + + + The JNI environment of the event (current) thread. + + + + + + Thread allocating the object. + + + + + + JNI local reference to the object that was allocated. + + + + + + JNI local reference to the class of the object + + + + + + Size of the object (in bytes). See . + + + + + + id="ObjectFree" const="JVMTI_EVENT_OBJECT_FREE" num="83"> 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 @@ The freed object's tag - + diff --git a/src/hotspot/share/prims/jvmtiEnv.cpp b/src/hotspot/share/prims/jvmtiEnv.cpp --- a/src/hotspot/share/prims/jvmtiEnv.cpp +++ b/src/hotspot/share/prims/jvmtiEnv.cpp @@ -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 @@ 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 @@ 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 // diff --git a/src/hotspot/share/prims/jvmtiEventController.cpp b/src/hotspot/share/prims/jvmtiEventController.cpp --- a/src/hotspot/share/prims/jvmtiEventController.cpp +++ b/src/hotspot/share/prims/jvmtiEventController.cpp @@ -84,6 +84,7 @@ 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 @@ 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); diff --git a/src/hotspot/share/prims/jvmtiExport.cpp b/src/hotspot/share/prims/jvmtiExport.cpp --- a/src/hotspot/share/prims/jvmtiExport.cpp +++ b/src/hotspot/share/prims/jvmtiExport.cpp @@ -1028,12 +1028,12 @@ 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_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 @@ // 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 @@ } } +// 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_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 @@ 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_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::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 @@ } 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 @@ // 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 @@ } // 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(1, true); } @@ -2762,9 +2826,9 @@ } // 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_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 @@ 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 @@ } }; +// 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()) { diff --git a/src/hotspot/share/prims/jvmtiExport.hpp b/src/hotspot/share/prims/jvmtiExport.hpp --- a/src/hotspot/share/prims/jvmtiExport.hpp +++ b/src/hotspot/share/prims/jvmtiExport.hpp @@ -123,6 +123,7 @@ // 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 @@ 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 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 @@ }; -// 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* _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* _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. diff --git a/src/hotspot/share/prims/jvmtiManageCapabilities.cpp b/src/hotspot/share/prims/jvmtiManageCapabilities.cpp --- a/src/hotspot/share/prims/jvmtiManageCapabilities.cpp +++ b/src/hotspot/share/prims/jvmtiManageCapabilities.cpp @@ -130,6 +130,7 @@ memset(&jc, 0, sizeof(jc)); jc.can_suspend = 1; + jc.can_generate_sampled_object_alloc_events = 1; return jc; } @@ -410,6 +411,8 @@ 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 ) diff --git a/src/hotspot/share/prims/jvmtiThreadState.cpp b/src/hotspot/share/prims/jvmtiThreadState.cpp --- a/src/hotspot/share/prims/jvmtiThreadState.cpp +++ b/src/hotspot/share/prims/jvmtiThreadState.cpp @@ -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 @@ _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; diff --git a/src/hotspot/share/prims/jvmtiThreadState.hpp b/src/hotspot/share/prims/jvmtiThreadState.hpp --- a/src/hotspot/share/prims/jvmtiThreadState.hpp +++ b/src/hotspot/share/prims/jvmtiThreadState.hpp @@ -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 @@ 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 @@ 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; + } // diff --git a/src/hotspot/share/runtime/mutexLocker.cpp b/src/hotspot/share/runtime/mutexLocker.cpp --- a/src/hotspot/share/runtime/mutexLocker.cpp +++ b/src/hotspot/share/runtime/mutexLocker.cpp @@ -129,6 +129,8 @@ Monitor* PeriodicTask_lock = NULL; Monitor* RedefineClasses_lock = NULL; +Mutex* ThreadHeapSampler_lock = NULL; + #if INCLUDE_JFR Mutex* JfrStacktrace_lock = NULL; Monitor* JfrMsg_lock = NULL; @@ -291,6 +293,9 @@ 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); } diff --git a/src/hotspot/share/runtime/mutexLocker.hpp b/src/hotspot/share/runtime/mutexLocker.hpp --- a/src/hotspot/share/runtime/mutexLocker.hpp +++ b/src/hotspot/share/runtime/mutexLocker.hpp @@ -128,6 +128,7 @@ 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 diff --git a/src/hotspot/share/runtime/thread.hpp b/src/hotspot/share/runtime/thread.hpp --- a/src/hotspot/share/runtime/thread.hpp +++ b/src/hotspot/share/runtime/thread.hpp @@ -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 @@ 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 @@ 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; } diff --git a/src/hotspot/share/runtime/threadHeapSampler.cpp b/src/hotspot/share/runtime/threadHeapSampler.cpp new file mode 100644 --- /dev/null +++ b/src/hotspot/share/runtime/threadHeapSampler.cpp @@ -0,0 +1,188 @@ +/* + * Copyright (c) 2018, Google and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#include "precompiled.hpp" +#include "runtime/handles.inline.hpp" +#include "runtime/orderAccess.inline.hpp" +#include "runtime/sharedRuntime.hpp" +#include "runtime/threadHeapSampler.hpp" + +// Cheap random number generator +uint64_t ThreadHeapSampler::_rnd; +// Default is 512kb. +int ThreadHeapSampler::_sampling_rate = 512 * 1024; +int ThreadHeapSampler::_enabled; + +// Statics for the fast log +static const int FastLogNumBits = 10; +static const int FastLogMask = (1 << FastLogNumBits) - 1; +static double log_table[1<0, "bad value passed to assert"); + uint64_t x = 0; + assert(sizeof(d) == sizeof(x), + "double and uint64_t do not have the same size"); + x = *reinterpret_cast(&d); + const uint32_t x_high = x >> 32; + assert(FastLogNumBits <= 20, "FastLogNumBits should be less than 20."); + const uint32_t y = x_high >> (20 - FastLogNumBits) & FastLogMask; + const int32_t exponent = ((x_high >> 20) & 0x7FF) - 1023; + return exponent + log_table[y]; +} + +// Generates a geometric variable with the specified mean (512K by default). +// This is done by generating a random number between 0 and 1 and applying +// the inverse cumulative distribution function for an exponential. +// Specifically: Let m be the inverse of the sample rate, then +// the probability distribution function is m*exp(-mx) so the CDF is +// p = 1 - exp(-mx), so +// q = 1 - p = exp(-mx) +// log_e(q) = -mx +// -log_e(q)/m = x +// log_2(q) * (-log_e(2) * 1/m) = x +// In the code, q is actually in the range 1 to 2**26, hence the -26 below +void ThreadHeapSampler::pick_next_geometric_sample() { + _rnd = next_random(_rnd); + // Take the top 26 bits as the random number + // (This plus a 1<<58 sampling bound gives a max possible step of + // 5194297183973780480 bytes. In this case, + // for sample_parameter = 1<<19, max possible step is + // 9448372 bytes (24 bits). + const uint64_t PrngModPower = 48; // Number of bits in prng + // The uint32_t cast is to prevent a (hard-to-reproduce) NAN + // under piii debug for some binaries. + double q = static_cast(_rnd >> (PrngModPower - 26)) + 1.0; + // Put the computed p-value through the CDF of a geometric. + // For faster performance (save ~1/20th exec time), replace + // min(0.0, FastLog2(q) - 26) by (Fastlog2(q) - 26.000705) + // The value 26.000705 is used rather than 26 to compensate + // for inaccuracies in FastLog2 which otherwise result in a + // negative answer. + double log_val = (fast_log2(q) - 26); + double result = + (0.0 < log_val ? 0.0 : log_val) * (-log(2.0) * (get_sampling_rate())) + 1; + assert(result > 0 && result < SIZE_MAX, "Result is not in an acceptable range."); + size_t rate = static_cast(result); + _bytes_until_sample = rate; +} + +void ThreadHeapSampler::pick_next_sample(size_t overflowed_bytes) { + if (get_sampling_rate() == 1) { + _bytes_until_sample = 1; + return; + } + + pick_next_geometric_sample(); + + // Try to correct sample size by removing extra space from last allocation. + if (overflowed_bytes > 0 && _bytes_until_sample > overflowed_bytes) { + _bytes_until_sample -= overflowed_bytes; + } +} + +void ThreadHeapSampler::check_for_sampling(HeapWord* ptr, size_t allocation_size, size_t bytes_since_allocation) { + oopDesc* oop = reinterpret_cast(ptr); + size_t total_allocated_bytes = bytes_since_allocation + allocation_size; + + // If not yet time for a sample, skip it. + if (total_allocated_bytes < _bytes_until_sample) { + _bytes_until_sample -= total_allocated_bytes; + return; + } + + JvmtiExport::sampled_object_alloc_event_collector(oop); + + size_t overflow_bytes = total_allocated_bytes - _bytes_until_sample; + pick_next_sample(overflow_bytes); +} + +void ThreadHeapSampler::init_log_table() { + MutexLockerEx mu(ThreadHeapSampler_lock, Mutex::_no_safepoint_check_flag); + + if (log_table_initialized) { + return; + } + + for (int i = 0; i < (1 << FastLogNumBits); i++) { + log_table[i] = (log(1.0 + static_cast(i+0.5) / (1 << FastLogNumBits)) + / log(2.0)); + } + + log_table_initialized = true; +} + +void ThreadHeapSampler::enable() { + // Done here to be done when things have settled. This adds a mutex lock but + // presumably, users won't be enabling and disabling all the time. + init_log_table(); + OrderAccess::release_store(&_enabled, 1); +} + +int ThreadHeapSampler::enabled() { + return OrderAccess::load_acquire(&_enabled); +} + +void ThreadHeapSampler::disable() { + OrderAccess::release_store(&_enabled, 0); +} + +int ThreadHeapSampler::get_sampling_rate() { + return OrderAccess::load_acquire(&_sampling_rate); +} + +void ThreadHeapSampler::set_sampling_rate(int sampling_rate) { + OrderAccess::release_store(&_sampling_rate, sampling_rate); +} + +// Methods used in assertion mode to check if a collector is present or not at +// the moment of TLAB sampling, ie a slow allocation path. +bool ThreadHeapSampler::sampling_collector_present() const { + return _collectors_present > 0; +} + +bool ThreadHeapSampler::remove_sampling_collector() { + assert(_collectors_present > 0, "Problem with collector counter."); + _collectors_present--; + return true; +} + +bool ThreadHeapSampler::add_sampling_collector() { + _collectors_present++; + return true; +} diff --git a/src/hotspot/share/runtime/threadHeapSampler.hpp b/src/hotspot/share/runtime/threadHeapSampler.hpp new file mode 100644 --- /dev/null +++ b/src/hotspot/share/runtime/threadHeapSampler.hpp @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2018, Google and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#ifndef RUNTIME_THREADHEAPSAMPLER_HPP +#define RUNTIME_THREADHEAPSAMPLER_HPP + +#include "memory/allocation.hpp" + +class ThreadHeapSampler { + private: + size_t _bytes_until_sample; + // Cheap random number generator + static uint64_t _rnd; + + void pick_next_geometric_sample(); + void pick_next_sample(size_t overflowed_bytes = 0); + static int _enabled; + static int _sampling_rate; + + // Used for assertion mode to determine if there is a path to a TLAB slow path + // without a collector present. + size_t _collectors_present; + + static void init_log_table(); + + public: + ThreadHeapSampler() : _bytes_until_sample(0) { + _rnd = static_cast(reinterpret_cast(this)); + if (_rnd == 0) { + _rnd = 1; + } + + _collectors_present = 0; + } + + size_t bytes_until_sample() { return _bytes_until_sample; } + void set_bytes_until_sample(size_t bytes) { _bytes_until_sample = bytes; } + + void check_for_sampling(HeapWord* obj, size_t size_in_bytes, size_t bytes_allocated_before = 0); + + static int enabled(); + static void enable(); + static void disable(); + + static void set_sampling_rate(int sampling_rate); + static int get_sampling_rate(); + + bool sampling_collector_present() const; + bool remove_sampling_collector(); + bool add_sampling_collector(); +}; + +#endif // SHARE_RUNTIME_THREADHEAPSAMPLER_HPP diff --git a/test/hotspot/jtreg/serviceability/jvmti/HeapMonitor/MyPackage/Frame.java b/test/hotspot/jtreg/serviceability/jvmti/HeapMonitor/MyPackage/Frame.java new file mode 100644 --- /dev/null +++ b/test/hotspot/jtreg/serviceability/jvmti/HeapMonitor/MyPackage/Frame.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2018, Google and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package MyPackage; + +class Frame { + Frame(String method, String signature, String fileName, int lineNumber) { + this.method = method; + this.signature = signature; + this.fileName = fileName; + this.lineNumber = lineNumber; + } + + public String method; + public String signature; + public String fileName; + public int lineNumber; +} diff --git a/test/hotspot/jtreg/serviceability/jvmti/HeapMonitor/MyPackage/HeapMonitor.java b/test/hotspot/jtreg/serviceability/jvmti/HeapMonitor/MyPackage/HeapMonitor.java new file mode 100644 --- /dev/null +++ b/test/hotspot/jtreg/serviceability/jvmti/HeapMonitor/MyPackage/HeapMonitor.java @@ -0,0 +1,172 @@ +/* + * Copyright (c) 2018, Google and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package MyPackage; + +import java.util.ArrayList; +import java.util.List; + +/** API for handling the underlying heap sampling monitoring system. */ +public class HeapMonitor { + private static int[][] arrays; + private static int allocationIterations = 1000; + + static { + try { + System.loadLibrary("HeapMonitorTest"); + } catch (UnsatisfiedLinkError ule) { + System.err.println("Could not load HeapMonitor library"); + System.err.println("java.library.path: " + System.getProperty("java.library.path")); + throw ule; + } + } + + /** Set a specific sampling rate, 0 samples every allocation. */ + public native static void setSamplingRate(int rate); + public native static void enableSamplingEvents(); + public native static boolean enableSamplingEventsForTwoThreads(Thread firstThread, Thread secondThread); + public native static void disableSamplingEvents(); + + /** + * Allocate memory but first create a stack trace. + * + * @return list of frames for the allocation. + */ + public static List allocate() { + int sum = 0; + List frames = new ArrayList(); + allocate(frames); + frames.add(new Frame("allocate", "()Ljava/util/List;", "HeapMonitor.java", 58)); + return frames; + } + + private static void allocate(List frames) { + int sum = 0; + for (int j = 0; j < allocationIterations; j++) { + sum += actuallyAllocate(); + } + frames.add(new Frame("actuallyAllocate", "()I", "HeapMonitor.java", 93)); + frames.add(new Frame("allocate", "(Ljava/util/List;)V", "HeapMonitor.java", 66)); + } + + public static List repeatAllocate(int max) { + List frames = null; + for (int i = 0; i < max; i++) { + frames = allocate(); + } + frames.add(new Frame("repeatAllocate", "(I)Ljava/util/List;", "HeapMonitor.java", 75)); + return frames; + } + + private static int actuallyAllocate() { + int sum = 0; + + // Let us assume that a 1-element array is 24 bytes of memory and we want + // 2MB allocated. + int iterations = (1 << 19) / 6; + + if (arrays == null) { + arrays = new int[iterations][]; + } + + for (int i = 0; i < iterations; i++) { + int tmp[] = new int[1]; + // Force it to be kept and, at the same time, wipe out any previous data. + arrays[i] = tmp; + sum += arrays[0][0]; + } + return sum; + } + + public static int allocateSize(int totalSize) { + int sum = 0; + + // Let us assume that a 1-element array is 24 bytes. + int iterations = totalSize / 24; + + if (arrays == null) { + arrays = new int[iterations][]; + } + + System.out.println("Allocating for " + iterations); + for (int i = 0; i < iterations; i++) { + int tmp[] = new int[1]; + + // Force it to be kept and, at the same time, wipe out any previous data. + arrays[i] = tmp; + sum += arrays[0][0]; + } + + return sum; + } + + /** Remove the reference to the global array to free data at the next GC. */ + public static void freeStorage() { + arrays = null; + } + + public static int[][][] sampleEverything() { + enableSamplingEvents(); + setSamplingRate(0); + + // Loop around an allocation loop and wait until the tlabs have settled. + final int maxTries = 10; + int[][][] result = new int[maxTries][][]; + for (int i = 0; i < maxTries; i++) { + final int maxInternalTries = 400; + result[i] = new int[maxInternalTries][]; + + resetEventStorage(); + for (int j = 0; j < maxInternalTries; j++) { + final int size = 1000; + result[i][j] = new int[size]; + } + + int sampledEvents = sampledEvents(); + if (sampledEvents == maxInternalTries) { + return result; + } + } + + throw new RuntimeException("Could not set the sampler"); + } + + public native static int sampledEvents(); + public native static boolean obtainedEvents(Frame[] frames); + public native static boolean garbageContains(Frame[] frames); + public native static boolean eventStorageIsEmpty(); + public native static void resetEventStorage(); + public native static int getEventStorageElementCount(); + public native static void forceGarbageCollection(); + public native static boolean enableVMEvents(); + + public static boolean statsHaveExpectedNumberSamples(int expected, int acceptedErrorPercentage) { + double actual = getEventStorageElementCount(); + double diffPercentage = Math.abs(actual - expected) / expected; + return diffPercentage < acceptedErrorPercentage; + } + + public static void setAllocationIterations(int iterations) { + allocationIterations = iterations; + } +} diff --git a/test/hotspot/jtreg/serviceability/jvmti/HeapMonitor/MyPackage/HeapMonitorArrayAllSampledTest.java b/test/hotspot/jtreg/serviceability/jvmti/HeapMonitor/MyPackage/HeapMonitorArrayAllSampledTest.java new file mode 100644 --- /dev/null +++ b/test/hotspot/jtreg/serviceability/jvmti/HeapMonitor/MyPackage/HeapMonitorArrayAllSampledTest.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2018, Google and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package MyPackage; + +/** + * @test + * @build Frame HeapMonitor + * @summary Verifies the JVMTI Heap Monitor rate when allocating arrays. + * @compile HeapMonitorArrayAllSampledTest.java + * @run main/othervm/native -agentlib:HeapMonitorTest MyPackage.HeapMonitorArrayAllSampledTest + */ + +public class HeapMonitorArrayAllSampledTest { + + // Do 1000 iterations and expect maxIteration samples. + private static final int maxIteration = 1000; + private static int array[]; + + private static void allocate(int size) { + for (int j = 0; j < maxIteration; j++) { + array = new int[size]; + } + } + + public static void main(String[] args) { + int sizes[] = {1000, 10000, 100000, 1000000}; + + HeapMonitor.setSamplingRate(0); + HeapMonitor.enableSamplingEvents(); + + for (int currentSize : sizes) { + System.out.println("Testing size " + currentSize); + + HeapMonitor.resetEventStorage(); + allocate(currentSize); + + // 10% error ensures a sanity test without becoming flaky. + // Flakiness is due to the fact that this test is dependent on the sampling rate, which is a + // statistical geometric variable around the sampling rate. This means that the test could be + // unlucky and not achieve the mean average fast enough for the test case. + if (!HeapMonitor.statsHaveExpectedNumberSamples(maxIteration, 10)) { + throw new RuntimeException("Statistics should show about " + maxIteration + " samples."); + } + } + } +} diff --git a/test/hotspot/jtreg/serviceability/jvmti/HeapMonitor/MyPackage/HeapMonitorEventOnOffTest.java b/test/hotspot/jtreg/serviceability/jvmti/HeapMonitor/MyPackage/HeapMonitorEventOnOffTest.java new file mode 100644 --- /dev/null +++ b/test/hotspot/jtreg/serviceability/jvmti/HeapMonitor/MyPackage/HeapMonitorEventOnOffTest.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2018, Google and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package MyPackage; + +import java.util.List; + +/** + * @test + * @summary Verifies if turning off the event notification stops events. + * @build Frame HeapMonitor + * @compile HeapMonitorEventOnOffTest.java + * @run main/othervm/native -agentlib:HeapMonitorTest MyPackage.HeapMonitorEventOnOffTest + */ +public class HeapMonitorEventOnOffTest { + private static void checkNoEventsAreBeingSent() { + HeapMonitor.resetEventStorage(); + HeapMonitor.repeatAllocate(10); + + // Check that the data is not available while heap sampling is disabled. + boolean status = HeapMonitor.eventStorageIsEmpty(); + if (!status) { + throw new RuntimeException("Storage is not empty after allocating with disabled events."); + } + } + + private static void checkEventsAreBeingSent() { + List frameList = HeapMonitor.repeatAllocate(10); + + frameList.add(new Frame("checkEventsAreBeingSent", "()V", "HeapMonitorEventOnOffTest.java", 48)); + Frame[] frames = frameList.toArray(new Frame[0]); + + // Check that the data is available while heap sampling is enabled. + boolean status = HeapMonitor.obtainedEvents(frames); + if (!status) { + throw new RuntimeException("Failed to find the traces after allocating with enabled events."); + } + } + + public static void main(String[] args) { + HeapMonitor.enableSamplingEvents(); + checkEventsAreBeingSent(); + + // Disabling the notification system should stop events. + HeapMonitor.disableSamplingEvents(); + checkNoEventsAreBeingSent(); + + // Enabling the notification system should start events again. + HeapMonitor.enableSamplingEvents(); + checkEventsAreBeingSent(); + } +} diff --git a/test/hotspot/jtreg/serviceability/jvmti/HeapMonitor/MyPackage/HeapMonitorEventsForTwoThreadsTest.java b/test/hotspot/jtreg/serviceability/jvmti/HeapMonitor/MyPackage/HeapMonitorEventsForTwoThreadsTest.java new file mode 100644 --- /dev/null +++ b/test/hotspot/jtreg/serviceability/jvmti/HeapMonitor/MyPackage/HeapMonitorEventsForTwoThreadsTest.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2018, Google and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package MyPackage; + +/** + * @test + * @build Frame HeapMonitor ThreadInformation + * @summary Ensures the JVMTI Heap Monitor is not thread enable (test to change when it becomes so) + * @compile HeapMonitorEventsForTwoThreadsTest.java + * @run main/othervm/native -agentlib:HeapMonitorTest MyPackage.HeapMonitorEventsForTwoThreadsTest + */ + +import java.util.List; + +public class HeapMonitorEventsForTwoThreadsTest { + public native static boolean checkSamples(); + + public static void main(String[] args) { + final int numThreads = 24; + List threadList = ThreadInformation.createThreadList(numThreads); + + Thread firstThread = threadList.get(0).getThread(); + Thread secondThread = threadList.get(1).getThread(); + if (HeapMonitor.enableSamplingEventsForTwoThreads(firstThread, secondThread)) { + throw new RuntimeException("Sampling event is thread enabled, that is unexpected."); + } + } +} diff --git a/test/hotspot/jtreg/serviceability/jvmti/HeapMonitor/MyPackage/HeapMonitorGCCMSTest.java b/test/hotspot/jtreg/serviceability/jvmti/HeapMonitor/MyPackage/HeapMonitorGCCMSTest.java new file mode 100644 --- /dev/null +++ b/test/hotspot/jtreg/serviceability/jvmti/HeapMonitor/MyPackage/HeapMonitorGCCMSTest.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2018, Google and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package MyPackage; + +/** + * @test + * @summary Verifies the JVMTI Heap Monitor Statistics using CMS GC + * @build Frame HeapMonitor + * @requires vm.gc == "ConcMarkSweep" | vm.gc == "null" + * @compile HeapMonitorGCCMSTest.java + * @run main/othervm/native -agentlib:HeapMonitorTest -XX:+UseConcMarkSweepGC MyPackage.HeapMonitorGCTest + */ diff --git a/test/hotspot/jtreg/serviceability/jvmti/HeapMonitor/MyPackage/HeapMonitorGCParallelTest.java b/test/hotspot/jtreg/serviceability/jvmti/HeapMonitor/MyPackage/HeapMonitorGCParallelTest.java new file mode 100644 --- /dev/null +++ b/test/hotspot/jtreg/serviceability/jvmti/HeapMonitor/MyPackage/HeapMonitorGCParallelTest.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2018, Google and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package MyPackage; + +/** + * @test + * @summary Verifies the JVMTI Heap Monitor Statistics using ParallelGc + * @build Frame HeapMonitor + * @compile HeapMonitorGCTest.java + * @requires vm.gc == "Parallel" | vm.gc == "null" + * @run main/othervm/native -agentlib:HeapMonitorTest -XX:+UseParallelGC MyPackage.HeapMonitorGCTest + */ diff --git a/test/hotspot/jtreg/serviceability/jvmti/HeapMonitor/MyPackage/HeapMonitorGCSerialTest.java b/test/hotspot/jtreg/serviceability/jvmti/HeapMonitor/MyPackage/HeapMonitorGCSerialTest.java new file mode 100644 --- /dev/null +++ b/test/hotspot/jtreg/serviceability/jvmti/HeapMonitor/MyPackage/HeapMonitorGCSerialTest.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2018, Google and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package MyPackage; + +/** + * @test + * @summary Verifies the JVMTI Heap Monitor Statistics using SerialGC + * @build Frame HeapMonitor + * @compile HeapMonitorGCTest.java + * @requires vm.gc == "Serial" | vm.gc == "null" + * @run main/othervm/native -agentlib:HeapMonitorTest -XX:+UseSerialGC MyPackage.HeapMonitorGCTest + */ diff --git a/test/hotspot/jtreg/serviceability/jvmti/HeapMonitor/MyPackage/HeapMonitorGCTest.java b/test/hotspot/jtreg/serviceability/jvmti/HeapMonitor/MyPackage/HeapMonitorGCTest.java new file mode 100644 --- /dev/null +++ b/test/hotspot/jtreg/serviceability/jvmti/HeapMonitor/MyPackage/HeapMonitorGCTest.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2018, Google and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package MyPackage; + +import java.util.List; + +/** + * @test + * @build Frame HeapMonitor + * @summary Verifies the default GC with the Heap Monitor event system. + * @compile HeapMonitorGCTest.java + * @requires vm.gc == "G1" | vm.gc == "null" + * @run main/othervm/native -agentlib:HeapMonitorTest MyPackage.HeapMonitorGCTest + */ + +/** + * This test is checking that various GCs work as intended: events are sent, forcing GC works, etc. + */ +public class HeapMonitorGCTest { + public static void main(String[] args) { + if (!HeapMonitor.eventStorageIsEmpty()) { + throw new RuntimeException("Statistics should be null to begin with."); + } + + HeapMonitor.enableSamplingEvents(); + + List frameList = HeapMonitor.allocate(); + frameList.add(new Frame("main", "([Ljava/lang/String;)V", "HeapMonitorGCTest.java", 48)); + Frame[] frames = frameList.toArray(new Frame[0]); + + if (!HeapMonitor.obtainedEvents(frames)) { + throw new RuntimeException("No expected events were found."); + } + + HeapMonitor.forceGarbageCollection(); + + if (!HeapMonitor.garbageContains(frames)) { + throw new RuntimeException("Forcing GC did not work, not a single object was collected."); + } + } +} diff --git a/test/hotspot/jtreg/serviceability/jvmti/HeapMonitor/MyPackage/HeapMonitorIllegalArgumentTest.java b/test/hotspot/jtreg/serviceability/jvmti/HeapMonitor/MyPackage/HeapMonitorIllegalArgumentTest.java new file mode 100644 --- /dev/null +++ b/test/hotspot/jtreg/serviceability/jvmti/HeapMonitor/MyPackage/HeapMonitorIllegalArgumentTest.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2018, Google and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package MyPackage; + +/** + * @test + * @summary Verifies the JVMTI SetHeapSamplingRate returns an illegal argument for negative ints. + * @build Frame HeapMonitor + * @compile HeapMonitorIllegalArgumentTest.java + * @run main/othervm/native -agentlib:HeapMonitorTest MyPackage.HeapMonitorIllegalArgumentTest + */ + +public class HeapMonitorIllegalArgumentTest { + private native static int testIllegalArgument(); + + public static void main(String[] args) { + int result = testIllegalArgument(); + + if (result == 0) { + throw new RuntimeException("Test illegal argument failed."); + } + } +} diff --git a/test/hotspot/jtreg/serviceability/jvmti/HeapMonitor/MyPackage/HeapMonitorInterpreterArrayTest.java b/test/hotspot/jtreg/serviceability/jvmti/HeapMonitor/MyPackage/HeapMonitorInterpreterArrayTest.java new file mode 100644 --- /dev/null +++ b/test/hotspot/jtreg/serviceability/jvmti/HeapMonitor/MyPackage/HeapMonitorInterpreterArrayTest.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2018, Google and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package MyPackage; + +/** + * @test + * @summary Verifies the JVMTI Heap Monitor using the interpreter. + * @build Frame HeapMonitor + * @compile HeapMonitorTest.java + * @run main/othervm/native -agentlib:HeapMonitorTest -Xint MyPackage.HeapMonitorTest 10 + */ diff --git a/test/hotspot/jtreg/serviceability/jvmti/HeapMonitor/MyPackage/HeapMonitorInterpreterObjectTest.java b/test/hotspot/jtreg/serviceability/jvmti/HeapMonitor/MyPackage/HeapMonitorInterpreterObjectTest.java new file mode 100644 --- /dev/null +++ b/test/hotspot/jtreg/serviceability/jvmti/HeapMonitor/MyPackage/HeapMonitorInterpreterObjectTest.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2018, Google and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package MyPackage; + +/** + * @test + * @summary Verifies the JVMTI Heap Monitor using the interpreter. + * @build Frame HeapMonitor + * @compile HeapMonitorStatObjectCorrectnessTest.java + * @run main/othervm/native -agentlib:HeapMonitorTest -Xint MyPackage.HeapMonitorStatObjectCorrectnessTest + */ diff --git a/test/hotspot/jtreg/serviceability/jvmti/HeapMonitor/MyPackage/HeapMonitorMultiArrayTest.java b/test/hotspot/jtreg/serviceability/jvmti/HeapMonitor/MyPackage/HeapMonitorMultiArrayTest.java new file mode 100644 --- /dev/null +++ b/test/hotspot/jtreg/serviceability/jvmti/HeapMonitor/MyPackage/HeapMonitorMultiArrayTest.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2018, Google and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package MyPackage; + +import java.util.List; + +/** + * @test + * @summary Verifies the JVMTI Heap Monitor API when allocating a multi-array. + * @build Frame HeapMonitor + * @compile HeapMonitorMultiArrayTest.java + * @run main/othervm/native -agentlib:HeapMonitorTest MyPackage.HeapMonitorMultiArrayTest + */ +public class HeapMonitorMultiArrayTest { + + private static int[][] tab; + public static void main(String[] args) throws Exception { + // Set ourselves to sample everything. + HeapMonitor.sampleEverything(); + + // Do a few allocations now and see if the callback happens and the program finishes: + HeapMonitor.resetEventStorage(); + int iterations = 1000; + int allocationsPerIteration = 6; + for (int i = 0; i < iterations; i++) { + tab = new int[5][5]; + } + + int sampledEvents = HeapMonitor.sampledEvents(); + int expectedNumber = iterations * allocationsPerIteration; + + if (sampledEvents != expectedNumber) { + throw new RuntimeException("Number of samples (" + sampledEvents + ") not the expected (" + + expectedNumber + ")"); + } + } +} diff --git a/test/hotspot/jtreg/serviceability/jvmti/HeapMonitor/MyPackage/HeapMonitorNoCapabilityTest.java b/test/hotspot/jtreg/serviceability/jvmti/HeapMonitor/MyPackage/HeapMonitorNoCapabilityTest.java new file mode 100644 --- /dev/null +++ b/test/hotspot/jtreg/serviceability/jvmti/HeapMonitor/MyPackage/HeapMonitorNoCapabilityTest.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2018, Google and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package MyPackage; + +/** + * @test + * @summary Verifies the JVMTI Heap Monitor does not work without the required capability. + * @build Frame HeapMonitor + * @compile HeapMonitorNoCapabilityTest.java + * @run main/othervm/native -agentlib:HeapMonitorTest MyPackage.HeapMonitorNoCapabilityTest + */ + +public class HeapMonitorNoCapabilityTest { + private native static int allSamplingMethodsFail(); + + public static void main(String[] args) { + int result = allSamplingMethodsFail(); + + if (result == 0) { + throw new RuntimeException("Some methods could be called without a capability."); + } + } +} diff --git a/test/hotspot/jtreg/serviceability/jvmti/HeapMonitor/MyPackage/HeapMonitorRecursiveTest.java b/test/hotspot/jtreg/serviceability/jvmti/HeapMonitor/MyPackage/HeapMonitorRecursiveTest.java new file mode 100644 --- /dev/null +++ b/test/hotspot/jtreg/serviceability/jvmti/HeapMonitor/MyPackage/HeapMonitorRecursiveTest.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2018, Google and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package MyPackage; + +import java.util.List; + +/** + * @test + * @summary Verifies the JVMTI Heap Monitor API does not do infinite recursion. + * @build Frame HeapMonitor + * @compile HeapMonitorRecursiveTest.java + * @run main/othervm/native -agentlib:HeapMonitorTest MyPackage.HeapMonitorRecursiveTest + */ +public class HeapMonitorRecursiveTest { + private native static void setCallbackToCallAllocateSomeMore(); + private native static boolean didCallback(); + private static int[] tab; + + public static void main(String[] args) throws Exception { + // Set ourselves to sample everything. + HeapMonitor.sampleEverything(); + + // Set a callback that does allocate more data. If a callback's allocations get sampled, this + // would do an infinite recursion. + setCallbackToCallAllocateSomeMore(); + + // Do a few allocations now and see if the callback happens and the program finishes: + for (int i = 0; i < 1000; i++) { + tab = new int[1024]; + } + + if (!didCallback()) { + throw new RuntimeException("Did not get a callback..."); + } + } +} diff --git a/test/hotspot/jtreg/serviceability/jvmti/HeapMonitor/MyPackage/HeapMonitorStatArrayCorrectnessTest.java b/test/hotspot/jtreg/serviceability/jvmti/HeapMonitor/MyPackage/HeapMonitorStatArrayCorrectnessTest.java new file mode 100644 --- /dev/null +++ b/test/hotspot/jtreg/serviceability/jvmti/HeapMonitor/MyPackage/HeapMonitorStatArrayCorrectnessTest.java @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2018, Google and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package MyPackage; + +/** + * @test + * @build Frame HeapMonitor + * @summary Verifies the JVMTI Heap Monitor rate when allocating arrays. + * @compile HeapMonitorStatArrayCorrectnessTest.java + * @run main/othervm/native -agentlib:HeapMonitorTest MyPackage.HeapMonitorStatArrayCorrectnessTest + */ + +public class HeapMonitorStatArrayCorrectnessTest { + + // Do 100000 iterations and expect maxIteration / multiplier samples. + private static final int maxIteration = 100000; + private static int array[]; + + private static void allocate(int size) { + for (int j = 0; j < maxIteration; j++) { + array = new int[size]; + } + } + + public static void main(String[] args) { + int sizes[] = {1000, 10000, 100000}; + + HeapMonitor.enableSamplingEvents(); + + for (int currentSize : sizes) { + System.out.println("Testing size " + currentSize); + + HeapMonitor.resetEventStorage(); + if (!HeapMonitor.eventStorageIsEmpty()) { + throw new RuntimeException("Should not have any events stored yet."); + } + + // 111 is as good a number as any. + final int samplingMultiplier = 111; + HeapMonitor.setSamplingRate(samplingMultiplier * currentSize); + + allocate(currentSize); + + // For simplifications, we ignore the array memory usage for array internals (with the array + // sizes requested, it should be a negligible oversight). + // + // That means that with maxIterations, the loop in the method allocate requests: + // maxIterations * currentSize * 4 bytes (4 for integers) + // + // Via the enable sampling, the code requests a sample every samplingMultiplier * currentSize bytes. + // + // Therefore, the expected sample number is: + // (maxIterations * currentSize * 4) / (samplingMultiplier * currentSize); + double expected = maxIteration; + expected *= 4; + expected /= samplingMultiplier; + + // 10% error ensures a sanity test without becoming flaky. + // Flakiness is due to the fact that this test is dependent on the sampling rate, which is a + // statistical geometric variable around the sampling rate. This means that the test could be + // unlucky and not achieve the mean average fast enough for the test case. + if (!HeapMonitor.statsHaveExpectedNumberSamples((int) expected, 10)) { + throw new RuntimeException("Statistics should show about " + expected + " samples."); + } + } + } +} diff --git a/test/hotspot/jtreg/serviceability/jvmti/HeapMonitor/MyPackage/HeapMonitorStatObjectCorrectnessTest.java b/test/hotspot/jtreg/serviceability/jvmti/HeapMonitor/MyPackage/HeapMonitorStatObjectCorrectnessTest.java new file mode 100644 --- /dev/null +++ b/test/hotspot/jtreg/serviceability/jvmti/HeapMonitor/MyPackage/HeapMonitorStatObjectCorrectnessTest.java @@ -0,0 +1,145 @@ +/* + * Copyright (c) 2018, Google and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package MyPackage; + +/** + * @test + * @build Frame HeapMonitor + * @summary Verifies the JVMTI Heap Monitor sampling via object allocation. + * @compile HeapMonitorStatObjectCorrectnessTest.java + * @run main/othervm/native -agentlib:HeapMonitorTest MyPackage.HeapMonitorStatObjectCorrectnessTest + */ + +/** This test is checking the object allocation path works with heap sampling. */ +public class HeapMonitorStatObjectCorrectnessTest { + + // Do 200000 iterations and expect maxIteration / multiplier samples. + private static final int maxIteration = 200000; + private static BigObject obj; + + private native static boolean statsHaveExpectedNumberSamples(int expected, int percentError); + + private static void allocate() { + for (int j = 0; j < maxIteration; j++) { + obj = new BigObject(); + } + } + + private static void testBigAllocationRate() { + final int sizeObject = 1400; + + // 111 is as good a number as any. + final int samplingMultiplier = 111; + HeapMonitor.setSamplingRate(samplingMultiplier * sizeObject); + + emptyStorage(); + allocate(); + + // For simplifications, the code is allocating: + // (BigObject size) * maxIteration. + // + // We ignore the class memory usage apart from field memory usage for BigObject. BigObject + // allocates 250 long, so 2000 bytes, so whatever is used for the class is negligible. + // + // That means that with maxIterations, the loop in the method allocate requests: + // maxIterations * 2000 bytes. + // + // Via the enable sampling, the code requests a sample every samplingMultiplier * sizeObject bytes. + // + // Therefore, the expected sample number is: + // (maxIterations * sizeObject) / (samplingMultiplier * sizeObject); + // + // Which becomes: + // maxIterations / samplingMultiplier + double expected = maxIteration; + expected /= samplingMultiplier; + + // 10% error ensures a sanity test without becoming flaky. + // Flakiness is due to the fact that this test is dependent on the sampling rate, which is a + // statistical geometric variable around the sampling rate. This means that the test could be + // unlucky and not achieve the mean average fast enough for the test case. + if (!HeapMonitor.statsHaveExpectedNumberSamples((int) expected, 10)) { + throw new RuntimeException("Statistics should show about " + expected + " samples."); + } + } + + private static void emptyStorage() { + HeapMonitor.resetEventStorage(); + + if (!HeapMonitor.eventStorageIsEmpty()) { + throw new RuntimeException("Statistics should be null to begin with."); + } + } + + private static void testEveryAllocationSampled() { + // 0 means sample every allocation. + HeapMonitor.setSamplingRate(0); + + emptyStorage(); + allocate(); + + double expected = maxIteration; + + // 10% error ensures a sanity test without becoming flaky. + // Flakiness is due to the fact that this test is dependent on the sampling rate, which is a + // statistical geometric variable around the sampling rate. This means that the test could be + // unlucky and not achieve the mean average fast enough for the test case. + if (!HeapMonitor.statsHaveExpectedNumberSamples((int) expected, 10)) { + throw new RuntimeException("Statistics should show about " + expected + " samples."); + } + } + + public static void main(String[] args) { + HeapMonitor.enableSamplingEvents(); + + testBigAllocationRate(); + testEveryAllocationSampled(); + } + + /** + * Big class on purpose to just be able to ignore the class memory space overhead. + * + * Class contains 175 long fields, so 175 * 8 = 1400 bytes. + */ + private static class BigObject { + private long a0_0, a0_1, a0_2, a0_3, a0_4, a0_5, a0_6, a0_7, a0_8, a0_9; + private long a1_0, a1_1, a1_2, a1_3, a1_4, a1_5, a1_6, a1_7, a1_8, a1_9; + private long a2_0, a2_1, a2_2, a2_3, a2_4, a2_5, a2_6, a2_7, a2_8, a2_9; + private long a3_0, a3_1, a3_2, a3_3, a3_4, a3_5, a3_6, a3_7, a3_8, a3_9; + private long a4_0, a4_1, a4_2, a4_3, a4_4, a4_5, a4_6, a4_7, a4_8, a4_9; + private long a5_0, a5_1, a5_2, a5_3, a5_4, a5_5, a5_6, a5_7, a5_8, a5_9; + private long a6_0, a6_1, a6_2, a6_3, a6_4, a6_5, a6_6, a6_7, a6_8, a6_9; + private long a7_0, a7_1, a7_2, a7_3, a7_4, a7_5, a7_6, a7_7, a7_8, a7_9; + private long a8_0, a8_1, a8_2, a8_3, a8_4, a8_5, a8_6, a8_7, a8_8, a8_9; + private long a9_0, a9_1, a9_2, a9_3, a9_4, a9_5, a9_6, a9_7, a9_8, a9_9; + private long a10_0, a10_1, a10_2, a10_3, a10_4, a10_5, a10_6, a10_7, a10_8, a10_9; + private long a11_0, a11_1, a11_2, a11_3, a11_4, a11_5, a11_6, a11_7, a11_8, a11_9; + private long a12_0, a12_1, a12_2, a12_3, a12_4, a12_5, a12_6, a12_7, a12_8, a12_9; + private long a13_0, a13_1, a13_2, a13_3, a13_4, a13_5, a13_6, a13_7, a13_8, a13_9; + private long a14_0, a14_1, a14_2, a14_3, a14_4, a14_5, a14_6, a14_7, a14_8, a14_9; + private long a15_0, a15_1, a15_2, a15_3, a15_4, a15_5, a15_6, a15_7, a15_8, a15_9; + private long a16_0, a16_1, a16_2, a16_3, a16_4, a16_5, a16_6, a16_7, a16_8, a16_9; + private long a17_0, a17_1, a17_2, a17_3, a17_4; + } +} diff --git a/test/hotspot/jtreg/serviceability/jvmti/HeapMonitor/MyPackage/HeapMonitorStatRateTest.java b/test/hotspot/jtreg/serviceability/jvmti/HeapMonitor/MyPackage/HeapMonitorStatRateTest.java new file mode 100644 --- /dev/null +++ b/test/hotspot/jtreg/serviceability/jvmti/HeapMonitor/MyPackage/HeapMonitorStatRateTest.java @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2018, Google and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package MyPackage; + +/** + * @test + * @summary Verifies the JVMTI Heap Monitor sampling rate average. + * @build Frame HeapMonitor + * @compile HeapMonitorStatRateTest.java + * @requires vm.compMode != "Xcomp" + * @run main/othervm/native -agentlib:HeapMonitorTest MyPackage.HeapMonitorStatRateTest + */ + +public class HeapMonitorStatRateTest { + + private native static double getAverageRate(); + + private static boolean testRateOnce(int rate, boolean throwIfFailure) { + HeapMonitor.resetEventStorage(); + HeapMonitor.setSamplingRate(rate); + + HeapMonitor.enableSamplingEvents(); + + int allocationTotal = 10 * 1024 * 1024; + HeapMonitor.allocateSize(allocationTotal); + + HeapMonitor.disableSamplingEvents(); + + double actualCount = HeapMonitor.getEventStorageElementCount(); + double expectedCount = allocationTotal / rate; + + double error = Math.abs(actualCount - expectedCount); + double errorPercentage = error / expectedCount * 100; + + boolean failure = (errorPercentage > 10.0); + + if (failure && throwIfFailure) { + throw new RuntimeException("Rate average over 10% for rate " + rate + " -> " + actualCount + + ", " + expectedCount); + } + + return failure; + } + + + private static void testRate(int rate) { + // Test the rate twice, it can happen that the test is "unlucky" and the rate just goes above + // the 10% mark. So try again to squash flakiness. + // Flakiness is due to the fact that this test is dependent on the sampling rate, which is a + // statistical geometric variable around the sampling rate. This means that the test could be + // unlucky and not achieve the mean average fast enough for the test case. + if (!testRateOnce(rate, false)) { + testRateOnce(rate, true); + } + } + + public static void main(String[] args) { + int[] tab = {1024, 8192}; + + for (int rateIdx = 0; rateIdx < tab.length; rateIdx++) { + testRate(tab[rateIdx]); + } + } +} diff --git a/test/hotspot/jtreg/serviceability/jvmti/HeapMonitor/MyPackage/HeapMonitorStatSimpleTest.java b/test/hotspot/jtreg/serviceability/jvmti/HeapMonitor/MyPackage/HeapMonitorStatSimpleTest.java new file mode 100644 --- /dev/null +++ b/test/hotspot/jtreg/serviceability/jvmti/HeapMonitor/MyPackage/HeapMonitorStatSimpleTest.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2018, Google and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package MyPackage; + +/** + * @test + * @build Frame HeapMonitor + * @summary Verifies the JVMTI Heap Monitor events are only sent after enabling. + * @compile HeapMonitorStatSimpleTest.java + * @run main/othervm/native -agentlib:HeapMonitorTest MyPackage.HeapMonitorStatSimpleTest + */ + +public class HeapMonitorStatSimpleTest { + private native static int areSamplingStatisticsZero(); + + public static void main(String[] args) { + HeapMonitor.allocate(); + if (!HeapMonitor.eventStorageIsEmpty()) { + throw new RuntimeException("Statistics should be null to begin with."); + } + + HeapMonitor.enableSamplingEvents(); + HeapMonitor.allocate(); + + if (HeapMonitor.eventStorageIsEmpty()) { + throw new RuntimeException("Statistics should not be null now."); + } + } +} diff --git a/test/hotspot/jtreg/serviceability/jvmti/HeapMonitor/MyPackage/HeapMonitorTest.java b/test/hotspot/jtreg/serviceability/jvmti/HeapMonitor/MyPackage/HeapMonitorTest.java new file mode 100644 --- /dev/null +++ b/test/hotspot/jtreg/serviceability/jvmti/HeapMonitor/MyPackage/HeapMonitorTest.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2018, Google and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package MyPackage; + +import java.util.List; + +/** + * @test + * @summary Verifies the JVMTI Heap Monitor API + * @build Frame HeapMonitor + * @compile HeapMonitorTest.java + * @run main/othervm/native -agentlib:HeapMonitorTest MyPackage.HeapMonitorTest + */ + +public class HeapMonitorTest { + + private static native boolean framesAreNotLive(Frame[] frames); + + public static void main(String[] args) { + if (args.length > 0) { + // For interpreter mode, have a means to reduce the default iteration count. + HeapMonitor.setAllocationIterations(Integer.parseInt(args[0])); + } + + if (!HeapMonitor.eventStorageIsEmpty()) { + throw new RuntimeException("Storage is not empty at test start..."); + } + + HeapMonitor.enableSamplingEvents(); + List frameList = HeapMonitor.allocate(); + frameList.add(new Frame("main", "([Ljava/lang/String;)V", "HeapMonitorTest.java", 51)); + + Frame[] frames = frameList.toArray(new Frame[0]); + if (!HeapMonitor.obtainedEvents(frames)) { + throw new RuntimeException("Events not found with the right frames."); + } + + HeapMonitor.disableSamplingEvents(); + HeapMonitor.resetEventStorage(); + if (!HeapMonitor.eventStorageIsEmpty()) { + throw new RuntimeException("Storage is not empty after reset."); + } + + HeapMonitor.allocate(); + if (!HeapMonitor.eventStorageIsEmpty()) { + throw new RuntimeException("Storage is not empty after allocation while disabled."); + } + } +} diff --git a/test/hotspot/jtreg/serviceability/jvmti/HeapMonitor/MyPackage/HeapMonitorThreadOnOffTest.java b/test/hotspot/jtreg/serviceability/jvmti/HeapMonitor/MyPackage/HeapMonitorThreadOnOffTest.java new file mode 100644 --- /dev/null +++ b/test/hotspot/jtreg/serviceability/jvmti/HeapMonitor/MyPackage/HeapMonitorThreadOnOffTest.java @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2018, Google and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package MyPackage; + +/** + * @test + * @build Frame HeapMonitor + * @summary Verifies the JVMTI Heap Monitor Thread sanity. + * @compile HeapMonitorThreadOnOffTest.java + * @run main/othervm/native -agentlib:HeapMonitorTest MyPackage.HeapMonitorThreadOnOffTest + */ + +import java.util.ArrayList; +import java.util.List; + +public class HeapMonitorThreadOnOffTest { + public static void main(String[] args) { + final int numThreads = 24; + ArrayList list = new ArrayList<>(); + + // Add one thread that consistently turns on/off the sampler to ensure correctness with + // potential resets. + Switch switchPlayer = new Switch(); + Thread switchThread = new Thread(switchPlayer, "Switch Player"); + switchThread.start(); + + for (int i = 0 ; i < numThreads; i++) { + Thread thread = new Thread(new Allocator(i), "Allocator" + i); + thread.start(); + list.add(thread); + } + + for (Thread elem : list) { + try { + elem.join(); + } catch(InterruptedException e) { + throw new RuntimeException("Thread got interrupted..."); + } + } + + switchPlayer.stop(); + try { + switchThread.join(); + } catch(InterruptedException e) { + throw new RuntimeException("Thread got interrupted while waiting for the switch player..."); + } + + // We don't check here for correctness of data. If we made it here, the test succeeded: + // Threads can allocate like crazy + // Other threads can turn on/off the system + } +} + +class Allocator implements Runnable { + private int depth; + private volatile int tmp[]; + + public Allocator(int depth) { + this.depth = depth; + } + + private int helper() { + int sum = 0; + // Let us assume that the array is 24 bytes of memory. + for (int i = 0; i < 127000 / 6; i++) { + int newTmp[] = new int[1]; + // Force it to be kept. + tmp = newTmp; + sum += tmp[0]; + } + return sum; + } + + private int recursiveWrapper(int depth) { + if (depth > 0) { + return recursiveWrapper(depth - 1); + } + return helper(); + } + + public void run() { + int sum = 0; + for (int j = 0; j < 100; j++) { + sum += recursiveWrapper(depth); + } + } +} + +class Switch implements Runnable { + private volatile boolean keepGoing; + + public Switch() { + keepGoing = true; + } + + public void stop() { + keepGoing = false; + } + + public void run() { + while (keepGoing) { + HeapMonitor.disableSamplingEvents(); + HeapMonitor.resetEventStorage(); + HeapMonitor.enableSamplingEvents(); + } + } +} diff --git a/test/hotspot/jtreg/serviceability/jvmti/HeapMonitor/MyPackage/HeapMonitorThreadTest.java b/test/hotspot/jtreg/serviceability/jvmti/HeapMonitor/MyPackage/HeapMonitorThreadTest.java new file mode 100644 --- /dev/null +++ b/test/hotspot/jtreg/serviceability/jvmti/HeapMonitor/MyPackage/HeapMonitorThreadTest.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2018, Google and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package MyPackage; + +/** + * @test + * @build Frame HeapMonitor ThreadInformation + * @summary Verifies the JVMTI Heap Monitor Thread information sanity. + * @compile HeapMonitorThreadTest.java + * @run main/othervm/native -agentlib:HeapMonitorTest MyPackage.HeapMonitorThreadTest + */ + +import java.util.List; + +public class HeapMonitorThreadTest { + private native static boolean checkSamples(int numThreads); + + public static void main(String[] args) { + final int numThreads = 24; + List threadList = ThreadInformation.createThreadList(numThreads); + + // Sample at a rate of 8k. + HeapMonitor.setSamplingRate(1 << 13); + HeapMonitor.enableSamplingEvents(); + + System.err.println("Starting threads"); + ThreadInformation.startThreads(threadList); + ThreadInformation.waitForThreads(threadList); + System.err.println("Waited for threads"); + + if (!checkSamples(numThreads)) { + throw new RuntimeException("Problem with checkSamples..."); + } + + // Now inform each thread we are done and wait for them to be done. + ThreadInformation.stopThreads(threadList); + } +} diff --git a/test/hotspot/jtreg/serviceability/jvmti/HeapMonitor/MyPackage/HeapMonitorTwoAgentsTest.java b/test/hotspot/jtreg/serviceability/jvmti/HeapMonitor/MyPackage/HeapMonitorTwoAgentsTest.java new file mode 100644 --- /dev/null +++ b/test/hotspot/jtreg/serviceability/jvmti/HeapMonitor/MyPackage/HeapMonitorTwoAgentsTest.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2018, Google and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package MyPackage; + +/** + * @test + * @build Frame HeapMonitor ThreadInformation + * @summary Verifies the JVMTI Heap Monitor does not work with two agents. + * @compile HeapMonitorTwoAgentsTest.java + * @run main/othervm/native -agentlib:HeapMonitorTest MyPackage.HeapMonitorTwoAgentsTest + */ + +import java.util.List; + +public class HeapMonitorTwoAgentsTest { + private native static boolean enablingSamplingInSecondaryAgent(); + private native static boolean obtainedEventsForBothAgents(Frame[] frames); + + public static void main(String[] args) { + HeapMonitor.enableSamplingEvents(); + + if (enablingSamplingInSecondaryAgent()) { + throw new RuntimeException("Enabling sampling in second agent succeeded..."); + } + } +} diff --git a/test/hotspot/jtreg/serviceability/jvmti/HeapMonitor/MyPackage/HeapMonitorVMEventsTest.java b/test/hotspot/jtreg/serviceability/jvmti/HeapMonitor/MyPackage/HeapMonitorVMEventsTest.java new file mode 100644 --- /dev/null +++ b/test/hotspot/jtreg/serviceability/jvmti/HeapMonitor/MyPackage/HeapMonitorVMEventsTest.java @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2018, Google and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package MyPackage; + +import java.util.ArrayList; +import java.util.List; + +/** + * @test + * @summary Verifies that when the VM event is sent, sampled events are also collected. + * @build Frame HeapMonitor + * @compile HeapMonitorVMEventsTest.java + * @run main/othervm/native -agentlib:HeapMonitorTest MyPackage.HeapMonitorVMEventsTest + */ + +public class HeapMonitorVMEventsTest implements Cloneable { + private static native int vmEvents(); + private static Object[] array; + + private static void cloneObjects(int iterations) { + HeapMonitorVMEventsTest object = new HeapMonitorVMEventsTest(); + for (int i = 0; i < iterations; i++) { + try { + array[i] = object.clone(); + } catch (Exception e) { + // Nop. + } + } + } + + public Object clone() throws CloneNotSupportedException { + return super.clone(); + } + + private static void checkDifference(int first, int second) { + double diff = Math.abs(first - second) * 100; + diff /= first; + + // Accept a 10% error rate: with objects being allocated: this allows a bit of room in + // case other items are getting allocated during the test. + if (diff > 10) { + throw new RuntimeException("Error rate is over the accepted rate: " + diff + + ": " + first + " , " + second); + } + } + + private static void compareSampledAndVM() { + final int iterations = 1024; + HeapMonitor.resetEventStorage(); + cloneObjects(iterations); + + int onlySampleCount = HeapMonitor.sampledEvents(); + + HeapMonitor.enableVMEvents(); + HeapMonitor.resetEventStorage(); + if (!HeapMonitor.eventStorageIsEmpty()) { + throw new RuntimeException("Storage is not empty after reset."); + } + + cloneObjects(iterations); + + int sampleCount = HeapMonitor.sampledEvents(); + int vmCount = vmEvents(); + + System.err.println("Obtained: " + onlySampleCount + " - " + sampleCount + " - " + vmCount); + checkDifference(onlySampleCount, sampleCount); + checkDifference(onlySampleCount, vmCount); + } + + public static void main(String[] args) { + final int iterations = 1 << 10; + array = new Object[iterations]; + + if (!HeapMonitor.eventStorageIsEmpty()) { + throw new RuntimeException("Storage is not empty at test start..."); + } + + HeapMonitor.sampleEverything(); + compareSampledAndVM(); + } +} diff --git a/test/hotspot/jtreg/serviceability/jvmti/HeapMonitor/MyPackage/ThreadInformation.java b/test/hotspot/jtreg/serviceability/jvmti/HeapMonitor/MyPackage/ThreadInformation.java new file mode 100644 --- /dev/null +++ b/test/hotspot/jtreg/serviceability/jvmti/HeapMonitor/MyPackage/ThreadInformation.java @@ -0,0 +1,196 @@ +/* + * Copyright (c) 2018, Google and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package MyPackage; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; + +/** API for handling heap allocating threads. */ +class ThreadInformation { + private Thread thread; + private Allocator allocator; + + public ThreadInformation(Thread thread, Allocator allocator) { + this.thread = thread; + this.allocator = allocator; + } + + public void waitForJobDone() { + allocator.waitForJobDone(); + } + + public void stop() { + try { + allocator.stopRun(); + thread.join(); + + if (!allocator.endedNormally()) { + throw new RuntimeException("Thread did not end normally..."); + } + + } catch(InterruptedException e) { + throw new RuntimeException("Thread got interrupted..."); + } + } + + private void start() { + allocator.start(); + } + + public static void startThreads(List threadList) { + for (ThreadInformation info : threadList) { + info.start(); + } + } + + public static void stopThreads(List threadList) { + for (ThreadInformation info : threadList) { + info.stop(); + } + } + + public Thread getThread() { + return thread; + } + + public static void waitForThreads(List threadList) { + System.err.println("Waiting for threads to be done"); + // Wait until all threads have put an object in the queue. + for (ThreadInformation info : threadList) { + info.waitForJobDone(); + } + } + + public static List createThreadList(int numThreads) { + List threadList = new ArrayList<>(); + for (int i = 0 ; i < numThreads; i++) { + Allocator allocator = new Allocator(i); + Thread thread = new Thread(allocator, "Allocator" + i); + + ThreadInformation info = new ThreadInformation(thread, allocator); + threadList.add(info); + thread.start(); + } + return threadList; + } +} + +class Allocator implements Runnable { + private int depth; + private List currentList; + private BlockingQueue jobCanStart; + private BlockingQueue jobDone; + private BlockingQueue jobCanStop; + private boolean failed; + + public Allocator(int depth) { + this.jobCanStart = new LinkedBlockingQueue<>(); + this.jobDone = new LinkedBlockingQueue<>(); + this.jobCanStop = new LinkedBlockingQueue<>(); + this.depth = depth; + } + + public boolean endedNormally() { + return !failed; + } + + private void helper() { + List newList = new ArrayList<>(); + // Let us assume that the array is 40 bytes of memory, keep in + // memory at least 1.7MB without counting the link-list itself, which adds to this. + int iterations = (1 << 20) / 24; + for (int i = 0; i < iterations; i++) { + int newTmp[] = new int[5]; + // Force it to be kept. + newList.add(newTmp); + } + + // Replace old list with new list, which provokes two things: + // Old list will get GC'd at some point. + // New list forces that this thread has some allocations still sampled. + currentList = newList; + } + + private void recursiveWrapper(int depth) { + if (depth > 0) { + recursiveWrapper(depth - 1); + return; + } + helper(); + } + + public void stopRun() throws InterruptedException { + jobCanStop.put(new Object()); + } + + public void run() { + String name = Thread.currentThread().getName(); + System.err.println("Going to run: " + name); + // Wait till we are told to really start. + waitForStart(); + + System.err.println("Running: " + name); + for (int j = 0; j < 100; j++) { + recursiveWrapper(depth); + } + + try { + // Tell the main thread we are done. + jobDone.put(new Object()); + + System.err.println("Waiting for main: " + name); + // Wait until the main thread says we can stop. + jobCanStop.take(); + System.err.println("Waited for main: " + name); + } catch (InterruptedException e) { + failed = true; + } + } + + public void waitForJobDone() { + try { + jobDone.take(); + } catch(InterruptedException e) { + throw new RuntimeException("Thread got interrupted..."); + } + } + + public void waitForStart() { + try { + jobCanStart.take(); + } catch(InterruptedException e) { + throw new RuntimeException("Thread got interrupted..."); + } + } + + public void start() { + try { + jobCanStart.put(new Object()); + } catch(InterruptedException e) { + throw new RuntimeException("Thread got interrupted..."); + } + } +} diff --git a/test/hotspot/jtreg/serviceability/jvmti/HeapMonitor/libHeapMonitorTest.c b/test/hotspot/jtreg/serviceability/jvmti/HeapMonitor/libHeapMonitorTest.c new file mode 100644 --- /dev/null +++ b/test/hotspot/jtreg/serviceability/jvmti/HeapMonitor/libHeapMonitorTest.c @@ -0,0 +1,1105 @@ +/* + * Copyright (c) 2018, Google and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#include +#include +#include +#include +#include "jvmti.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef JNI_ENV_ARG + +#ifdef __cplusplus +#define JNI_ENV_ARG(x, y) y +#define JNI_ENV_PTR(x) x +#else +#define JNI_ENV_ARG(x,y) x, y +#define JNI_ENV_PTR(x) (*x) +#endif + +#endif + +#define TRUE 1 +#define FALSE 0 +#define PRINT_OUT 0 + +static jvmtiEnv *jvmti = NULL; +static jvmtiEnv *second_jvmti = NULL; + +typedef struct _ObjectTrace{ + jweak object; + size_t size; + jvmtiFrameInfo* frames; + size_t frame_count; + jthread thread; +} ObjectTrace; + +typedef struct _EventStorage { + int live_object_additions; + int live_object_size; + int live_object_count; + ObjectTrace** live_objects; + + int garbage_history_size; + int garbage_history_index; + ObjectTrace** garbage_collected_objects; + + // Two separate monitors to separate storage data race and the compaction field + // data race. + jrawMonitorID storage_monitor; + + int compaction_required; + jrawMonitorID compaction_monitor; +} EventStorage; + +typedef struct _ExpectedContentFrame { + const char *name; + const char *signature; + const char *file_name; + int line_number; +} ExpectedContentFrame; + +static +void event_storage_lock(EventStorage* storage) { + (*jvmti)->RawMonitorEnter(jvmti, storage->storage_monitor); +} + +static +void event_storage_unlock(EventStorage* storage) { + (*jvmti)->RawMonitorExit(jvmti, storage->storage_monitor); +} + +static +void event_storage_lock_compaction(EventStorage* storage) { + (*jvmti)->RawMonitorEnter(jvmti, storage->compaction_monitor); +} + +static +void event_storage_unlock_compaction(EventStorage* storage) { + (*jvmti)->RawMonitorExit(jvmti, storage->compaction_monitor); +} + +// Given a method and a location, this method gets the line number. +static +jint get_line_number(jvmtiEnv* jvmti, jmethodID method, + jlocation location) { + // Read the line number table. + jvmtiLineNumberEntry *table_ptr = 0; + jint line_number_table_entries; + int l; + jlocation last_location; + int jvmti_error = (*jvmti)->GetLineNumberTable(jvmti, method, + &line_number_table_entries, + &table_ptr); + + if (JVMTI_ERROR_NONE != jvmti_error) { + return -1; + } + if (line_number_table_entries <= 0) { + return -1; + } + if (line_number_table_entries == 1) { + return table_ptr[0].line_number; + } + + // Go through all the line numbers... + last_location = table_ptr[0].start_location; + for (l = 1; l < line_number_table_entries; l++) { + // ... and if you see one that is in the right place for your + // location, you've found the line number! + if ((location < table_ptr[l].start_location) && + (location >= last_location)) { + return table_ptr[l - 1].line_number; + } + last_location = table_ptr[l].start_location; + } + + if (location >= last_location) { + return table_ptr[line_number_table_entries - 1].line_number; + } else { + return -1; + } +} + +static void print_out_frames(JNIEnv* env, ObjectTrace* trace) { + jvmtiFrameInfo* frames = trace->frames; + size_t i; + for (i = 0; i < trace->frame_count; i++) { + // Get basic information out of the trace. + jlocation bci = frames[i].location; + jmethodID methodid = frames[i].method; + char *name = NULL, *signature = NULL, *file_name = NULL; + jclass declaring_class; + int line_number; + jvmtiError err; + + if (bci < 0) { + fprintf(stderr, "\tNative frame\n"); + continue; + } + + // Transform into usable information. + line_number = get_line_number(jvmti, methodid, bci); + if (JVMTI_ERROR_NONE != + (*jvmti)->GetMethodName(jvmti, methodid, &name, &signature, 0)) { + fprintf(stderr, "\tUnknown method name\n"); + continue; + } + + if (JVMTI_ERROR_NONE != + (*jvmti)->GetMethodDeclaringClass(jvmti, methodid, &declaring_class)) { + fprintf(stderr, "\tUnknown class\n"); + continue; + } + + err = (*jvmti)->GetSourceFileName(jvmti, declaring_class, + &file_name); + if (err != JVMTI_ERROR_NONE) { + fprintf(stderr, "\tUnknown file\n"); + continue; + } + + // Compare now, none should be NULL. + if (name == NULL) { + fprintf(stderr, "\tUnknown name\n"); + continue; + } + + if (file_name == NULL) { + fprintf(stderr, "\tUnknown file\n"); + continue; + } + + if (signature == NULL) { + fprintf(stderr, "\tUnknown signature\n"); + continue; + } + + fprintf(stderr, "\t%s%s (%s: %d)\n", + name, signature, file_name, line_number); + } +} + +static jboolean check_sample_content(JNIEnv* env, + ObjectTrace* trace, + ExpectedContentFrame *expected, + size_t expected_count, + int print_out_comparisons) { + jvmtiFrameInfo* frames; + size_t i; + + if (expected_count > trace->frame_count) { + return FALSE; + } + + frames = trace->frames; + for (i = 0; i < expected_count; i++) { + // Get basic information out of the trace. + jlocation bci = frames[i].location; + jmethodID methodid = frames[i].method; + char *name = NULL, *signature = NULL, *file_name = NULL; + jclass declaring_class; + int line_number; + jvmtiError err; + + if (bci < 0 && expected[i].line_number != -1) { + return FALSE; + } + + // Transform into usable information. + line_number = get_line_number(jvmti, methodid, bci); + (*jvmti)->GetMethodName(jvmti, methodid, &name, &signature, 0); + + if (JVMTI_ERROR_NONE != + (*jvmti)->GetMethodDeclaringClass(jvmti, methodid, &declaring_class)) { + return FALSE; + } + + err = (*jvmti)->GetSourceFileName(jvmti, declaring_class, + &file_name); + if (err != JVMTI_ERROR_NONE) { + return FALSE; + } + + // Compare now, none should be NULL. + if (name == NULL) { + return FALSE; + } + + if (file_name == NULL) { + return FALSE; + } + + if (signature == NULL) { + return FALSE; + } + + if (print_out_comparisons) { + fprintf(stderr, "\tComparing:\n"); + fprintf(stderr, "\t\tNames: %s and %s\n", name, expected[i].name); + fprintf(stderr, "\t\tSignatures: %s and %s\n", signature, expected[i].signature); + fprintf(stderr, "\t\tFile name: %s and %s\n", file_name, expected[i].file_name); + fprintf(stderr, "\t\tLines: %d and %d\n", line_number, expected[i].line_number); + fprintf(stderr, "\t\tResult is %d\n", + (strcmp(name, expected[i].name) || + strcmp(signature, expected[i].signature) || + strcmp(file_name, expected[i].file_name) || + line_number != expected[i].line_number)); + } + + if (strcmp(name, expected[i].name) || + strcmp(signature, expected[i].signature) || + strcmp(file_name, expected[i].file_name) || + line_number != expected[i].line_number) { + return FALSE; + } + } + + return TRUE; +} + +// Static native API for various tests. +static void fill_native_frames(JNIEnv* env, jobjectArray frames, + ExpectedContentFrame* native_frames, size_t size) { + size_t i; + for (i = 0; i < size; i++) { + jobject obj = (*env)->GetObjectArrayElement(env, frames, (jsize) i); + jclass frame_class = (*env)->GetObjectClass(env, obj); + jfieldID line_number_field_id = (*env)->GetFieldID(env, frame_class, + "lineNumber", "I"); + int line_number = (*env)->GetIntField(env, obj, line_number_field_id); + + jfieldID string_id = (*env)->GetFieldID(env, frame_class, "method", + "Ljava/lang/String;"); + jstring string_object = (jstring) (*env)->GetObjectField(env, obj, + string_id); + const char* method = (*env)->GetStringUTFChars(env, string_object, 0); + const char* file_name; + const char* signature; + + string_id = (*env)->GetFieldID(env, frame_class, "fileName", + "Ljava/lang/String;"); + string_object = (jstring) (*env)->GetObjectField(env, obj, string_id); + file_name = (*env)->GetStringUTFChars(env, string_object, 0); + + string_id = (*env)->GetFieldID(env, frame_class, "signature", + "Ljava/lang/String;"); + string_object = (jstring) (*env)->GetObjectField(env, obj, string_id); + signature= (*env)->GetStringUTFChars(env, string_object, 0); + + native_frames[i].name = method; + native_frames[i].file_name = file_name; + native_frames[i].signature = signature; + native_frames[i].line_number = line_number; + } +} + +// Internal storage system implementation. +static EventStorage global_event_storage; +static EventStorage second_global_event_storage; + +static void event_storage_set_compaction_required(EventStorage* storage) { + event_storage_lock_compaction(storage); + storage->compaction_required = 1; + event_storage_unlock_compaction(storage); +} + +static int event_storage_get_compaction_required(EventStorage* storage) { + int result; + event_storage_lock_compaction(storage); + result = storage->compaction_required; + event_storage_unlock_compaction(storage); + return result; +} + +static void event_storage_set_garbage_history(EventStorage* storage, int value) { + size_t size; + event_storage_lock(storage); + global_event_storage.garbage_history_size = value; + free(global_event_storage.garbage_collected_objects); + size = sizeof(*global_event_storage.garbage_collected_objects) * value; + global_event_storage.garbage_collected_objects = malloc(size); + memset(global_event_storage.garbage_collected_objects, 0, size); + event_storage_unlock(storage); +} + +// No mutex here, it is handled by the caller. +static void event_storage_add_garbage_collected_object(EventStorage* storage, + ObjectTrace* object) { + int idx = storage->garbage_history_index; + ObjectTrace* old_object = storage->garbage_collected_objects[idx]; + if (old_object != NULL) { + free(old_object->frames); + free(storage->garbage_collected_objects[idx]); + } + + storage->garbage_collected_objects[idx] = object; + storage->garbage_history_index = (idx + 1) % storage->garbage_history_size; +} + +static int event_storage_get_count(EventStorage* storage) { + int result; + event_storage_lock(storage); + result = storage->live_object_count; + event_storage_unlock(storage); + return result; +} + +static double event_storage_get_average_rate(EventStorage* storage) { + double accumulation = 0; + int max_size; + int i; + + event_storage_lock(storage); + max_size = storage->live_object_count; + + for (i = 0; i < max_size; i++) { + accumulation += storage->live_objects[i]->size; + } + + event_storage_unlock(storage); + return accumulation / max_size; +} + +static jboolean event_storage_contains(JNIEnv* env, + EventStorage* storage, + ExpectedContentFrame* frames, + size_t size) { + int i; + event_storage_lock(storage); + fprintf(stderr, "Checking storage count %d\n", storage->live_object_count); + for (i = 0; i < storage->live_object_count; i++) { + ObjectTrace* trace = storage->live_objects[i]; + + if (check_sample_content(env, trace, frames, size, PRINT_OUT)) { + event_storage_unlock(storage); + return TRUE; + } + } + event_storage_unlock(storage); + return FALSE; +} + +static jboolean event_storage_garbage_contains(JNIEnv* env, + EventStorage* storage, + ExpectedContentFrame* frames, + size_t size) { + int i; + event_storage_lock(storage); + fprintf(stderr, "Checking garbage storage count %d\n", + storage->garbage_history_size); + for (i = 0; i < storage->garbage_history_size; i++) { + ObjectTrace* trace = storage->garbage_collected_objects[i]; + + if (trace == NULL) { + continue; + } + + if (check_sample_content(env, trace, frames, size, PRINT_OUT)) { + event_storage_unlock(storage); + return TRUE; + } + } + event_storage_unlock(storage); + return FALSE; +} + +// No mutex here, handled by the caller. +static void event_storage_augment_storage(EventStorage* storage) { + int new_max = (storage->live_object_size * 2) + 1; + ObjectTrace** new_objects = malloc(new_max * sizeof(*new_objects)); + + int current_count = storage->live_object_count; + memcpy(new_objects, storage->live_objects, current_count * sizeof(*new_objects)); + free(storage->live_objects); + storage->live_objects = new_objects; + storage->live_object_size = new_max; +} + +static void event_storage_add(EventStorage* storage, + JNIEnv* jni, + jthread thread, + jobject object, + jclass klass, + jlong size) { + jvmtiFrameInfo frames[64]; + jint count; + jvmtiError err; + + err = (*jvmti)->GetStackTrace(jvmti, thread, 0, 64, frames, &count); + if (err == JVMTI_ERROR_NONE && count >= 1) { + ObjectTrace* live_object; + jvmtiFrameInfo* allocated_frames = (jvmtiFrameInfo*) malloc(count * sizeof(*allocated_frames)); + memcpy(allocated_frames, frames, count * sizeof(*allocated_frames)); + + live_object = (ObjectTrace*) malloc(sizeof(*live_object)); + live_object->frames = allocated_frames; + live_object->frame_count = count; + live_object->size = size; + live_object->thread = thread; + live_object->object = (*jni)->NewWeakGlobalRef(jni, object); + + // Only now lock and get things done quickly. + event_storage_lock(storage); + + storage->live_object_additions++; + + if (storage->live_object_count >= storage->live_object_size) { + event_storage_augment_storage(storage); + } + assert(storage->live_object_count < storage->live_object_size); + + if (PRINT_OUT) { + fprintf(stderr, "Adding trace for thread %p, frame_count %d, storage %p\n", + thread, count, storage); + print_out_frames(jni, live_object); + } + storage->live_objects[storage->live_object_count] = live_object; + storage->live_object_count++; + + event_storage_unlock(storage); + } +} + +static void event_storage_compact(EventStorage* storage, JNIEnv* jni) { + int max, i, dest; + ObjectTrace** live_objects; + + event_storage_lock_compaction(storage); + storage->compaction_required = 0; + event_storage_unlock_compaction(storage); + + event_storage_lock(storage); + + max = storage->live_object_count; + live_objects = storage->live_objects; + + for (i = 0, dest = 0; i < max; i++) { + ObjectTrace* live_object = live_objects[i]; + jweak object = live_object->object; + + if (!(*jni)->IsSameObject(jni, object, NULL)) { + if (dest != i) { + live_objects[dest] = live_object; + dest++; + } + } else { + (*jni)->DeleteWeakGlobalRef(jni, object); + live_object->object = NULL; + + event_storage_add_garbage_collected_object(storage, live_object); + } + } + + storage->live_object_count = dest; + event_storage_unlock(storage); +} + +static void event_storage_free_objects(ObjectTrace** array, int max) { + int i; + for (i = 0; i < max; i++) { + free(array[i]), array[i] = NULL; + } +} + +static void event_storage_reset(EventStorage* storage) { + event_storage_lock(storage); + + // Reset everything except the mutex and the garbage collection. + event_storage_free_objects(storage->live_objects, + storage->live_object_count); + storage->live_object_additions = 0; + storage->live_object_size = 0; + storage->live_object_count = 0; + free(storage->live_objects), storage->live_objects = NULL; + + event_storage_free_objects(storage->garbage_collected_objects, + storage->garbage_history_size); + + storage->compaction_required = 0; + storage->garbage_history_index = 0; + + event_storage_unlock(storage); +} + +static int event_storage_number_additions(EventStorage* storage) { + int result; + event_storage_lock(storage); + result = storage->live_object_additions; + event_storage_unlock(storage); + return result; +} + +// Start of the JVMTI agent code. +static const char *EXC_CNAME = "java/lang/Exception"; + +static int check_error(jvmtiError err, const char *s) { + if (err != JVMTI_ERROR_NONE) { + printf(" ## %s error: %d\n", s, err); + return 1; + } + return 0; +} + +static int check_capability_error(jvmtiError err, const char *s) { + if (err != JVMTI_ERROR_NONE) { + if (err == JVMTI_ERROR_MUST_POSSESS_CAPABILITY) { + return 0; + } + fprintf(stderr, " ## %s error: %d\n", s, err); + } + return 1; +} + +static jint throw_exception(JNIEnv *env, char *msg) { + jclass exc_class = JNI_ENV_PTR(env)->FindClass(JNI_ENV_ARG(env, EXC_CNAME)); + + if (exc_class == NULL) { + fprintf(stderr, "throw_exception: Error in FindClass(env, %s)\n", + EXC_CNAME); + return -1; + } + return JNI_ENV_PTR(env)->ThrowNew(JNI_ENV_ARG(env, exc_class), msg); +} + +static jint Agent_Initialize(JavaVM *jvm, char *options, void *reserved); + +JNIEXPORT +jint JNICALL Agent_OnLoad(JavaVM *jvm, char *options, void *reserved) { + return Agent_Initialize(jvm, options, reserved); +} + +JNIEXPORT +jint JNICALL Agent_OnAttach(JavaVM *jvm, char *options, void *reserved) { + return Agent_Initialize(jvm, options, reserved); +} + +JNIEXPORT +jint JNICALL JNI_OnLoad(JavaVM *jvm, void *reserved) { + return JNI_VERSION_1_8; +} + +#define MAX_THREADS 500 + +typedef struct ThreadStats { + int number_threads; + int counts[MAX_THREADS]; + int not_helper_counts[MAX_THREADS]; + int index[MAX_THREADS]; + jthread threads[MAX_THREADS]; + + int method_resolution_problem; +} ThreadStats; + +static ThreadStats thread_stats; + +static void add_thread_count(jthread thread, int lock, int helper) { + int i; + jvmtiThreadInfo info; + const char* name; + char* end; + int idx; + int err; + + if (lock) { + event_storage_lock(&global_event_storage); + } + + for (i = 0; i < thread_stats.number_threads; i++) { + if (thread_stats.threads[i] == thread) { + if (helper) { + thread_stats.counts[i]++; + } else { + thread_stats.not_helper_counts[i]++; + } + + if (lock) { + event_storage_unlock(&global_event_storage); + } + return; + } + } + + thread_stats.threads[thread_stats.number_threads] = thread; + + err = (*jvmti)->GetThreadInfo(jvmti, thread, &info); + if (err != JVMTI_ERROR_NONE) { + if (lock) { + event_storage_unlock(&global_event_storage); + } + + // Just to have it accounted as an error... + info.name = "Allocator99"; + } + + if (!strstr(info.name, "Allocator")) { + if (lock) { + event_storage_unlock(&global_event_storage); + } + + // Just to have it accounted as an error... + info.name = "Allocator98"; + } + + name = info.name + 9; + end = NULL; + idx = strtol(name, &end, 0); + + if (*end == '\0') { + if (helper) { + thread_stats.counts[thread_stats.number_threads]++; + } else { + thread_stats.not_helper_counts[thread_stats.number_threads]++; + } + + thread_stats.index[thread_stats.number_threads] = idx; + thread_stats.number_threads++; + } else { + fprintf(stderr, "Problem with thread name...: %p %s\n", thread, name); + } + + if (PRINT_OUT) { + fprintf(stderr, "Added %s - %p - %d - lock: %d\n", info.name, thread, idx, lock); + } + + if (lock) { + event_storage_unlock(&global_event_storage); + } +} + +static void print_thread_stats() { + int i; + event_storage_lock(&global_event_storage); + fprintf(stderr, "Method resolution problem: %d\n", thread_stats.method_resolution_problem); + fprintf(stderr, "Thread count:\n"); + for (i = 0; i < thread_stats.number_threads; i++) { + fprintf(stderr, "\t%p: %d: %d - %d\n", thread_stats.threads[i], + thread_stats.index[i], + thread_stats.counts[i], + thread_stats.not_helper_counts[i]); + } + event_storage_unlock(&global_event_storage); +} + +JNIEXPORT +void JNICALL SampledObjectAlloc(jvmtiEnv *jvmti_env, + JNIEnv* jni_env, + jthread thread, + jobject object, + jclass object_klass, + jlong size) { + add_thread_count(thread, 1, 1); + + if (event_storage_get_compaction_required(&global_event_storage)) { + event_storage_compact(&global_event_storage, jni_env); + } + + event_storage_add(&global_event_storage, jni_env, thread, object, + object_klass, size); +} + +JNIEXPORT +void JNICALL VMObjectAlloc(jvmtiEnv *jvmti_env, + JNIEnv* jni_env, + jthread thread, + jobject object, + jclass object_klass, + jlong size) { + event_storage_add(&second_global_event_storage, jni_env, thread, object, + object_klass, size); +} + +JNIEXPORT +void JNICALL GarbageCollectionFinish(jvmtiEnv *jvmti_env) { + event_storage_set_compaction_required(&global_event_storage); +} + +static int enable_notifications() { + if (check_error((*jvmti)->SetEventNotificationMode( + jvmti, JVMTI_ENABLE, JVMTI_EVENT_GARBAGE_COLLECTION_FINISH, NULL), + "Set event notifications")) { + return 1; + } + + return check_error((*jvmti)->SetEventNotificationMode( + jvmti, JVMTI_ENABLE, JVMTI_EVENT_SAMPLED_OBJECT_ALLOC, NULL), + "Set event notifications"); +} + +static int enable_notifications_for_two_threads(jthread first, jthread second) { + if (check_error((*jvmti)->SetEventNotificationMode( + jvmti, JVMTI_ENABLE, JVMTI_EVENT_GARBAGE_COLLECTION_FINISH, NULL), + "Set event notifications")) { + return 0; + } + + if (check_error((*jvmti)->SetEventNotificationMode( + jvmti, JVMTI_ENABLE, JVMTI_EVENT_SAMPLED_OBJECT_ALLOC, first), + "Set event notifications")) { + return 0; + } + + // Second thread should fail. + if (check_error((*jvmti)->SetEventNotificationMode( + jvmti, JVMTI_ENABLE, JVMTI_EVENT_SAMPLED_OBJECT_ALLOC, second), + "Set event notifications")) { + return 0; + } + + return 1; +} + +static +jint Agent_Initialize(JavaVM *jvm, char *options, void *reserved) { + jint res; + jvmtiEventCallbacks callbacks; + jvmtiCapabilities caps; + + res = JNI_ENV_PTR(jvm)->GetEnv(JNI_ENV_ARG(jvm, (void **) &jvmti), + JVMTI_VERSION_9); + if (res != JNI_OK || jvmti == NULL) { + fprintf(stderr, "Error: wrong result of a valid call to GetEnv!\n"); + return JNI_ERR; + } + + // Get second jvmti environment. + res = JNI_ENV_PTR(jvm)->GetEnv(JNI_ENV_ARG(jvm, (void **) &second_jvmti), + JVMTI_VERSION_9); + if (res != JNI_OK || second_jvmti == NULL) { + fprintf(stderr, "Error: wrong result of a valid second call to GetEnv!\n"); + return JNI_ERR; + } + + if (PRINT_OUT) { + fprintf(stderr, "Storage is at %p, secondary is at %p\n", + &global_event_storage, &second_global_event_storage); + } + + (*jvmti)->CreateRawMonitor(jvmti, "storage_monitor", + &global_event_storage.storage_monitor); + (*jvmti)->CreateRawMonitor(jvmti, "second_storage_monitor", + &second_global_event_storage.storage_monitor); + + (*jvmti)->CreateRawMonitor(jvmti, "compaction_monitor", + &global_event_storage.compaction_monitor); + (*jvmti)->CreateRawMonitor(jvmti, "second_compaction_monitor", + &second_global_event_storage.compaction_monitor); + + event_storage_set_garbage_history(&global_event_storage, 200); + event_storage_set_garbage_history(&second_global_event_storage, 200); + + memset(&callbacks, 0, sizeof(callbacks)); + callbacks.SampledObjectAlloc = &SampledObjectAlloc; + callbacks.VMObjectAlloc = &VMObjectAlloc; + callbacks.GarbageCollectionFinish = &GarbageCollectionFinish; + + memset(&caps, 0, sizeof(caps)); + // Get line numbers, sample events, filename, and gc events for the tests. + caps.can_get_line_numbers = 1; + caps.can_get_source_file_name = 1; + caps.can_generate_garbage_collection_events = 1; + caps.can_generate_sampled_object_alloc_events = 1; + caps.can_generate_vm_object_alloc_events = 1; + if (check_error((*jvmti)->AddCapabilities(jvmti, &caps), "Add capabilities")) { + return JNI_ERR; + } + + if (check_error((*jvmti)->SetEventCallbacks(jvmti, &callbacks, + sizeof(jvmtiEventCallbacks)), + " Set Event Callbacks")) { + return JNI_ERR; + } + return JNI_OK; +} + +JNIEXPORT void JNICALL +Java_MyPackage_HeapMonitor_setSamplingRate(JNIEnv* env, jclass cls, jint value) { + (*jvmti)->SetHeapSamplingRate(jvmti, value); +} + +JNIEXPORT jboolean JNICALL +Java_MyPackage_HeapMonitor_eventStorageIsEmpty(JNIEnv* env, jclass cls) { + return event_storage_get_count(&global_event_storage) == 0; +} + +JNIEXPORT jint JNICALL +Java_MyPackage_HeapMonitor_getEventStorageElementCount(JNIEnv* env, jclass cls) { + return event_storage_get_count(&global_event_storage); +} + +JNIEXPORT void JNICALL +Java_MyPackage_HeapMonitor_enableSamplingEvents(JNIEnv* env, jclass cls) { + enable_notifications(); +} + +JNIEXPORT jboolean JNICALL +Java_MyPackage_HeapMonitor_enableSamplingEventsForTwoThreads(JNIEnv* env, + jclass cls, + jthread first, + jthread second) { + return enable_notifications_for_two_threads(first, second); +} + +JNIEXPORT void JNICALL +Java_MyPackage_HeapMonitor_disableSamplingEvents(JNIEnv* env, jclass cls) { + check_error((*jvmti)->SetEventNotificationMode( + jvmti, JVMTI_DISABLE, JVMTI_EVENT_SAMPLED_OBJECT_ALLOC, NULL), + "Set event notifications"); + + check_error((*jvmti)->SetEventNotificationMode( + jvmti, JVMTI_DISABLE, JVMTI_EVENT_GARBAGE_COLLECTION_FINISH, NULL), + "Garbage Collection Finish"); +} + +JNIEXPORT jboolean JNICALL +Java_MyPackage_HeapMonitor_obtainedEvents(JNIEnv* env, jclass cls, jobjectArray frames) { + jboolean result; + jsize size = (*env)->GetArrayLength(env, frames); + ExpectedContentFrame *native_frames = malloc(size * sizeof(*native_frames)); + + if (native_frames == NULL) { + return 0; + } + + fill_native_frames(env, frames, native_frames, size); + result = event_storage_contains(env, &global_event_storage, native_frames, size); + + free(native_frames), native_frames = NULL; + return result; +} + +JNIEXPORT jboolean JNICALL +Java_MyPackage_HeapMonitor_garbageContains(JNIEnv* env, jclass cls, jobjectArray frames) { + jboolean result; + jsize size = (*env)->GetArrayLength(env, frames); + ExpectedContentFrame *native_frames = malloc(size * sizeof(*native_frames)); + + if (native_frames == NULL) { + return 0; + } + + fill_native_frames(env, frames, native_frames, size); + result = event_storage_garbage_contains(env, &global_event_storage, native_frames, size); + + free(native_frames), native_frames = NULL; + return result; +} + +JNIEXPORT void JNICALL +Java_MyPackage_HeapMonitor_forceGarbageCollection(JNIEnv* env, jclass cls) { + check_error((*jvmti)->ForceGarbageCollection(jvmti), + "Forced Garbage Collection"); +} + +JNIEXPORT void JNICALL +Java_MyPackage_HeapMonitor_resetEventStorage(JNIEnv* env, jclass cls) { + event_storage_reset(&global_event_storage); + event_storage_reset(&second_global_event_storage); +} + +JNIEXPORT jboolean JNICALL +Java_MyPackage_HeapMonitorNoCapabilityTest_allSamplingMethodsFail(JNIEnv *env, + jclass cls) { + jvmtiCapabilities caps; + memset(&caps, 0, sizeof(caps)); + caps.can_generate_sampled_object_alloc_events = 1; + if (check_error((*jvmti)->RelinquishCapabilities(jvmti, &caps), + "Add capabilities\n")){ + return FALSE; + } + + if (check_capability_error((*jvmti)->SetHeapSamplingRate(jvmti, 1<<19), + "Set Heap Sampling Rate")) { + return FALSE; + } + return TRUE; +} + +JNIEXPORT jboolean JNICALL +Java_MyPackage_HeapMonitorIllegalArgumentTest_testIllegalArgument(JNIEnv *env, + jclass cls) { + if (check_error((*jvmti)->SetHeapSamplingRate(jvmti, 0), + "Sampling rate 0 failed\n")){ + return FALSE; + } + + if (check_error((*jvmti)->SetHeapSamplingRate(jvmti, 1024), + "Sampling rate 1024 failed\n")){ + return FALSE; + } + + if (!check_error((*jvmti)->SetHeapSamplingRate(jvmti, -1), + "Sampling rate -1 passed\n")){ + return FALSE; + } + + if (!check_error((*jvmti)->SetHeapSamplingRate(jvmti, -1024), + "Sampling rate -1024 passed\n")){ + return FALSE; + } + + return TRUE; +} + +JNIEXPORT jdouble JNICALL +Java_MyPackage_HeapMonitorStatRateTest_getAverageRate(JNIEnv *env, jclass cls) { + return event_storage_get_average_rate(&global_event_storage); +} + +typedef struct sThreadsFound { + jthread* threads; + int num_threads; +} ThreadsFound; + +JNIEXPORT jboolean JNICALL +Java_MyPackage_HeapMonitorThreadTest_checkSamples(JNIEnv* env, jclass cls, + jint num_threads) { + + print_thread_stats(); + // Ensure we got stacks from at least num_threads. + return thread_stats.number_threads >= num_threads; +} + +JNIEXPORT +void JNICALL SampledObjectAlloc2(jvmtiEnv *jvmti_env, + JNIEnv* jni_env, + jthread thread, + jobject object, + jclass object_klass, + jlong size) { + // Nop for now, two agents are not yet implemented. + assert(0); +} + +JNIEXPORT jboolean JNICALL +Java_MyPackage_HeapMonitorTwoAgentsTest_enablingSamplingInSecondaryAgent( + JNIEnv* env, jclass cls) { + // Currently this method should be failing directly at the AddCapability step + // but the implementation is correct for when multi-agent support is enabled. + jvmtiCapabilities caps; + jvmtiEventCallbacks callbacks; + + memset(&caps, 0, sizeof(caps)); + caps.can_generate_sampled_object_alloc_events = 1; + if (check_error((*second_jvmti)->AddCapabilities(second_jvmti, &caps), + "Set the capability for second agent")) { + return FALSE; + } + + memset(&callbacks, 0, sizeof(callbacks)); + callbacks.SampledObjectAlloc = &SampledObjectAlloc2; + + if (check_error((*second_jvmti)->SetEventCallbacks(second_jvmti, &callbacks, + sizeof(jvmtiEventCallbacks)), + " Set Event Callbacks for second agent")) { + return FALSE; + } + + return TRUE; +} + +JNIEXPORT void JNICALL +Java_MyPackage_HeapMonitor_enableVMEvents(JNIEnv* env, jclass cls) { + check_error((*jvmti)->SetEventNotificationMode( + jvmti, JVMTI_ENABLE, JVMTI_EVENT_VM_OBJECT_ALLOC, NULL), + "Set vm event notifications"); +} + +JNIEXPORT jint JNICALL +Java_MyPackage_HeapMonitorVMEventsTest_vmEvents(JNIEnv* env, jclass cls) { + return event_storage_number_additions(&second_global_event_storage); +} + +JNIEXPORT jint JNICALL +Java_MyPackage_HeapMonitor_sampledEvents(JNIEnv* env, jclass cls) { + return event_storage_number_additions(&global_event_storage); +} + +static void allocate_object(JNIEnv* env) { + // Construct an Object. + jclass cls = (*env)->FindClass(env, "java/lang/Object"); + jmethodID constructor; + + if (cls == NULL) { + throw_exception(env, "Cannot find Object class."); + return; + } + + constructor = (*env)->GetMethodID(env, cls, "", "()V"); + + if (constructor == NULL) { + throw_exception(env, "Cannot find Object class constructor."); + return; + } + + // Call back constructor to allocate a new instance, with an int argument + (*env)->NewObject(env, cls, constructor); +} + +// Ensure we got a callback for the test. +static int did_recursive_callback_test; + +JNIEXPORT +void JNICALL RecursiveSampledObjectAlloc(jvmtiEnv *jvmti_env, + JNIEnv* jni_env, + jthread thread, + jobject object, + jclass object_klass, + jlong size) { + // Basically ensure that if we were to allocate objects, we would not have an + // infinite recursion here. + int i; + for (i = 0; i < 1000; i++) { + allocate_object(jni_env); + } + + did_recursive_callback_test = 1; +} + +JNIEXPORT jboolean JNICALL +Java_MyPackage_HeapMonitorRecursiveTest_didCallback(JNIEnv* env, jclass cls) { + return did_recursive_callback_test != 0; +} + +JNIEXPORT void JNICALL +Java_MyPackage_HeapMonitorRecursiveTest_setCallbackToCallAllocateSomeMore(JNIEnv* env, jclass cls) { + jvmtiEventCallbacks callbacks; + + memset(&callbacks, 0, sizeof(callbacks)); + callbacks.SampledObjectAlloc = &RecursiveSampledObjectAlloc; + + if (check_error((*jvmti)->SetEventCallbacks(jvmti, &callbacks, + sizeof(jvmtiEventCallbacks)), + " Set Event Callbacks")) { + throw_exception(env, "Cannot reset the callback."); + return; + } +} + +#ifdef __cplusplus +} +#endif