--- old/make/test/JtregNative.gmk 2017-05-22 08:52:04.659074713 -0700
+++ new/make/test/JtregNative.gmk 2017-05-22 08:52:04.539075172 -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/assembler_x86.hpp 2017-05-22 08:52:05.051073211 -0700
+++ new/src/cpu/x86/vm/assembler_x86.hpp 2017-05-22 08:52:04.931073670 -0700
@@ -2225,6 +2225,44 @@
_embedded_opmask_register_specifier = (*mask).encoding() & 0x7;
}
+// This is shared between the interpreter and C1, and needs to be in multiple
+// places for each. The code to invoke the actual sampling methods needs
+// to be provided by the user; thus, a macro.
+#define HEAP_MONITORING(ma, thread, var_size_in_bytes, con_size_in_bytes, object, t1, t2, sample_invocation) \
+do { \
+ { \
+ SkipIfEqual skip_if(ma, HeapMonitoring::initialized_address() , 0); \
+ Label skip_sample; \
+ Register thr = thread; \
+ if (!thr->is_valid()) { \
+ NOT_LP64(assert(t1 != noreg, \
+ "Need temporary register for constants")); \
+ thr = NOT_LP64(t1) LP64_ONLY(r15_thread); \
+ NOT_LP64(ma -> get_thread(thr);) \
+ } \
+ /* Trigger heap monitoring event */ \
+ Address bus(thr, \
+ JavaThread::bytes_until_sample_offset()); \
+ \
+ if (var_size_in_bytes->is_valid()) { \
+ ma -> NOT_LP64(subl) LP64_ONLY(subq)(bus, var_size_in_bytes); \
+ } else { \
+ int csib = (con_size_in_bytes); \
+ assert(t2 != noreg, \
+ "Need temporary register for constants"); \
+ ma -> NOT_LP64(movl) LP64_ONLY(mov64)(t2, csib); \
+ ma -> NOT_LP64(subl) LP64_ONLY(subq)(bus, t2); \
+ } \
+ \
+ ma -> jcc(Assembler::positive, skip_sample); \
+ \
+ { \
+ sample_invocation \
+ } \
+ ma -> bind(skip_sample); \
+ } \
+} while(0)
+
};
#endif // CPU_X86_VM_ASSEMBLER_X86_HPP
--- old/src/cpu/x86/vm/c1_MacroAssembler_x86.cpp 2017-05-22 08:52:05.447071694 -0700
+++ new/src/cpu/x86/vm/c1_MacroAssembler_x86.cpp 2017-05-22 08:52:05.339072109 -0700
@@ -23,6 +23,7 @@
*/
#include "precompiled.hpp"
+#include "assembler_x86.hpp"
#include "c1/c1_MacroAssembler.hpp"
#include "c1/c1_Runtime1.hpp"
#include "classfile/systemDictionary.hpp"
@@ -201,6 +202,10 @@
try_allocate(obj, noreg, object_size * BytesPerWord, t1, t2, slow_case);
initialize_object(obj, klass, noreg, object_size * HeapWordSize, t1, t2, UseTLAB);
+
+ HEAP_MONITORING(this, noreg, noreg, object_size * HeapWordSize, obj,
+ t1, t2, call(RuntimeAddress(Runtime1::entry_for(
+ Runtime1::heap_object_sample_id))););
}
void C1_MacroAssembler::initialize_object(Register obj, Register klass, Register var_size_in_bytes, int con_size_in_bytes, Register t1, Register t2, bool is_tlab_allocated) {
@@ -277,13 +282,19 @@
// clear rest of allocated space
const Register len_zero = len;
+ // Initialize body destroys arr_size so remember it.
+ push(arr_size);
initialize_body(obj, arr_size, header_size * BytesPerWord, len_zero);
+ pop(arr_size);
if (CURRENT_ENV->dtrace_alloc_probes()) {
assert(obj == rax, "must be");
call(RuntimeAddress(Runtime1::entry_for(Runtime1::dtrace_object_alloc_id)));
}
+ HEAP_MONITORING(this, noreg, arr_size, 0, obj, t1, noreg,
+ call(RuntimeAddress(Runtime1::entry_for(
+ Runtime1::heap_object_sample_id))););
verify_oop(obj);
}
--- old/src/cpu/x86/vm/c1_Runtime1_x86.cpp 2017-05-22 08:52:05.839070193 -0700
+++ new/src/cpu/x86/vm/c1_Runtime1_x86.cpp 2017-05-22 08:52:05.719070653 -0700
@@ -414,7 +414,8 @@
}
static OopMap* save_live_registers(StubAssembler* sasm, int num_rt_args,
- bool save_fpu_registers = true) {
+ bool save_fpu_registers = true,
+ bool do_generate_oop_map = true) {
__ block_comment("save_live_registers");
__ pusha(); // integer registers
@@ -489,7 +490,9 @@
// FPU stack must be empty now
__ verify_FPU(0, "save_live_registers");
- return generate_oop_map(sasm, num_rt_args, save_fpu_registers);
+ return do_generate_oop_map
+ ? generate_oop_map(sasm, num_rt_args, save_fpu_registers)
+ : NULL;
}
@@ -957,6 +960,24 @@
return oop_maps;
}
+static void heap_support_stub(StubAssembler* sasm, Register obj,
+ Register size_in_bytes, int con_size_in_bytes,
+ Register t1, Register t2) {
+ // Usually, when we invoke the sampling methods from within the client
+ // compiler, we do so in a stub. However, sometimes, we are already in a stub
+ // when we want to call these things, and stack trace gathering gets confused
+ // when you call a stub inside another stub.
+ HEAP_MONITORING(sasm, noreg, size_in_bytes, con_size_in_bytes, obj, t1, t2, \
+ { \
+ save_live_registers(sasm, 1, true, false); \
+ __ NOT_LP64(push(rax)) LP64_ONLY(mov(c_rarg0, rax)); \
+ __ call(RuntimeAddress(
+ CAST_FROM_FN_PTR(address, \
+ HeapMonitoring::object_alloc_unsized))); \
+ NOT_LP64(__ pop(rax)); \
+ restore_live_registers(sasm); \
+ });
+}
OopMapSet* Runtime1::generate_code_for(StubID id, StubAssembler* sasm) {
@@ -1042,6 +1063,7 @@
__ initialize_object(obj, klass, obj_size, 0, t1, t2, /* is_tlab_allocated */ true);
__ verify_oop(obj);
+ heap_support_stub(sasm, obj, obj_size, 0, t1, t2);
__ pop(rbx);
__ pop(rdi);
__ ret(0);
@@ -1170,8 +1192,12 @@
__ subptr(arr_size, t1); // body length
__ addptr(t1, obj); // body start
if (!ZeroTLAB) {
+ // Initialize body destroys arr_size so remember it.
+ __ push(arr_size);
__ initialize_body(t1, arr_size, 0, t2);
+ __ pop(arr_size);
}
+ heap_support_stub(sasm, obj, arr_size, 0, t1, t2);
__ verify_oop(obj);
__ ret(0);
@@ -1504,6 +1530,22 @@
NOT_LP64(__ pop(rax));
restore_live_registers(sasm);
+ }
+ break;
+
+ case heap_object_sample_id:
+ { // rax,: object
+ StubFrame f(sasm, "heap_object_sample", dont_gc_arguments);
+ // We can't gc here so skip the oopmap but make sure that all
+ // the live registers get saved
+ save_live_registers(sasm, 1);
+
+ __ NOT_LP64(push(rax)) LP64_ONLY(mov(c_rarg0, rax));
+ __ call(RuntimeAddress(CAST_FROM_FN_PTR(address,
+ HeapMonitoring::object_alloc)));
+ NOT_LP64(__ pop(rax));
+
+ restore_live_registers(sasm);
}
break;
--- old/src/cpu/x86/vm/templateTable_x86.cpp 2017-05-22 08:52:06.247068631 -0700
+++ new/src/cpu/x86/vm/templateTable_x86.cpp 2017-05-22 08:52:06.135069060 -0700
@@ -3927,6 +3927,7 @@
// The object is initialized before the header. If the object size is
// zero, go directly to the header initialization.
__ bind(initialize_object);
+ __ movq(rbx, rdx); // Save the size for HeapMonitoring
__ decrement(rdx, sizeof(oopDesc));
__ jcc(Assembler::zero, initialize_header);
@@ -3957,6 +3958,10 @@
// initialize object header only.
__ bind(initialize_header);
+
+ // Restore size for HeapMonitoring
+ __ movq(rdx, rbx);
+
if (UseBiasedLocking) {
__ pop(rcx); // get saved klass back in the register.
__ movptr(rbx, Address(rcx, Klass::prototype_header_offset()));
@@ -3977,10 +3982,20 @@
// Trigger dtrace event for fastpath
__ push(atos);
__ call_VM_leaf(
- CAST_FROM_FN_PTR(address, SharedRuntime::dtrace_object_alloc), rax);
+ CAST_FROM_FN_PTR(address, SharedRuntime::dtrace_object_alloc),
+ rax, rdx);
__ pop(atos);
}
+ HEAP_MONITORING(_masm, noreg, rdx, 0, rax, rcx, noreg, \
+ { \
+ __ push(atos); \
+ __ call_VM_leaf( \
+ CAST_FROM_FN_PTR(address, HeapMonitoring::object_alloc), \
+ rax, rdx); \
+ __ pop(atos); \
+ });
+
__ jmp(done);
}
--- old/src/share/vm/c1/c1_Runtime1.cpp 2017-05-22 08:52:06.679066977 -0700
+++ new/src/share/vm/c1/c1_Runtime1.cpp 2017-05-22 08:52:06.563067421 -0700
@@ -202,6 +202,7 @@
switch (id) {
// These stubs don't need to have an oopmap
case dtrace_object_alloc_id:
+ case heap_object_sample_id:
case g1_pre_barrier_slow_id:
case g1_post_barrier_slow_id:
case slow_subtype_check_id:
--- old/src/share/vm/c1/c1_Runtime1.hpp 2017-05-22 08:52:07.079065445 -0700
+++ new/src/share/vm/c1/c1_Runtime1.hpp 2017-05-22 08:52:06.967065874 -0700
@@ -39,6 +39,7 @@
#define RUNTIME1_STUBS(stub, last_entry) \
stub(dtrace_object_alloc) \
+ stub(heap_object_sample) \
stub(unwind_exception) \
stub(forward_exception) \
stub(throw_range_check_failed) /* throws ArrayIndexOutOfBoundsException */ \
--- old/src/share/vm/gc/g1/g1CollectedHeap.cpp 2017-05-22 08:52:07.447064037 -0700
+++ new/src/share/vm/gc/g1/g1CollectedHeap.cpp 2017-05-22 08:52:07.339064449 -0700
@@ -4507,6 +4507,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-05-22 08:52:07.907062274 -0700
+++ new/src/share/vm/gc/g1/g1MarkSweep.cpp 2017-05-22 08:52:07.799062689 -0700
@@ -250,6 +250,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-05-22 08:52:08.291060805 -0700
+++ new/src/share/vm/gc/parallel/psMarkSweep.cpp 2017-05-22 08:52:08.175061248 -0700
@@ -610,6 +610,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-05-22 08:52:08.699059242 -0700
+++ new/src/share/vm/gc/parallel/psParallelCompact.cpp 2017-05-22 08:52:08.579059701 -0700
@@ -2169,6 +2169,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.inline.hpp 2017-05-22 08:52:09.115057649 -0700
+++ new/src/share/vm/gc/shared/collectedHeap.inline.hpp 2017-05-22 08:52:08.999058093 -0700
@@ -33,6 +33,7 @@
#include "oops/arrayOop.hpp"
#include "oops/oop.inline.hpp"
#include "prims/jvmtiExport.hpp"
+#include "runtime/heapMonitoring.hpp"
#include "runtime/sharedRuntime.hpp"
#include "runtime/thread.inline.hpp"
#include "services/lowMemoryDetector.hpp"
@@ -81,6 +82,24 @@
SharedRuntime::dtrace_object_alloc(obj, size);
}
}
+
+ if (HeapMonitoring::initialized()) {
+ // support for object alloc event (no-op most of the time)
+ if (klass != NULL && klass->name() != NULL) {
+ Thread *base_thread = Thread::current();
+ if (base_thread->is_Java_thread()) {
+ JavaThread *thread = (JavaThread *) base_thread;
+ size_t *bytes_until_sample = thread->bytes_until_sample();
+ size_t size_in_bytes = ((size_t) size) << LogHeapWordSize;
+ assert(size > 0, "positive size");
+ if (*bytes_until_sample < size_in_bytes) {
+ HeapMonitoring::object_alloc_do_sample(thread, obj, size_in_bytes);
+ } else {
+ *bytes_until_sample -= size_in_bytes;
+ }
+ }
+ }
+ }
}
void CollectedHeap::post_allocation_setup_obj(Klass* klass,
--- old/src/share/vm/gc/shared/genCollectedHeap.cpp 2017-05-22 08:52:09.491056209 -0700
+++ new/src/share/vm/gc/shared/genCollectedHeap.cpp 2017-05-22 08:52:09.379056638 -0700
@@ -722,6 +722,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-05-22 08:52:09.871054754 -0700
+++ new/src/share/vm/gc/shared/referenceProcessor.cpp 2017-05-22 08:52:09.747055230 -0700
@@ -257,6 +257,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/opto/macro.cpp 2017-05-22 08:52:10.259053268 -0700
+++ new/src/share/vm/opto/macro.cpp 2017-05-22 08:52:10.171053605 -0700
@@ -1126,6 +1126,75 @@
}
}
+void PhaseMacroExpand::conditional_sample(Node *should_sample,
+ BoolTest::mask test,
+ float probability,
+ CallLeafNode *call,
+ Node *thread,
+ Node **fast_oop_ctrl,
+ Node **fast_oop_rawmem,
+ Node **fast_oop,
+ Node *size_in_bytes,
+ Node *in_node) {
+ Node* sample_cmp = new CmpXNode(should_sample, _igvn.MakeConX(0));
+ transform_later(sample_cmp);
+
+ Node *sample_bool = new BoolNode(sample_cmp, test);
+ transform_later(sample_bool);
+
+ IfNode *sample_if = new IfNode(*fast_oop_ctrl,
+ sample_bool,
+ probability,
+ COUNT_UNKNOWN);
+ transform_later(sample_if);
+
+ // Slow-path call to sample
+ Node *sample_true = new IfTrueNode(sample_if);
+ transform_later(sample_true);
+
+ // Fast path to no sample
+ Node *sample_false = new IfFalseNode(sample_if);
+ transform_later(sample_false);
+
+ // Create postdominators for both the control and data flow paths.
+ Node *sample_region = new RegionNode(3);
+ Node *sample_phi_rawmem = new PhiNode(sample_region,
+ Type::MEMORY,
+ TypeRawPtr::BOTTOM);
+
+ sample_region->init_req(1, sample_false);
+ sample_phi_rawmem->init_req(1, *fast_oop_rawmem);
+
+ // Invoke the sampling method on the slow path.
+ int size = TypeFunc::Parms + 2;
+
+ call->init_req(TypeFunc::Parms+0, thread);
+ call->init_req(TypeFunc::Parms+1, *fast_oop);
+ call->init_req(TypeFunc::Parms+2, size_in_bytes);
+#ifdef _LP64
+ // The size is TypeX, so in a 64-bit JVM this a long, and we need
+ // // a second, dummy argument (an idiosyncracy of C2).
+ call->init_req(TypeFunc::Parms+3, C->top());
+#endif
+ call->init_req( TypeFunc::Control, sample_true);
+ call->init_req( TypeFunc::I_O , top()); // does no i/o
+ call->init_req( TypeFunc::Memory , *fast_oop_rawmem );
+ call->init_req( TypeFunc::ReturnAdr, in_node->in(TypeFunc::ReturnAdr));
+ call->init_req( TypeFunc::FramePtr, in_node->in(TypeFunc::FramePtr));
+ transform_later(call);
+ Node *sample_oop_rawmem = new ProjNode(call, TypeFunc::Memory);
+ transform_later(sample_oop_rawmem);
+
+ // Tie the slow path to the postdominating node.
+ sample_region->init_req(2, sample_true);
+ sample_phi_rawmem->init_req(2, sample_oop_rawmem);
+ transform_later(sample_region);
+
+ *fast_oop_ctrl = sample_region;
+ *fast_oop_rawmem = sample_phi_rawmem;
+ transform_later(*fast_oop_rawmem);
+}
+
bool PhaseMacroExpand::eliminate_allocate_node(AllocateNode *alloc) {
// Don't do scalar replacement if the frame can be popped by JVMTI:
// if reallocation fails during deoptimization we'll pop all
@@ -1636,6 +1705,65 @@
transform_later(fast_oop_rawmem);
}
+ if (HeapMonitoring::initialized()) {
+ // Inlined version of HeapMonitoring::object_alloc_base
+ // Get base of thread-local storage area
+ Node* thread = new ThreadLocalNode();
+ transform_later(thread);
+
+ ByteSize sample_offset = JavaThread::bytes_until_sample_offset();
+
+ // Do test to see if we should sample.
+ // Get bytes_until_sample from thread local storage.
+ Node *bytes_until_sample = make_load(fast_oop_ctrl,
+ fast_oop_rawmem,
+ thread,
+ in_bytes(sample_offset),
+ TypeX_X,
+ TypeX_X->basic_type());
+
+ // new_bytes_until_sample = bytes_until_sample - size_in_bytes
+ Node *new_bytes_until_sample =
+ new SubXNode(bytes_until_sample, size_in_bytes);
+ transform_later(new_bytes_until_sample);
+
+ // bytes_until_sample = new_bytes_until_sample;
+ fast_oop_rawmem = make_store(fast_oop_ctrl,
+ fast_oop_rawmem,
+ thread,
+ in_bytes(sample_offset),
+ new_bytes_until_sample,
+ TypeX_X->basic_type());
+
+ // Call to make if sampling succeeds
+ CallLeafNode *call = new CallLeafNode(
+ OptoRuntime::heap_object_alloc_Type(),
+ CAST_FROM_FN_PTR(address,
+ HeapMonitoring::object_alloc_do_sample),
+ "object_alloc_do_sample",
+ TypeRawPtr::BOTTOM);
+
+ // Copy debug information and adjust JVMState information:
+ // We are copying the debug information because the allocation JVMS
+ // might be modified/removed, etc. But also we want to preserve the JVMS
+ // in case this node is from an inlined method.
+ copy_call_debug_info((CallNode *) alloc, call);
+
+ // if (new_bytes_until_sample < 0)
+ conditional_sample(new_bytes_until_sample,
+ BoolTest::le,
+ // Probability
+ // ~1/10000
+ PROB_UNLIKELY_MAG(4),
+ call,
+ thread,
+ &fast_oop_ctrl,
+ &fast_oop_rawmem,
+ &fast_oop,
+ size_in_bytes,
+ alloc);
+ }
+
// Plug in the successful fast-path into the result merge point
result_region ->init_req(fast_result_path, fast_oop_ctrl);
result_phi_rawoop->init_req(fast_result_path, fast_oop);
--- old/src/share/vm/opto/macro.hpp 2017-05-22 08:52:10.703051569 -0700
+++ new/src/share/vm/opto/macro.hpp 2017-05-22 08:52:10.583052029 -0700
@@ -66,6 +66,19 @@
Node* make_store(Node* ctl, Node* mem, Node* base, int offset,
Node* value, BasicType bt);
+ // For Heap-related sampling - will generate code to invoke call()
+ // if the given sampling parameters are true.
+ void conditional_sample(Node *should_sample,
+ BoolTest::mask test,
+ float probability,
+ CallLeafNode *call,
+ Node *thread,
+ Node **fast_oop_ctrl,
+ Node **fast_oop_rawmem,
+ Node **fast_oop,
+ Node* size_in_bytes,
+ Node *in_node);
+
// projections extracted from a call node
ProjNode *_fallthroughproj;
ProjNode *_fallthroughcatchproj;
--- old/src/share/vm/opto/runtime.cpp 2017-05-22 08:52:11.083050114 -0700
+++ new/src/share/vm/opto/runtime.cpp 2017-05-22 08:52:10.963050574 -0700
@@ -1558,6 +1558,28 @@
return TypeFunc::make(domain,range);
}
+const TypeFunc *OptoRuntime::heap_object_alloc_Type() {
+ // Keep it separate so that we don't have to worry if they change it.
+ // create input type (domain)
+ const Type **fields = TypeTuple::fields(3 LP64_ONLY( + 1));
+
+ // Thread-local storage
+ fields[TypeFunc::Parms+0] = TypeRawPtr::BOTTOM;
+ // oop; newly allocated object
+ fields[TypeFunc::Parms+1] = TypeInstPtr::NOTNULL;
+ // byte size of object
+ fields[TypeFunc::Parms+2] = TypeX_X;
+ // other half of long length
+ LP64_ONLY(fields[TypeFunc::Parms+3] = Type::HALF);
+
+ const TypeTuple *domain = TypeTuple::make(TypeFunc::Parms+4, fields);
+ // create result type (range)
+ fields = TypeTuple::fields(0);
+
+ const TypeTuple *range = TypeTuple::make(TypeFunc::Parms+0, fields);
+
+ return TypeFunc::make(domain, range);
+}
JRT_ENTRY_NO_ASYNC(void, OptoRuntime::register_finalizer(oopDesc* obj, JavaThread* thread))
assert(obj->is_oop(), "must be a valid oop");
--- old/src/share/vm/opto/runtime.hpp 2017-05-22 08:52:11.455048688 -0700
+++ new/src/share/vm/opto/runtime.hpp 2017-05-22 08:52:11.355049072 -0700
@@ -329,6 +329,9 @@
static const TypeFunc* dtrace_method_entry_exit_Type();
static const TypeFunc* dtrace_object_alloc_Type();
+ // Heap sampling support
+ static const TypeFunc* heap_object_alloc_Type();
+
private:
static NamedCounter * volatile _named_counters;
--- old/src/share/vm/prims/forte.cpp 2017-05-22 08:52:11.839047219 -0700
+++ new/src/share/vm/prims/forte.cpp 2017-05-22 08:52:11.727047648 -0700
@@ -35,19 +35,6 @@
#include "runtime/vframe.hpp"
#include "runtime/vframeArray.hpp"
-// call frame copied from old .h file and renamed
-typedef struct {
- jint lineno; // line number in the source file
- jmethodID method_id; // method executed in this frame
-} ASGCT_CallFrame;
-
-// call trace copied from old .h file and renamed
-typedef struct {
- JNIEnv *env_id; // Env where trace was recorded
- jint num_frames; // number of frames in this trace
- ASGCT_CallFrame *frames; // frames
-} ASGCT_CallTrace;
-
// These name match the names reported by the forte quality kit
enum {
ticks_no_Java_frame = 0,
--- old/src/share/vm/prims/forte.hpp 2017-05-22 08:52:12.231045717 -0700
+++ new/src/share/vm/prims/forte.hpp 2017-05-22 08:52:12.119046146 -0700
@@ -34,4 +34,20 @@
// register internal VM stub
};
+// call frame copied from old .h file and renamed
+typedef struct {
+ jint lineno; // line number in the source file
+ jmethodID method_id; // method executed in this frame
+} ASGCT_CallFrame;
+
+// call trace copied from old .h file and renamed
+typedef struct {
+ JNIEnv *env_id; // Env where trace was recorded
+ jint num_frames; // number of frames in this trace
+ ASGCT_CallFrame *frames; // frames
+} ASGCT_CallTrace;
+
+extern "C"
+void AsyncGetCallTrace(ASGCT_CallTrace *trace, jint depth, void* ucontext);
+
#endif // SHARE_VM_PRIMS_FORTE_HPP
--- old/src/share/vm/prims/jvmti.xml 2017-05-22 08:52:12.611044262 -0700
+++ new/src/share/vm/prims/jvmti.xml 2017-05-22 08:52:12.491044722 -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-05-22 08:52:13.247041828 -0700
+++ new/src/share/vm/prims/jvmtiEnv.cpp 2017-05-22 08:52:13.127042287 -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"
@@ -1947,6 +1948,64 @@
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;
+ }
+
+ fprintf(stderr, "Initializing\n");
+ 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/init.cpp 2017-05-22 08:52:13.695040112 -0700
+++ new/src/share/vm/runtime/init.cpp 2017-05-22 08:52:13.575040572 -0700
@@ -32,6 +32,8 @@
#include "prims/methodHandles.hpp"
#include "runtime/globals.hpp"
#include "runtime/handles.inline.hpp"
+#include "runtime/heapMonitoring.hpp"
+#include "prims/jvmtiHeapTransition.hpp"
#include "runtime/icache.hpp"
#include "runtime/init.hpp"
#include "runtime/safepoint.hpp"
--- old/src/share/vm/runtime/thread.cpp 2017-05-22 08:52:14.087038610 -0700
+++ new/src/share/vm/runtime/thread.cpp 2017-05-22 08:52:13.967039070 -0700
@@ -1488,6 +1488,7 @@
_do_not_unlock_if_synchronized = false;
_cached_monitor_info = NULL;
_parker = Parker::Allocate(this);
+ _bytes_until_sample = 0;
#ifndef PRODUCT
_jmp_ring_index = 0;
--- old/src/share/vm/runtime/thread.hpp 2017-05-22 08:52:14.555036819 -0700
+++ new/src/share/vm/runtime/thread.hpp 2017-05-22 08:52:14.435037278 -0700
@@ -815,6 +815,9 @@
JavaFrameAnchor _anchor; // Encapsulation of current java frame and it state
+ size_t _bytes_until_sample; // Thread local counter to determine when to sample
+ // allocations.
+
ThreadFunction _entry_point;
JNIEnv _jni_environment;
@@ -1102,6 +1105,9 @@
address last_Java_pc(void) { return _anchor.last_Java_pc(); }
+ // Bytes until next heap sample.
+ size_t* bytes_until_sample() { return &_bytes_until_sample; }
+
// Safepoint support
#if !(defined(PPC64) || defined(AARCH64))
JavaThreadState thread_state() const { return _thread_state; }
@@ -1554,6 +1560,7 @@
static ByteSize frame_anchor_offset() {
return byte_offset_of(JavaThread, _anchor);
}
+ static ByteSize bytes_until_sample_offset() { return byte_offset_of(JavaThread, _bytes_until_sample); }
static ByteSize callee_target_offset() { return byte_offset_of(JavaThread, _callee_target); }
static ByteSize vm_result_offset() { return byte_offset_of(JavaThread, _vm_result); }
static ByteSize vm_result_2_offset() { return byte_offset_of(JavaThread, _vm_result_2); }
--- /dev/null 2017-05-01 09:42:45.355096588 -0700
+++ new/src/share/vm/prims/jvmtiHeapTransition.hpp 2017-05-22 08:52:14.867035624 -0700
@@ -0,0 +1,62 @@
+/*
+ * 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
+
+// TODO(jcbeyler): is there a better/standard JVM way of doing this?
+// 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-05-22 08:52:15.207034323 -0700
@@ -0,0 +1,748 @@
+/*
+ * 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"
+
+// TODO: add capability to get stacktraces in forte.cpp
+
+const int kMaxStackDepth = 64;
+
+// Internal data structure representing traces.
+struct StackTraceData : CHeapObj {
+ ASGCT_CallTrace *trace;
+ intx byte_size;
+ jlong thread_id;
+ oop obj;
+ int references;
+
+ StackTraceData(ASGCT_CallTrace *t, intx size, jlong tid, oop o) : trace(t),
+ byte_size(size), thread_id(tid), obj(o), references(0) {}
+
+ StackTraceData() : trace(NULL), byte_size(0), thread_id(0), 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(ASGCT_CallFrame, data->trace->frames);
+ FREE_C_HEAP_OBJ(data->trace);
+ }
+ delete data;
+ }
+};
+
+// RAII class that acquires / releases lock
+class MuxLocker {
+ 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. trace is the stacktrace, and thread
+ // is the thread that did the allocation.
+ void add_trace(ASGCT_CallTrace *trace, intx byte_size, Thread *thread, 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(ASGCT_CallTrace *trace,
+ intx byte_size,
+ Thread *thread,
+ oop o) {
+ StackTraceData new_data(trace, byte_size, SharedRuntime::get_java_tid(thread), 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) {
+ to->thread_id = from->thread_id;
+ to->size = from->byte_size;
+
+ // ASGCT_CallTrace is folded into jvmtiStackTrace.
+ const ASGCT_CallTrace *src = from->trace;
+ to->env_id =src->env_id;
+ to->frame_count = src->num_frames;
+
+ to->frames =
+ NEW_C_HEAP_ARRAY(jvmtiCallFrame, kMaxStackDepth, mtInternal);
+
+ if (to->frames == NULL) {
+ return false;
+ }
+
+ // This supposes right now that the ASGCT_CallFrame is the same size as the
+ // jvmtiCallFrame structure. If not, we have to do it by hand and it means
+ // something is off between both structures.
+ if (sizeof(ASGCT_CallFrame) != sizeof(jvmtiCallFrame)) {
+ for (int i = 0; i < to->frame_count; i++) {
+ // Note we still have the hack where ASCGT is piggy backing the bci via
+ // the lineno. This might change soon.
+ to->frames[i].bci = src->frames[i].lineno;
+ to->frames[i].method_id = src->frames[i].method_id;
+ }
+ }
+ memcpy(to->frames,
+ src->frames,
+ sizeof(ASGCT_CallFrame) * 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(JavaThread *t) {
+ _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.
+ size_t *bytes_until_sample = t->bytes_until_sample();
+ double log_val = (fast_log2(q) - 26);
+ *bytes_until_sample = 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);
+ size_t *bytes_until_sample = thread->bytes_until_sample();
+ if (StackTraceStorage::storage()->IsInitialized()) {
+ assert(t->is_Java_thread(), "non-Java thread passed to do_sample");
+ JavaThread *thread = static_cast(t);
+
+ pick_next_sample(thread);
+
+ ASGCT_CallTrace *trace = NEW_C_HEAP_OBJ(ASGCT_CallTrace, mtInternal);
+ if (trace == NULL) {
+ return;
+ }
+
+ ASGCT_CallFrame *frames =
+ NEW_C_HEAP_ARRAY(ASGCT_CallFrame, kMaxStackDepth, mtInternal);
+ if (frames == NULL) {
+ FREE_C_HEAP_OBJ(trace);
+ return;
+ }
+
+ trace->frames = frames;
+ trace->env_id = (JavaThread::current())->jni_environment();
+
+ ucontext_t uc;
+ if (!getcontext(&uc)) {
+#if defined(IA32)
+ // On Linux/x86 (but not x64), AsyncGetCallTrace/JVM reads the
+ // stack pointer from the REG_UESP field (as opposed to the
+ // REG_ESP field). The kernel sets both the REG_UESP and REG_ESP
+ // fields to the correct stack pointer for the ucontexts passed to
+ // signal handlers. However, getcontext() sets only REG_ESP,
+ // leaving REG_UESP uninitialized. Since there is no way to
+ // distinguish where a ucontext_t came from, copy from REG_ESP to
+ // REG_UESP so that AGCT will read the right stack pointer.
+ uc.uc_mcontext.gregs[REG_UESP] = uc.uc_mcontext.gregs[REG_ESP];
+#endif
+
+ AsyncGetCallTrace(trace, kMaxStackDepth, &uc);
+
+ if (trace->num_frames > 0) {
+ // Success!
+ StackTraceStorage::storage()->add_trace(trace, byte_size, thread, o);
+ return;
+ }
+ }
+ // Failure!
+ FREE_C_HEAP_ARRAY(ASGCT_CallFrame, 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");
+ JavaThread *thread = static_cast(t);
+ *(thread->bytes_until_sample()) = 65536;
+ }
+#else
+ Unimplemented();
+#endif
+}
--- /dev/null 2017-05-01 09:42:45.355096588 -0700
+++ new/src/share/vm/runtime/heapMonitoring.hpp 2017-05-22 08:52:15.535033067 -0700
@@ -0,0 +1,98 @@
+/*
+ * 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 {
+ 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 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-05-22 08:52:15.827031949 -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-05-22 08:52:16.175030617 -0700
@@ -0,0 +1,351 @@
+/*
+ * 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) {
+ // Only look at the first few frames, underneath we go under main and it is
+ // less interesting.
+
+ // TODO: There is an issue with the stacktrace walker and I think it is
+ // getting confused, so this is just to show what the test will look like.
+ // Basically the line 61 is not yet accuracte, I have a fix for it but need to
+ // double check.
+ // The full system is not yet functional.
+ ExpectedContentFrame expected_contents[] = {
+ { "helper", "()I", "HeapMonitorTest.java", 61 },
+ { "main", "([Ljava/lang/String;)V", "HeapMonitorTest.java", 69 },
+ };
+
+ const int expected_contents_count =
+ sizeof(expected_contents) / sizeof(expected_contents[0]);
+
+ // We also 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;
+ int j;
+ for (j = 0; j < trace->frame_count; j++) {
+ // Get basic information out of the trace.
+ int bci = trace->frames[j].bci;
+ jmethodID methodid = trace->frames[j].method_id;
+ char *name = NULL, *signature = NULL, *file_name = NULL;
+
+ // Transform into usable information.
+ int line_number = GetLineNumber(jvmti, methodid, bci);
+ (*jvmti)->GetMethodName(jvmti, methodid, &name, &signature, 0);
+
+ jclass declaring_class = 0;
+ (*jvmti)->GetMethodDeclaringClass(jvmti, methodid, &declaring_class);
+
+ if (declaring_class) {
+ jvmtiError err = (*jvmti)->GetSourceFileName(jvmti, declaring_class, &file_name);
+ }
+ }
+ }
+
+ 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);
+
+ 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