--- old/make/test/JtregNative.gmk 2017-06-01 20:40:54.766619965 -0700 +++ new/make/test/JtregNative.gmk 2017-06-01 20:40:54.650620422 -0700 @@ -61,6 +61,7 @@ $(HOTSPOT_TOPDIR)/test/compiler/floatingpoint/ \ $(HOTSPOT_TOPDIR)/test/compiler/calls \ $(HOTSPOT_TOPDIR)/test/serviceability/jvmti/GetNamedModule \ + $(HOTSPOT_TOPDIR)/test/serviceability/jvmti/HeapMonitor \ $(HOTSPOT_TOPDIR)/test/serviceability/jvmti/AddModuleReads \ $(HOTSPOT_TOPDIR)/test/serviceability/jvmti/AddModuleExportsAndOpens \ $(HOTSPOT_TOPDIR)/test/serviceability/jvmti/AddModuleUsesAndProvides \ --- old/src/cpu/x86/vm/macroAssembler_x86.cpp 2017-06-01 20:40:55.142618488 -0700 +++ new/src/cpu/x86/vm/macroAssembler_x86.cpp 2017-06-01 20:40:55.034618912 -0700 @@ -5600,6 +5600,10 @@ subptr(top, (int32_t)ThreadLocalAllocBuffer::alignment_reserve_in_bytes()); movptr(Address(thread_reg, in_bytes(JavaThread::tlab_end_offset())), top); + // Currently, if this happens, just set back the actual end to where it was. + // We miss a chance to sample here but it is a TODO. + movptr(Address(thread_reg, in_bytes(JavaThread::tlab_actual_end_offset())), top); + if (ZeroTLAB) { // This is a fast TLAB refill, therefore the GC is not notified of it. // So compiled code must fill the new TLAB with zeroes. --- old/src/share/vm/gc/g1/g1CollectedHeap.cpp 2017-06-01 20:40:55.710616255 -0700 +++ new/src/share/vm/gc/g1/g1CollectedHeap.cpp 2017-06-01 20:40:55.590616727 -0700 @@ -75,6 +75,7 @@ #include "memory/resourceArea.hpp" #include "oops/oop.inline.hpp" #include "runtime/atomic.hpp" +#include "runtime/heapMonitoring.hpp" #include "runtime/init.hpp" #include "runtime/orderAccess.inline.hpp" #include "runtime/vmThread.hpp" @@ -4507,6 +4508,7 @@ G1STWIsAliveClosure is_alive(this); G1KeepAliveClosure keep_alive(this); + HeapMonitoring::do_weak_oops(NULL, &is_alive, &keep_alive, NULL); G1StringDedup::unlink_or_oops_do(&is_alive, &keep_alive, true, g1_policy()->phase_times()); double fixup_time_ms = (os::elapsedTime() - fixup_start) * 1000.0; --- old/src/share/vm/gc/g1/g1MarkSweep.cpp 2017-06-01 20:40:56.190614369 -0700 +++ new/src/share/vm/gc/g1/g1MarkSweep.cpp 2017-06-01 20:40:56.074614825 -0700 @@ -48,6 +48,7 @@ #include "runtime/atomic.hpp" #include "runtime/biasedLocking.hpp" #include "runtime/fprofiler.hpp" +#include "runtime/heapMonitoring.hpp" #include "runtime/synchronizer.hpp" #include "runtime/thread.hpp" #include "runtime/vmThread.hpp" @@ -250,6 +251,7 @@ // Now adjust pointers in remaining weak roots. (All of which should // have been cleared if they pointed to non-surviving objects.) JNIHandles::weak_oops_do(&GenMarkSweep::adjust_pointer_closure); + HeapMonitoring::do_weak_oops(&GenMarkSweep::adjust_pointer_closure); if (G1StringDedup::is_enabled()) { G1StringDedup::oops_do(&GenMarkSweep::adjust_pointer_closure); --- old/src/share/vm/gc/parallel/psMarkSweep.cpp 2017-06-01 20:40:56.578612844 -0700 +++ new/src/share/vm/gc/parallel/psMarkSweep.cpp 2017-06-01 20:40:56.462613300 -0700 @@ -51,6 +51,7 @@ #include "oops/oop.inline.hpp" #include "runtime/biasedLocking.hpp" #include "runtime/fprofiler.hpp" +#include "runtime/heapMonitoring.hpp" #include "runtime/safepoint.hpp" #include "runtime/vmThread.hpp" #include "services/management.hpp" @@ -610,6 +611,7 @@ // have been cleared if they pointed to non-surviving objects.) // Global (weak) JNI handles JNIHandles::weak_oops_do(adjust_pointer_closure()); + HeapMonitoring::do_weak_oops(adjust_pointer_closure()); CodeBlobToOopClosure adjust_from_blobs(adjust_pointer_closure(), CodeBlobToOopClosure::FixRelocations); CodeCache::blobs_do(&adjust_from_blobs); --- old/src/share/vm/gc/parallel/psParallelCompact.cpp 2017-06-01 20:40:56.966611319 -0700 +++ new/src/share/vm/gc/parallel/psParallelCompact.cpp 2017-06-01 20:40:56.850611775 -0700 @@ -60,6 +60,7 @@ #include "oops/oop.inline.hpp" #include "runtime/atomic.hpp" #include "runtime/fprofiler.hpp" +#include "runtime/heapMonitoring.hpp" #include "runtime/safepoint.hpp" #include "runtime/vmThread.hpp" #include "services/management.hpp" @@ -2169,6 +2170,8 @@ // Global (weak) JNI handles JNIHandles::weak_oops_do(&oop_closure); + HeapMonitoring::do_weak_oops(&oop_closure); + CodeBlobToOopClosure adjust_from_blobs(&oop_closure, CodeBlobToOopClosure::FixRelocations); CodeCache::blobs_do(&adjust_from_blobs); AOTLoader::oops_do(&oop_closure); --- old/src/share/vm/gc/shared/collectedHeap.cpp 2017-06-01 20:40:57.390609653 -0700 +++ new/src/share/vm/gc/shared/collectedHeap.cpp 2017-06-01 20:40:57.274610109 -0700 @@ -38,6 +38,7 @@ #include "memory/resourceArea.hpp" #include "oops/instanceMirrorKlass.hpp" #include "oops/oop.inline.hpp" +#include "runtime/heapMonitoring.hpp" #include "runtime/init.hpp" #include "runtime/thread.inline.hpp" #include "services/heapDumper.hpp" @@ -296,6 +297,25 @@ #endif HeapWord* CollectedHeap::allocate_from_tlab_slow(Klass* klass, Thread* thread, size_t size) { + // We can come here for three reasons: + // - We either really did fill the tlab. + // - We pretended to everyone we did and we want to sample. + // - Both of the above reasons are true at the same time. + if (thread->tlab().should_sample()) { + // The tlab could still have space after this sample. + thread->tlab().set_back_actual_end(); + + // Try to allocate again: it could work now. + HeapWord* obj = thread->tlab().allocate(size); + if (obj != NULL) { + // Object got allocated, sample it now. + HeapMonitoring::object_alloc_do_sample(thread, + reinterpret_cast(obj), + size); + thread->tlab().pick_next_sample(); + return obj; + } + } // Retain tlab and allocate object in shared space if // the amount free in the tlab is too large to discard. @@ -336,6 +356,14 @@ #endif // ASSERT } thread->tlab().fill(obj, obj + size, new_tlab_size); + + if (thread->tlab().should_sample()) { + HeapMonitoring::object_alloc_do_sample(thread, + reinterpret_cast(obj), + size); + } + // Always pick a next sample here. + thread->tlab().pick_next_sample(); return obj; } --- old/src/share/vm/gc/shared/collectedHeap.inline.hpp 2017-06-01 20:40:57.778608128 -0700 +++ new/src/share/vm/gc/shared/collectedHeap.inline.hpp 2017-06-01 20:40:57.662608584 -0700 @@ -157,6 +157,7 @@ AllocTracer::send_allocation_outside_tlab_event(klass, size * HeapWordSize); + THREAD->tlab().handle_sample(THREAD, result, size); return result; } --- old/src/share/vm/gc/shared/genCollectedHeap.cpp 2017-06-01 20:40:58.158606635 -0700 +++ new/src/share/vm/gc/shared/genCollectedHeap.cpp 2017-06-01 20:40:58.042607090 -0700 @@ -49,6 +49,7 @@ #include "runtime/fprofiler.hpp" #include "runtime/handles.hpp" #include "runtime/handles.inline.hpp" +#include "runtime/heapMonitoring.hpp" #include "runtime/java.hpp" #include "runtime/vmThread.hpp" #include "services/management.hpp" @@ -722,6 +723,7 @@ void GenCollectedHeap::gen_process_weak_roots(OopClosure* root_closure) { JNIHandles::weak_oops_do(root_closure); + HeapMonitoring::do_weak_oops(root_closure); _young_gen->ref_processor()->weak_oops_do(root_closure); _old_gen->ref_processor()->weak_oops_do(root_closure); } --- old/src/share/vm/gc/shared/referenceProcessor.cpp 2017-06-01 20:40:58.562605047 -0700 +++ new/src/share/vm/gc/shared/referenceProcessor.cpp 2017-06-01 20:40:58.438605534 -0700 @@ -37,6 +37,7 @@ #include "oops/oop.inline.hpp" #include "runtime/java.hpp" #include "runtime/jniHandles.hpp" +#include "runtime/heapMonitoring.hpp" ReferencePolicy* ReferenceProcessor::_always_clear_soft_ref_policy = NULL; ReferencePolicy* ReferenceProcessor::_default_soft_ref_policy = NULL; @@ -257,6 +258,7 @@ process_phaseJNI(is_alive, keep_alive, complete_gc); } + HeapMonitoring::do_weak_oops(task_executor, is_alive, keep_alive, complete_gc); log_debug(gc, ref)("Ref Counts: Soft: " SIZE_FORMAT " Weak: " SIZE_FORMAT " Final: " SIZE_FORMAT " Phantom: " SIZE_FORMAT, stats.soft_count(), stats.weak_count(), stats.final_count(), stats.phantom_count()); log_develop_trace(gc, ref)("JNI Weak Reference count: " SIZE_FORMAT, count_jni_refs()); --- old/src/share/vm/gc/shared/threadLocalAllocBuffer.cpp 2017-06-01 20:40:58.930603600 -0700 +++ new/src/share/vm/gc/shared/threadLocalAllocBuffer.cpp 2017-06-01 20:40:58.818604040 -0700 @@ -29,6 +29,7 @@ #include "memory/resourceArea.hpp" #include "memory/universe.inline.hpp" #include "oops/oop.inline.hpp" +#include "runtime/heapMonitoring.hpp" #include "runtime/thread.inline.hpp" #include "utilities/copy.hpp" @@ -121,6 +122,7 @@ set_top(NULL); set_pf_top(NULL); set_end(NULL); + set_actual_end(NULL); } } assert(!(retire || ZeroTLAB) || @@ -183,7 +185,9 @@ set_top(top); set_pf_top(top); set_end(end); + set_actual_end(end); invariants(); + _bytes_until_sample = 0; } void ThreadLocalAllocBuffer::initialize() { @@ -306,12 +310,50 @@ guarantee(p == top(), "end of last object must match end of space"); } +void ThreadLocalAllocBuffer::pick_next_sample() { + if (bytes_until_sample() == 0) { + HeapMonitoring::pick_next_sample(bytes_until_sample_addr()); + } + + // Finally, fix up the sampling bytes left and _end. + size_t heap_words_remaining = _end - _top; + size_t bytes_left = bytes_until_sample(); + size_t words_until_sample = bytes_left / HeapWordSize; + + if (heap_words_remaining > words_until_sample) { + set_end(_top + words_until_sample); + set_bytes_until_sample(0); + } else { + // TODO(jcbeyler): this is not exact actually since there can be left over + // space, this will need to get fixed... + bytes_left -= heap_words_remaining * HeapWordSize; + set_bytes_until_sample(bytes_left); + } +} + Thread* ThreadLocalAllocBuffer::myThread() { return (Thread*)(((char *)this) + in_bytes(start_offset()) - in_bytes(Thread::tlab_start_offset())); } +void ThreadLocalAllocBuffer::handle_sample(Thread* thread, HeapWord* result, + size_t size) { + set_bytes_until_sample(bytes_until_sample() - size); + + // Should we sample now? + if (should_sample()) { + set_back_actual_end(); + HeapMonitoring::object_alloc_do_sample(thread, + reinterpret_cast(result), + size); + pick_next_sample(); + } else { + // Update the size and end fields. + set_back_actual_end(); + pick_next_sample(); + } +} GlobalTLABStats::GlobalTLABStats() : _allocating_threads_avg(TLABAllocationWeight) { --- old/src/share/vm/gc/shared/threadLocalAllocBuffer.hpp 2017-06-01 20:40:59.314602091 -0700 +++ new/src/share/vm/gc/shared/threadLocalAllocBuffer.hpp 2017-06-01 20:40:59.210602500 -0700 @@ -45,6 +45,7 @@ HeapWord* _top; // address after last allocation HeapWord* _pf_top; // allocation prefetch watermark HeapWord* _end; // allocation end (excluding alignment_reserve) + HeapWord* _actual_end; // allocation actual_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 @@ -52,6 +53,8 @@ static size_t _max_size; // maximum size of any TLAB static int _reserve_for_allocation_prefetch; // Reserve at the end of the TLAB static unsigned _target_refills; // expected number of refills between GCs + size_t _bytes_until_sample; // bytes until sample. + size_t _extra_space; // extra space due to sampling logic. unsigned _number_of_refills; unsigned _fast_refill_waste; @@ -66,10 +69,12 @@ void set_start(HeapWord* start) { _start = start; } void set_end(HeapWord* end) { _end = end; } + void set_actual_end(HeapWord* actual_end) { _actual_end = actual_end; } 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; } void set_refill_waste_limit(size_t waste) { _refill_waste_limit = waste; } + void set_bytes_until_sample(size_t bytes) { _bytes_until_sample = bytes; } size_t initial_refill_waste_limit() { return desired_size() / TLABRefillWasteFraction; } @@ -115,13 +120,14 @@ HeapWord* start() const { return _start; } HeapWord* end() const { return _end; } - HeapWord* hard_end() const { return _end + alignment_reserve(); } + HeapWord* hard_end() const { return _actual_end + alignment_reserve(); } HeapWord* top() const { return _top; } HeapWord* pf_top() const { return _pf_top; } size_t desired_size() const { return _desired_size; } size_t used() const { return pointer_delta(top(), start()); } size_t used_bytes() const { return pointer_delta(top(), start(), 1); } 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; } @@ -159,14 +165,22 @@ // Resize tlabs for all threads static void resize_all_tlabs(); + void set_back_actual_end() { _end = _actual_end; } void fill(HeapWord* start, HeapWord* top, size_t new_size); void initialize(); + void pick_next_sample(); + void handle_sample(Thread* thread, HeapWord* result, size_t size); + size_t bytes_until_sample() { return _bytes_until_sample; } + size_t *bytes_until_sample_addr() { return &_bytes_until_sample; } + bool should_sample() { return bytes_until_sample() == 0; } + static size_t refill_waste_limit_increment() { return TLABWasteIncrement; } // Code generation support static ByteSize start_offset() { return byte_offset_of(ThreadLocalAllocBuffer, _start); } static ByteSize end_offset() { return byte_offset_of(ThreadLocalAllocBuffer, _end ); } + static ByteSize actual_end_offset() { return byte_offset_of(ThreadLocalAllocBuffer, _actual_end ); } static ByteSize top_offset() { return byte_offset_of(ThreadLocalAllocBuffer, _top ); } static ByteSize pf_top_offset() { return byte_offset_of(ThreadLocalAllocBuffer, _pf_top ); } static ByteSize size_offset() { return byte_offset_of(ThreadLocalAllocBuffer, _desired_size ); } --- old/src/share/vm/prims/jvmti.xml 2017-06-01 20:40:59.698600582 -0700 +++ new/src/share/vm/prims/jvmti.xml 2017-06-01 20:40:59.586601022 -0700 @@ -11528,6 +11528,179 @@ + + + + + BCI for the given allocation. + + + + Method ID for the given frame. + + + + + + JNIEnv + Environment where the trace was recorded. + + + + jvmtiCallFrame + + Pointer to the call frames. + + + + The number of frames for the trace. + + + + The size of the object allocation. + + + + The thread id number. + + + + + + + jvmtiStackTrace + + + The array with the various stack traces. + + + + + + + Number of traces pointed by the array . + + + + + + Start Heap Sampling + + Start the heap sampler in the JVM. The function provides, via its argument, the sampling + rate requested and will fill internal data structures with heap allocation samples. The + samples are obtained via the , + , , + functions. + + new + + + + + + + The monitoring rate used for sampling. The sampler will use a statistical approach to + provide in average sampling every allocated bytes. + + + + + + The maximum storage used for the sampler. By default, the value is 200. + + + + + + is less than zero. + + + + + + Get Live Traces + + Get Live Heap Sampled traces. The fields of the + structure are filled in with details of the specified sampled allocation. + + new + + + + + jvmtiStackTraces + + The stack trace data structure to be filled. + + + + + + + + + Get Garbage Traces + + Get the recent garbage heap sampled traces. The fields of the + structure are filled in with details of the specified sampled allocation. + + new + + + + + jvmtiStackTraces + + The stack trace data structure to be filled. + + + + + + + + + Get Frequent Garbage Traces + + Get the frequent garbage heap sampled traces. The fields of the + structure are filled in with details of the specified sampled allocation. + + new + + + + + jvmtiStackTraces + + The stack trace data structure to be filled. + + + + + + + + + Release traces provided by the heap monitoring + + Release traces provided by any of the trace retrieval methods. + + new + + + + + jvmtiStackTraces + + The stack trace data structure to be released. + + + + + + + + --- old/src/share/vm/prims/jvmtiEnv.cpp 2017-06-01 20:41:00.366597957 -0700 +++ new/src/share/vm/prims/jvmtiEnv.cpp 2017-06-01 20:41:00.250598412 -0700 @@ -46,6 +46,7 @@ #include "prims/jvmtiCodeBlobEvents.hpp" #include "prims/jvmtiExtensions.hpp" #include "prims/jvmtiGetLoadedClasses.hpp" +#include "prims/jvmtiHeapTransition.hpp" #include "prims/jvmtiImpl.hpp" #include "prims/jvmtiManageCapabilities.hpp" #include "prims/jvmtiRawMonitor.hpp" @@ -55,6 +56,7 @@ #include "prims/jvmtiUtil.hpp" #include "runtime/arguments.hpp" #include "runtime/deoptimization.hpp" +#include "runtime/heapMonitoring.hpp" #include "runtime/interfaceSupport.hpp" #include "runtime/javaCalls.hpp" #include "runtime/jfieldIDWorkaround.hpp" @@ -1947,6 +1949,63 @@ return JVMTI_ERROR_NONE; } /* end IterateOverInstancesOfClass */ +// Start the sampler. +jvmtiError +JvmtiEnv::StartHeapSampling(jint monitoring_rate, jint max_storage) { + if (monitoring_rate < 0) { + return JVMTI_ERROR_ILLEGAL_ARGUMENT; + } + + HeapThreadTransition htt(Thread::current()); + HeapMonitoring::initialize_profiling(monitoring_rate, max_storage); + return JVMTI_ERROR_NONE; +} /* end StartHeapSampling */ + +// Get the currently live sampled allocations. +jvmtiError +JvmtiEnv::GetLiveTraces(jvmtiStackTraces *stack_traces) { + HeapThreadTransition htt(Thread::current()); + if (stack_traces == NULL) { + return JVMTI_ERROR_ILLEGAL_ARGUMENT; + } + + HeapMonitoring::get_live_traces(stack_traces); + return JVMTI_ERROR_NONE; +} /* end GetLiveTraces */ + +// Get the currently live sampled allocations. +jvmtiError +JvmtiEnv::GetGarbageTraces(jvmtiStackTraces *stack_traces) { + HeapThreadTransition htt(Thread::current()); + if (stack_traces == NULL) { + return JVMTI_ERROR_ILLEGAL_ARGUMENT; + } + + HeapMonitoring::get_garbage_traces(stack_traces); + return JVMTI_ERROR_NONE; +} /* end GetGarbageTraces */ + +// Get the currently live sampled allocations. +jvmtiError +JvmtiEnv::GetFrequentGarbageTraces(jvmtiStackTraces *stack_traces) { + HeapThreadTransition htt(Thread::current()); + if (stack_traces == NULL) { + return JVMTI_ERROR_ILLEGAL_ARGUMENT; + } + + HeapMonitoring::get_frequent_garbage_traces(stack_traces); + return JVMTI_ERROR_NONE; +} /* end GetFrequentGarbageTraces */ + +// Release sampled traces. +jvmtiError +JvmtiEnv::ReleaseTraces(jvmtiStackTraces *stack_traces) { + if (stack_traces == NULL) { + return JVMTI_ERROR_NONE; + } + HeapMonitoring::release_traces(stack_traces); + return JVMTI_ERROR_NONE; +} /* end ReleaseTraces */ // // Local Variable functions --- old/src/share/vm/runtime/thread.hpp 2017-06-01 20:41:00.794596275 -0700 +++ new/src/share/vm/runtime/thread.hpp 2017-06-01 20:41:00.694596668 -0700 @@ -615,6 +615,7 @@ TLAB_FIELD_OFFSET(start) TLAB_FIELD_OFFSET(end) + TLAB_FIELD_OFFSET(actual_end) TLAB_FIELD_OFFSET(top) TLAB_FIELD_OFFSET(pf_top) TLAB_FIELD_OFFSET(size) // desired_size --- /dev/null 2017-05-01 09:42:45.355096588 -0700 +++ new/src/share/vm/prims/jvmtiHeapTransition.hpp 2017-06-01 20:41:01.082595143 -0700 @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2017, 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 SHARE_VM_PRIMS_JVMTIHEAPSAMPLING_HPP +#define SHARE_VM_PRIMS_JVMTIHEAPSAMPLING_HPP + +// A RAII class that handles transitions from the agent into the VM. +class HeapThreadTransition : StackObj { + private: + JavaThreadState _saved_state; + JavaThread *_jthread; + + public: + // Transitions this thread from the agent (thread_in_native) to the VM. + HeapThreadTransition(Thread *thread) { + if (thread->is_Java_thread()) { + _jthread = static_cast(thread); + _saved_state = _jthread->thread_state(); + if (_saved_state == _thread_in_native) { + ThreadStateTransition::transition_from_native(_jthread, _thread_in_vm); + } else { + ThreadStateTransition::transition(_jthread, + _saved_state, + _thread_in_vm); + } + } else { + _jthread = NULL; + _saved_state = _thread_new; + } + } + + // Transitions this thread back to the agent from the VM. + ~HeapThreadTransition() { + if (_jthread != NULL) { + ThreadStateTransition::transition(_jthread, _thread_in_vm, _saved_state); + } + } +}; + +#endif // SHARE_VM_PRIMS_JVMTIHEAPSAMPLING_HPP --- /dev/null 2017-05-01 09:42:45.355096588 -0700 +++ new/src/share/vm/runtime/heapMonitoring.cpp 2017-06-01 20:41:01.430593775 -0700 @@ -0,0 +1,722 @@ +/* + * Copyright (c) 2017, 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 "prims/forte.hpp" +#include "runtime/heapMonitoring.hpp" + +// Keep muxlock for now +// Now that ASGCT is gone, a bit of refactoring in the addtrace... and +// StackTraceData + +const int kMaxStackDepth = 64; + +// Internal data structure representing traces. +struct StackTraceData : CHeapObj { + jvmtiStackTrace *trace; + oop obj; + int references; + + StackTraceData(jvmtiStackTrace *t, oop o) : trace(t), obj(o), references(0) {} + + StackTraceData() : trace(NULL), obj(NULL), references(0) {} + + // StackTraceDatas are shared around the board between various lists. So + // handle this by hand instead of having this in the destructor. There are + // cases where the struct is on the stack but holding heap data not to be + // freed. + static void FreeData(StackTraceData *data) { + if (data->trace != NULL) { + FREE_C_HEAP_ARRAY(jvmtiCallFrame, data->trace->frames); + FREE_C_HEAP_OBJ(data->trace); + } + delete data; + } +}; + +// RAII class that acquires / releases lock +class MuxLocker : StackObj { + private: + volatile intptr_t *_lock; + const char *_name; + public: + MuxLocker(volatile intptr_t *lock, const char *name) : + _lock(lock), + _name(name) { + Thread::muxAcquire(lock, name); + } + ~MuxLocker() { + Thread::muxRelease(_lock); + } +}; + +// Fixed size buffer for holding garbage traces. +class GarbageTracesBuffer : public CHeapObj { + public: + GarbageTracesBuffer(uint32_t size) : _size(size) { + _garbage_traces = NEW_C_HEAP_ARRAY(StackTraceData*, + size, + mtInternal); + memset(_garbage_traces, 0, sizeof(StackTraceData*) * size); + } + + virtual ~GarbageTracesBuffer() { + FREE_C_HEAP_ARRAY(StackTraceData*, _garbage_traces); + } + + StackTraceData** get_traces() const { + return _garbage_traces; + } + + bool store_trace(StackTraceData *trace) { + uint32_t index; + if (!select_replacement(&index)) { + return false; + } + + StackTraceData *old_data = _garbage_traces[index]; + + if (old_data != NULL) { + old_data->references--; + + if (old_data->references == 0) { + StackTraceData::FreeData(old_data); + } + } + + trace->references++; + _garbage_traces[index] = trace; + return true; + } + + uint32_t size() const { + return _size; + } + + protected: + // Subclasses select the trace to replace. Returns false if no replacement + // is to happen, otherwise stores the index of the trace to replace in + // *index. + virtual bool select_replacement(uint32_t *index) = 0; + + const uint32_t _size; + + private: + // The current garbage traces. A fixed-size ring buffer. + StackTraceData **_garbage_traces; +}; + +// Keep statistical sample of traces over the lifetime of the server. +// When the buffer is full, replace a random entry with probability +// 1/samples_seen. This strategy tends towards preserving the most frequently +// occuring traces over time. +class FrequentGarbageTraces : public GarbageTracesBuffer { + public: + FrequentGarbageTraces(int size) + : GarbageTracesBuffer(size), + _garbage_traces_pos(0), + _samples_seen(0) { + } + + virtual ~FrequentGarbageTraces() { + } + + virtual bool select_replacement(uint32_t* index) { + ++_samples_seen; + + if (_garbage_traces_pos < _size) { + *index = _garbage_traces_pos++; + return true; + } + + uint64_t random_uint64 = + (static_cast(::random()) << 32) + | ::random(); + + uint32_t random_index = random_uint64 % _samples_seen; + if (random_index < _size) { + *index = random_index; + return true; + } + + return false; + } + + private: + // The current position in the buffer as we initially fill it. + uint32_t _garbage_traces_pos; + + uint64_t _samples_seen; +}; + +// Store most recent garbage traces. +class MostRecentGarbageTraces : public GarbageTracesBuffer { + public: + MostRecentGarbageTraces(int size) + : GarbageTracesBuffer(size), + _garbage_traces_pos(0) { + } + + virtual ~MostRecentGarbageTraces() { + } + + virtual bool select_replacement(uint32_t* index) { + *index = _garbage_traces_pos; + + _garbage_traces_pos = + (_garbage_traces_pos + 1) % _size; + + return true; + } + + private: + // The current position in the buffer. + uint32_t _garbage_traces_pos; +}; + +// Each object that we profile is stored as trace with the thread_id. +class StackTraceStorage { + public: + // The function that gets called to add a trace to the list of + // traces we are maintaining. + void add_trace(jvmtiStackTrace *trace, oop o); + + // The function that gets called by the client to retrieve the list + // of stack traces. Passes a jvmtiStackTraces which will get mutated. + void get_all_stack_traces(jvmtiStackTraces *traces); + + // The function that gets called by the client to retrieve the list + // of stack traces. Passes a jvmtiStackTraces which will get mutated. + void get_garbage_stack_traces(jvmtiStackTraces *traces); + + // The function that gets called by the client to retrieve the list + // of stack traces. Passes a jvmtiStackTraces which will get mutated. + void get_frequent_garbage_stack_traces(jvmtiStackTraces *traces); + + // Executes whenever weak references are traversed. is_alive tells + // you if the given oop is still reachable and live. + void do_weak_oops(BoolObjectClosure* is_alive, + OopClosure *f, + VoidClosure* complete_gc); + + ~StackTraceStorage(); + StackTraceStorage(); + + // The global storage. Not a global static because + // StackTraceStorage isn't available at module-loading time. + static StackTraceStorage* storage() { + static StackTraceStorage storage; + return &storage; + } + + bool IsInitialized() { + return _initialized; + } + + // Static method to set the storage in place at initialization. + static void InitializeStackTraceStorage(int max_storage) { + StackTraceStorage *storage = StackTraceStorage::storage(); + storage->InitializeStorage(max_storage); + } + + bool initialized() { return _initialized; } + volatile bool *initialized_address() { return &_initialized; } + + private: + // Protects the traces currently sampled (below). + volatile intptr_t _allocated_traces_lock[1]; + + // The traces currently sampled. + GrowableArray *_allocated_traces; + + // Recent garbage traces. + MostRecentGarbageTraces *_recent_garbage_traces; + + // Frequent garbage traces. + FrequentGarbageTraces *_frequent_garbage_traces; + + // Maximum size of the allocation. + size_t _allocated_traces_size; + + // Maximum amount of storage provided by the JVMTI call initialize_profiling. + int _max_storage; + + volatile bool _initialized; + + // Support functions and classes for copying data to the external + // world. + class StackTraceDataCopier { + public: + virtual int size() const = 0; + virtual const StackTraceData *get(uint32_t i) const = 0; + }; + + class LiveStackTraceDataCopier : public StackTraceDataCopier { + public: + LiveStackTraceDataCopier(GrowableArray *data) : + _data(data) {} + int size() const { return _data->length(); } + const StackTraceData *get(uint32_t i) const { return _data->adr_at(i); } + + private: + GrowableArray *_data; + }; + + class GarbageStackTraceDataCopier : public StackTraceDataCopier { + public: + GarbageStackTraceDataCopier(StackTraceData **data, int size) : + _data(data), _size(size) {} + int size() const { return _size; } + const StackTraceData *get(uint32_t i) const { return _data[i]; } + + private: + StackTraceData **_data; + int _size; + }; + + // Instance initialization. + void InitializeStorage(int max_storage); + + // Copies from StackTraceData to jvmtiStackTrace. + bool deep_copy(jvmtiStackTrace *to, const StackTraceData *from); + + // Creates a deep copy of the list of StackTraceData. + void copy_stack_traces(const StackTraceDataCopier &copier, + jvmtiStackTraces *traces); + + void store_garbage_trace(const StackTraceData &trace); + + void FreeGarbage(); + +}; + +// Statics for Sampler +double HeapMonitoring::_log_table[1 << kFastlogNumBits]; + +jint HeapMonitoring::_monitoring_rate; + +// Cheap random number generator +uint64_t HeapMonitoring::_rnd; + +StackTraceStorage::StackTraceStorage() : + _allocated_traces(NULL), + _recent_garbage_traces(NULL), + _frequent_garbage_traces(NULL), + _max_storage(0), + _initialized(false) { + _allocated_traces_lock[0] = 0; +} + +void StackTraceStorage::FreeGarbage() { + StackTraceData **recent_garbage = NULL; + uint32_t recent_size = 0; + + StackTraceData **frequent_garbage = NULL; + uint32_t frequent_size = 0; + + if (_recent_garbage_traces != NULL) { + recent_garbage = _recent_garbage_traces->get_traces(); + recent_size = _recent_garbage_traces->size(); + } + + if (_frequent_garbage_traces != NULL) { + frequent_garbage = _frequent_garbage_traces->get_traces(); + frequent_size = _frequent_garbage_traces->size(); + } + + // Simple solution since this happens at exit. + // Go through the recent and remove any that only are referenced there. + for (uint32_t i = 0; i < recent_size; i++) { + StackTraceData *trace = recent_garbage[i]; + if (trace != NULL) { + trace->references--; + + if (trace->references == 0) { + StackTraceData::FreeData(trace); + } + } + } + + // Then go through the frequent and remove those that are now only there. + for (uint32_t i = 0; i < frequent_size; i++) { + StackTraceData *trace = frequent_garbage[i]; + if (trace != NULL) { + trace->references--; + + if (trace->references == 0) { + StackTraceData::FreeData(trace); + } + } + } +} + +StackTraceStorage::~StackTraceStorage() { + MuxLocker mu(_allocated_traces_lock, "StackTraceStorage::Destructor"); + delete _allocated_traces; + + FreeGarbage(); + delete _recent_garbage_traces; + delete _frequent_garbage_traces; + _initialized = false; +} + +void StackTraceStorage::InitializeStorage(int max_storage) { + MuxLocker mu(_allocated_traces_lock, "StackTraceStorage::InitializeStorage"); + + // In case multiple threads got locked and then 1 by 1 got through. + if (_initialized) { + return; + } + + _allocated_traces = new (ResourceObj::C_HEAP, mtInternal) + GrowableArray(128, true); + + _recent_garbage_traces = new MostRecentGarbageTraces(max_storage); + _frequent_garbage_traces = new FrequentGarbageTraces(max_storage); + + _max_storage = max_storage; + _initialized = true; +} + +void StackTraceStorage::add_trace(jvmtiStackTrace *trace, oop o) { + StackTraceData new_data(trace, o); + + MuxLocker mu(_allocated_traces_lock, "StackTraceStorage::add_trace"); + _allocated_traces->append(new_data); +} + +void StackTraceStorage::do_weak_oops(BoolObjectClosure *is_alive, + OopClosure *f, + VoidClosure *complete_gc) { + MuxLocker mu(_allocated_traces_lock, "StackTraceStorage::do_weak_oops"); + + if (IsInitialized()) { + int len = _allocated_traces->length(); + + // Compact the oop traces. Moves the live oops to the beginning of the + // growable array, potentially overwriting the dead ones. + int curr_pos = 0; + for (int i = 0; i < len; i++) { + StackTraceData &trace = _allocated_traces->at(i); + oop value = trace.obj; + if ((value != NULL && Universe::heap()->is_in_reserved(value)) && + (is_alive == NULL || is_alive->do_object_b(value))) { + // Update the oop to point to the new object if it is still alive. + f->do_oop(&(trace.obj)); + + // Copy the old trace, if it is still live. + _allocated_traces->at_put(curr_pos++, trace); + } else { + // If the old trace is no longer live, add it to the list of + // recently collected garbage. + store_garbage_trace(trace); + } + } + + // Zero out remaining array elements. Even though the call to trunc_to + // below truncates these values, zeroing them out is good practice. + StackTraceData zero_trace; + for (int i = curr_pos; i < len; i++) { + _allocated_traces->at_put(i, zero_trace); + } + + // Set the array's length to the number of live elements. + _allocated_traces->trunc_to(curr_pos); + if (complete_gc != NULL) { + complete_gc->do_void(); + } + } +} + +bool StackTraceStorage::deep_copy(jvmtiStackTrace *to, + const StackTraceData *from) { + const jvmtiStackTrace *src = from->trace; + *to = *src; + + to->frames = + NEW_C_HEAP_ARRAY(jvmtiCallFrame, kMaxStackDepth, mtInternal); + + if (to->frames == NULL) { + return false; + } + + memcpy(to->frames, + src->frames, + sizeof(jvmtiCallFrame) * kMaxStackDepth); + return true; +} + +// Called by the outside world; returns a copy of the stack traces +// (because we could be replacing them as the user handles them). +// The array is secretly null-terminated (to make it easier to reclaim). +void StackTraceStorage::get_all_stack_traces(jvmtiStackTraces *traces) { + LiveStackTraceDataCopier copier(_allocated_traces); + copy_stack_traces(copier, traces); +} + +// See comment on get_all_stack_traces +void StackTraceStorage::get_garbage_stack_traces(jvmtiStackTraces *traces) { + GarbageStackTraceDataCopier copier(_recent_garbage_traces->get_traces(), + _recent_garbage_traces->size()); + copy_stack_traces(copier, traces); +} + +// See comment on get_all_stack_traces +void StackTraceStorage::get_frequent_garbage_stack_traces( + jvmtiStackTraces *traces) { + GarbageStackTraceDataCopier copier(_frequent_garbage_traces->get_traces(), + _frequent_garbage_traces->size()); + copy_stack_traces(copier, traces); +} + + +void StackTraceStorage::copy_stack_traces(const StackTraceDataCopier &copier, + jvmtiStackTraces *traces) { + MuxLocker mu(_allocated_traces_lock, "StackTraceStorage::copy_stack_traces"); + int len = copier.size(); + + // Create a new array to store the StackTraceData objects. + // + 1 for a NULL at the end. + jvmtiStackTrace *t = + NEW_C_HEAP_ARRAY(jvmtiStackTrace, len + 1, mtInternal); + if (t == NULL) { + traces->stack_traces = NULL; + traces->trace_count = 0; + return; + } + // +1 to have a NULL at the end of the array. + memset(t, 0, (len + 1) * sizeof(*t)); + + // Copy the StackTraceData objects into the new array. + int trace_count = 0; + for (int i = 0; i < len; i++) { + const StackTraceData *stack_trace = copier.get(i); + if (stack_trace != NULL) { + jvmtiStackTrace *to = &t[trace_count]; + if (!deep_copy(to, stack_trace)) { + continue; + } + trace_count++; + } + } + + traces->stack_traces = t; + traces->trace_count = trace_count; +} + +void StackTraceStorage::store_garbage_trace(const StackTraceData &trace) { + StackTraceData *new_trace = new StackTraceData(); + *new_trace = trace; + + bool accepted = _recent_garbage_traces->store_trace(new_trace); + + // Accepted is on the right of the boolean to force the store_trace to happen. + accepted = _frequent_garbage_traces->store_trace(new_trace) || accepted; + + if (!accepted) { + // No one wanted to use it. + delete new_trace; + } +} + +// Delegate the initialization question to the underlying storage system. +bool HeapMonitoring::initialized() { + return StackTraceStorage::storage()->initialized(); +} + +// Delegate the initialization question to the underlying storage system. +bool *HeapMonitoring::initialized_address() { + return + const_cast(StackTraceStorage::storage()->initialized_address()); +} + +void HeapMonitoring::get_live_traces(jvmtiStackTraces *traces) { + StackTraceStorage::storage()->get_all_stack_traces(traces); +} + +void HeapMonitoring::get_frequent_garbage_traces(jvmtiStackTraces *traces) { + StackTraceStorage::storage()->get_frequent_garbage_stack_traces(traces); +} + +void HeapMonitoring::get_garbage_traces(jvmtiStackTraces *traces) { + StackTraceStorage::storage()->get_garbage_stack_traces(traces); +} + +void HeapMonitoring::release_traces(jvmtiStackTraces *trace_info) { + jint trace_count = trace_info->trace_count; + jvmtiStackTrace *traces = trace_info->stack_traces; + + for (jint i = 0; i < trace_count; i++) { + jvmtiStackTrace *current_trace = traces + i; + FREE_C_HEAP_ARRAY(jvmtiCallFrame, current_trace->frames); + } + + FREE_C_HEAP_ARRAY(jvmtiStackTrace, trace_info->stack_traces); + trace_info->trace_count = 0; + trace_info->stack_traces = NULL; +} + +// Invoked by the GC to clean up old stack traces and remove old arrays +// of instrumentation that are still lying around. +void HeapMonitoring::do_weak_oops( + AbstractRefProcTaskExecutor *task_executor, + BoolObjectClosure* is_alive, + OopClosure *f, + VoidClosure *complete_gc) { + if (task_executor != NULL) { + task_executor->set_single_threaded_mode(); + } + StackTraceStorage::storage()->do_weak_oops(is_alive, f, complete_gc); +} + +void HeapMonitoring::initialize_profiling(jint monitoring_rate, jint max_storage) { + _monitoring_rate = monitoring_rate; + + StackTraceStorage::InitializeStackTraceStorage(max_storage); + + // Populate the lookup table for fast_log2. + // This approximates the log2 curve with a step function. + // Steps have height equal to log2 of the mid-point of the step. + for (int i = 0; i < (1 << kFastlogNumBits); i++) { + double half_way = static_cast(i + 0.5); + _log_table[i] = (log(1.0 + half_way / (1 << kFastlogNumBits)) / log(2.0)); + } + + JavaThread *t = static_cast(Thread::current()); + _rnd = static_cast(reinterpret_cast(t)); + if (_rnd == 0) { + _rnd = 1; + } + for (int i = 0; i < 20; i++) { + _rnd = next_random(_rnd); + } +} + +// 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 HeapMonitoring::pick_next_sample(size_t *ptr) { + _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 prng_mod_power = 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 >> (prng_mod_power - 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); + *ptr = static_cast( + (0.0 < log_val ? 0.0 : log_val) * (-log(2.0) * (_monitoring_rate)) + 1); +} + +// Called from the interpreter and C1 +void HeapMonitoring::object_alloc_unsized(oopDesc* o) { + JavaThread *thread = static_cast(Thread::current()); + assert(o->size() << LogHeapWordSize == static_cast(byte_size), + "Object size is incorrect."); + object_alloc_do_sample(thread, o, o->size() << LogHeapWordSize); +} + +void HeapMonitoring::object_alloc(oopDesc* o, intx byte_size) { + JavaThread *thread = static_cast(Thread::current()); + object_alloc_do_sample(thread, o, byte_size); +} + +// Called directly by C2 +void HeapMonitoring::object_alloc_do_sample(Thread *t, oopDesc *o, intx byte_size) { +#if defined(X86) || defined(PPC) + JavaThread *thread = static_cast(t); + if (StackTraceStorage::storage()->IsInitialized()) { + assert(t->is_Java_thread(), "non-Java thread passed to do_sample"); + JavaThread *thread = static_cast(t); + + jvmtiStackTrace *trace = NEW_C_HEAP_OBJ(jvmtiStackTrace, mtInternal); + if (trace == NULL) { + return; + } + + jvmtiCallFrame *frames = + NEW_C_HEAP_ARRAY(jvmtiCallFrame, kMaxStackDepth, mtInternal); + + if (frames == NULL) { + FREE_C_HEAP_OBJ(trace); + return; + } + + trace->frames = frames; + trace->env_id = (JavaThread::current())->jni_environment(); + trace->thread_id = SharedRuntime::get_java_tid(thread); + trace->size = byte_size; + trace->frame_count = 0; + + if (thread->has_last_Java_frame()) { // just to be safe + vframeStream vfst(thread, true); + int count = 0; + while (!vfst.at_end() && count < kMaxStackDepth) { + Method* m = vfst.method(); + frames[count].bci = vfst.bci(); + frames[count].method_id = m->jmethod_id(); + count++; + + vfst.next(); + } + trace->frame_count = count; + } + + if (trace->frame_count> 0) { + // Success! + StackTraceStorage::storage()->add_trace(trace, o); + return; + } + + // Failure! + FREE_C_HEAP_ARRAY(jvmtiCallFrame, trace->frames); + FREE_C_HEAP_OBJ(trace); + return; + } else { + // There is something like 64K worth of allocation before the VM + // initializes. This is just in the interests of not slowing down + // startup. + assert(t->is_Java_thread(), "non-Java thread passed to do_sample"); + } +#else + Unimplemented(); +#endif +} --- /dev/null 2017-05-01 09:42:45.355096588 -0700 +++ new/src/share/vm/runtime/heapMonitoring.hpp 2017-06-01 20:41:01.786592377 -0700 @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2017, 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 SHARE_VM_RUNTIME_HEAPMONITORING_HPP +#define SHARE_VM_RUNTIME_HEAPMONITORING_HPP + +#include "gc/shared/referenceProcessor.hpp" +#include "runtime/sharedRuntime.hpp" + +// Support class for sampling heap allocations across the VM. +class HeapMonitoring : AllStatic { + private: + // Cheap random number generator + static uint64_t _rnd; + static bool _initialized; + static jint _monitoring_rate; + + // Statics for the fast log + static const int kFastlogNumBits = 10; + static const int kFastlogMask = (1 << kFastlogNumBits) - 1; + static double _log_table[1<(0)) << prng_mod_power); + return (prng_mult * rnd + prng_add) & prng_mod_mask; + } + + // Adapted from //util/math/fastmath.[h|cc] by Noam Shazeer + // This mimics the VeryFastLog2 code in those files + static inline double fast_log2(const double & d) { + assert(d>0, "bad value passed to assert"); + uint64_t x = 0; + memcpy(&x, &d, sizeof(uint64_t)); + const uint32_t x_high = x >> 32; + const uint32_t y = x_high >> (20 - kFastlogNumBits) & kFastlogMask; + const int32_t exponent = ((x_high >> 20) & 0x7FF) - 1023; + return exponent + _log_table[y]; + } + + public: + static void pick_next_sample(size_t *ptr); + + static void get_live_traces(jvmtiStackTraces* stack_traces); + static void get_garbage_traces(jvmtiStackTraces* stack_traces); + static void get_frequent_garbage_traces(jvmtiStackTraces* stack_traces); + static void release_traces(jvmtiStackTraces *trace_info); + static void initialize_profiling(jint monitoring_rate, jint max_storage); + static bool initialized(); + static bool *initialized_address(); + + // Called when o is allocated, called by interpreter and C1. + static void object_alloc_unsized(oopDesc* o); + static void object_alloc(oopDesc* o, intx byte_size); + + // Called when o is allocated from C2 directly, + // we know the thread, and we have done the sampling. + static void object_alloc_do_sample(Thread *t, oopDesc *o, intx size_in_bytes); + + // Called to clean up oops that have been saved by our sampling function, + // but which no longer have other references in the heap. + static void do_weak_oops(AbstractRefProcTaskExecutor *task_executor, + BoolObjectClosure* is_alive, + OopClosure *f, + VoidClosure *complete_gc); + static void do_weak_oops(OopClosure* oop_closure) { + do_weak_oops(NULL, NULL, oop_closure, NULL); + } +}; + +#endif // SHARE_VM_RUNTIME_HEAPMONITORING_HPP --- /dev/null 2017-05-01 09:42:45.355096588 -0700 +++ new/test/serviceability/jvmti/HeapMonitor/MyPackage/HeapMonitorTest.java 2017-06-01 20:41:02.146590961 -0700 @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2017, 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 API + * @compile HeapMonitorTest.java + * @run main/othervm/native -agentlib:HeapMonitor MyPackage.HeapMonitorTest + */ + +import java.io.PrintStream; + +public class HeapMonitorTest { + + static { + try { + System.loadLibrary("HeapMonitor"); + } catch (UnsatisfiedLinkError ule) { + System.err.println("Could not load HeapMonitor library"); + System.err.println("java.library.path: " + + System.getProperty("java.library.path")); + throw ule; + } + } + + native static int check(); + + public static int cnt; + public static int g_tmp[]; + public int array[]; + + public static 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 tmp[] = new int[1]; + // Force it to be kept. + g_tmp = tmp; + sum += g_tmp[0]; + } + return sum; + } + + public static void main(String[] args) { + int sum = 0; + for (int j = 0; j < 1000; j++) { + sum += helper(); + } + System.out.println(sum); + + int status = check(); + if (status != 0) { + throw new RuntimeException("Non-zero status returned from the agent: " + status); + } + } +} --- /dev/null 2017-05-01 09:42:45.355096588 -0700 +++ new/test/serviceability/jvmti/HeapMonitor/libHeapMonitor.c 2017-06-01 20:41:02.466589704 -0700 @@ -0,0 +1,323 @@ +/* + * Copyright (c) 2016, 2017, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#include +#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 PASSED 0 +#define FAILED 2 + +#define MAX_TRACES 400 + +static const char *EXC_CNAME = "java/lang/Exception"; +static jvmtiEnv *jvmti = NULL; + +static +jint throw_exc(JNIEnv *env, char *msg) { + jclass exc_class = JNI_ENV_PTR(env)->FindClass(JNI_ENV_ARG(env, EXC_CNAME)); + + if (exc_class == NULL) { + printf("throw_exc: 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; +} + +JNIEXPORT void JNICALL OnVMInit(jvmtiEnv *jvmti, JNIEnv *jni_env, jthread thread) { + (*jvmti)->StartHeapSampling(jvmti, 1<<19, MAX_TRACES); +} + +JNIEXPORT void JNICALL OnClassLoad(jvmtiEnv *jvmti_env, JNIEnv *jni_env, + jthread thread, jclass klass) { + // NOP. +} + +JNIEXPORT void JNICALL OnClassPrepare(jvmtiEnv *jvmti_env, JNIEnv *jni_env, + jthread thread, jclass klass) { + // We need to do this to "prime the pump", as it were -- make sure + // that all of the methodIDs have been initialized internally, for + // AsyncGetCallTrace. + jint method_count; + jmethodID *methods = 0; + jvmtiError err = (*jvmti)->GetClassMethods(jvmti, klass, &method_count, &methods); + if ((err != JVMTI_ERROR_NONE) && (err != JVMTI_ERROR_CLASS_NOT_PREPARED)) { + // JVMTI_ERROR_CLASS_NOT_PREPARED is okay because some classes may + // be loaded but not prepared at this point. + throw_exc(jni_env, "Failed to create method IDs for methods in class\n"); + } +} + +static +jint Agent_Initialize(JavaVM *jvm, char *options, void *reserved) { + jint res; + + res = JNI_ENV_PTR(jvm)->GetEnv(JNI_ENV_ARG(jvm, (void **) &jvmti), + JVMTI_VERSION_9); + if (res != JNI_OK || jvmti == NULL) { + printf(" Error: wrong result of a valid call to GetEnv!\n"); + return JNI_ERR; + } + + jvmtiEventCallbacks callbacks; + memset(&callbacks, 0, sizeof(callbacks)); + + callbacks.VMInit = &OnVMInit; + callbacks.ClassLoad = &OnClassLoad; + callbacks.ClassPrepare = &OnClassPrepare; + + jvmtiCapabilities caps; + memset(&caps, 0, sizeof(caps)); + // Get line numbers and filename for the test. + caps.can_get_line_numbers = 1; + caps.can_get_source_file_name = 1; + int ernum = (*jvmti)->AddCapabilities(jvmti, &caps); + + ernum = (*jvmti)->SetEventCallbacks(jvmti, &callbacks, sizeof(jvmtiEventCallbacks)); + ernum = (*jvmti)->SetEventNotificationMode(jvmti, JVMTI_ENABLE, JVMTI_EVENT_VM_INIT, NULL); + ernum = (*jvmti)->SetEventNotificationMode(jvmti, JVMTI_ENABLE, JVMTI_EVENT_CLASS_LOAD, NULL); + ernum = (*jvmti)->SetEventNotificationMode(jvmti, JVMTI_ENABLE, JVMTI_EVENT_CLASS_PREPARE, NULL); + + return JNI_OK; +} + +// Given a method and a location, this method gets the line number. +// Kind of expensive, comparatively. +jint GetLineNumber(jvmtiEnv *jvmti, jmethodID method, jlocation location) { + // The location is -1 if the bci isn't known or -3 for a native method. + if (location == -1 || location == -3) { + return -1; + } + + // Read the line number table. + jvmtiLineNumberEntry *table_ptr = 0; + jint line_number_table_entries; + 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... + jint last_location = table_ptr[0].start_location; + int l; + 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; + } +} + +typedef struct _ExpectedContentFrame { + char *name; + char *signature; + char *file_name; + int line_number; +} ExpectedContentFrame; + +static jint CheckSampleContent(JNIEnv *env, + jvmtiStackTrace *trace, + ExpectedContentFrame *expected, + int expected_count) { + int i; + + if (expected_count > trace->frame_count) { + return FAILED; + } + + for (i = 0; i < expected_count; i++) { + // Get basic information out of the trace. + int bci = trace->frames[i].bci; + jmethodID methodid = trace->frames[i].method_id; + char *name = NULL, *signature = NULL, *file_name = NULL; + + if (bci < 0) { + return FAILED; + } + + // Transform into usable information. + int line_number = GetLineNumber(jvmti, methodid, bci); + (*jvmti)->GetMethodName(jvmti, methodid, &name, &signature, 0); + + jclass declaring_class; + if (JVMTI_ERROR_NONE != + (*jvmti)->GetMethodDeclaringClass(jvmti, methodid, &declaring_class)) { + return FAILED; + } + + jvmtiError err = (*jvmti)->GetSourceFileName(jvmti, declaring_class, + &file_name); + if (err != JVMTI_ERROR_NONE) { + return FAILED; + } + + // Compare now, none should be NULL. + if (name == NULL) { + return FAILED; + } + + if (file_name == NULL) { + return FAILED; + } + + if (signature == NULL) { + return FAILED; + } + + 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 FAILED; + } + } + + return PASSED; +} + +static jint CheckSamples(JNIEnv* env, jvmtiStackTrace* traces, int trace_count) { + ExpectedContentFrame expected_contents[] = { + { "helper", "()I", "HeapMonitorTest.java", 58 }, + { "main", "([Ljava/lang/String;)V", "HeapMonitorTest.java", 69 }, + }; + + const int expected_contents_count = + sizeof(expected_contents) / sizeof(expected_contents[0]); + + // We expect the code to record correctly the bci, retrieve the line + // number, have the right method and the class name of the first frames. + int i; + for (i = 0; i < trace_count; i++) { + jvmtiStackTrace *trace = traces + i; + if (CheckSampleContent(env, trace, expected_contents, + expected_contents_count) == PASSED) { + // At least one frame matched what we were looking for. + return PASSED; + } + } + + return FAILED; +} + +static jint GetTraces(JNIEnv* env) { + jvmtiStackTraces full_traces; + jvmtiStackTraces garbage_traces; + jvmtiStackTraces frequent_garbage_traces; + + // Call the JVMTI interface to get the traces. + (*jvmti)->GetLiveTraces(jvmti, &full_traces); + (*jvmti)->GetGarbageTraces(jvmti, &garbage_traces); + (*jvmti)->GetFrequentGarbageTraces(jvmti, &frequent_garbage_traces); + + if (CheckSamples(env, full_traces.stack_traces, + full_traces.trace_count) == FAILED) { + return FAILED; + } + + // Currently we just test the garbage contains also some similar stacktraces. + if (CheckSamples(env, garbage_traces.stack_traces, + garbage_traces.trace_count) == FAILED) { + return FAILED; + } + + // Currently we just test the frequent garbage contains also some similar + // stacktraces. + if (CheckSamples(env, frequent_garbage_traces.stack_traces, + frequent_garbage_traces.trace_count) == FAILED) { + return FAILED; + } + + // Release Traces. + (*jvmti)->ReleaseTraces(jvmti, &full_traces); + (*jvmti)->ReleaseTraces(jvmti, &garbage_traces); + (*jvmti)->ReleaseTraces(jvmti, &frequent_garbage_traces); + + return PASSED; +} + +JNIEXPORT jint JNICALL +Java_MyPackage_HeapMonitorTest_check(JNIEnv *env) { + jobject loader = NULL; + + if (jvmti == NULL) { + throw_exc(env, "JVMTI client was not properly loaded!\n"); + return FAILED; + } + + return GetTraces(env); +} + +#ifdef __cplusplus +} +#endif