--- old/src/hotspot/share/gc/g1/g1CollectedHeap.cpp 2017-09-22 13:32:59.897871642 -0700 +++ new/src/hotspot/share/gc/g1/g1CollectedHeap.cpp 2017-09-22 13:32:59.545872803 -0700 @@ -4140,7 +4140,7 @@ } void G1CollectedHeap::process_heap_monitoring() { - log_develop_trace(gc, ref)("HeapSampling [other] : heap monitoring processing"); + log_develop_trace(gc, ref)("Heap Sampler: heap monitoring processing"); G1STWIsAliveClosure is_alive(this); G1KeepAliveClosure keep_alive(this); HeapMonitoring::weak_oops_do(&is_alive, &keep_alive); --- old/src/hotspot/share/gc/shared/collectedHeap.cpp 2017-09-22 13:33:00.797868676 -0700 +++ new/src/hotspot/share/gc/shared/collectedHeap.cpp 2017-09-22 13:33:00.473869743 -0700 @@ -304,10 +304,12 @@ // - Both of the above reasons are true at the same time. if (HeapMonitoring::enabled()) { if (thread->tlab().should_sample()) { + HeapWord *end = thread->tlab().end(); + thread->tlab().set_back_actual_end(); + // If we don't have an object yet, try to allocate it. if (obj == NULL) { // The tlab could still have space after this sample. - thread->tlab().set_back_actual_end(); obj = thread->tlab().allocate(size); } @@ -318,9 +320,9 @@ // Object is allocated, sample it now. HeapMonitoring::object_alloc_do_sample(thread, reinterpret_cast(obj), - size); + size * HeapWordSize); // Pick a next sample in this case, we allocated right. - thread->tlab().pick_next_sample(); + thread->tlab().pick_next_sample(thread->tlab().top() - end); } } } @@ -330,6 +332,7 @@ HeapWord* CollectedHeap::allocate_from_tlab_slow(Klass* klass, Thread* thread, size_t size) { HeapWord* obj = handle_heap_sampling(thread, NULL, size); + bool should_sample = thread->tlab().should_sample(); if (obj != NULL) { return obj; @@ -374,8 +377,12 @@ #endif // ASSERT } thread->tlab().fill(obj, obj + size, new_tlab_size); - handle_heap_sampling(thread, obj, size); - return obj; + + if (should_sample) { + return handle_heap_sampling(thread, obj, size); + } else { + return obj; + } } void CollectedHeap::flush_deferred_store_barrier(JavaThread* thread) { --- old/src/hotspot/share/gc/shared/referenceProcessor.cpp 2017-09-22 13:33:01.677865774 -0700 +++ new/src/hotspot/share/gc/shared/referenceProcessor.cpp 2017-09-22 13:33:01.333866908 -0700 @@ -262,8 +262,8 @@ // Heap Monitoring references size_t handled; { - GCTraceTime(Debug, gc, ref) tt("Heap Monitoring Weak Reference", gc_timer); - handled = process_phaseHeapSampling(is_alive, keep_alive, complete_gc, task_executor); + GCTraceTime(Debug, gc, ref) tt("Heap Sampler Weak Reference", phase_times->gc_timer()); + handled = process_phaseHeapSampling(is_alive, keep_alive, complete_gc); } phase_times->set_total_time_ms((os::elapsedTime() - start_time) * 1000); @@ -302,13 +302,9 @@ size_t ReferenceProcessor::process_phaseHeapSampling( BoolObjectClosure* is_alive, OopClosure* keep_alive, - VoidClosure* complete_gc, - AbstractRefProcTaskExecutor* task_executor) { + VoidClosure* complete_gc) { size_t count = 0; if (HeapMonitoring::enabled()) { - if (task_executor != NULL) { - task_executor->set_single_threaded_mode(); - } count = HeapMonitoring::weak_oops_do(is_alive, keep_alive); complete_gc->do_void(); } --- old/src/hotspot/share/gc/shared/referenceProcessor.hpp 2017-09-22 13:33:02.573862820 -0700 +++ new/src/hotspot/share/gc/shared/referenceProcessor.hpp 2017-09-22 13:33:02.229863954 -0700 @@ -252,8 +252,7 @@ size_t process_phaseHeapSampling(BoolObjectClosure* is_alive, OopClosure* keep_alive, - VoidClosure* complete_gc, - AbstractRefProcTaskExecutor* task_executor); + VoidClosure* complete_gc); // Work methods used by the method process_discovered_reflist // Phase1: keep alive all those referents that are otherwise --- old/src/hotspot/share/gc/shared/threadLocalAllocBuffer.cpp 2017-09-22 13:33:03.401860091 -0700 +++ new/src/hotspot/share/gc/shared/threadLocalAllocBuffer.cpp 2017-09-22 13:33:03.089861119 -0700 @@ -174,8 +174,21 @@ _number_of_refills++; print_stats("fill"); assert(top <= start + new_size - alignment_reserve(), "size too small"); + + // Remember old bytes until sample for the next tlab only if this is our first + // actual refill. + size_t old_bytes_until_sample = 0; + if (_number_of_refills > 1) { + old_bytes_until_sample = bytes_until_sample(); + } + initialize(start, top, start + new_size - alignment_reserve()); + if (old_bytes_until_sample > 0) { + set_bytes_until_sample(old_bytes_until_sample); + set_sample_end(); + } + // Reset amount of internal fragmentation set_refill_waste_limit(initial_refill_waste_limit()); } @@ -313,16 +326,7 @@ guarantee(p == top(), "end of last object must match end of space"); } -void ThreadLocalAllocBuffer::pick_next_sample() { - if (!HeapMonitoring::enabled()) { - return; - } - - if (bytes_until_sample() == 0) { - HeapMonitoring::pick_next_sample(bytes_until_sample_addr()); - } - - // Finally, fix up the sampling bytes left and _end. +void ThreadLocalAllocBuffer::set_sample_end() { size_t heap_words_remaining = _end - _top; size_t bytes_left = bytes_until_sample(); size_t words_until_sample = bytes_left / HeapWordSize; @@ -336,6 +340,25 @@ bytes_left -= heap_words_remaining * HeapWordSize; set_bytes_until_sample(bytes_left); } +} + +void ThreadLocalAllocBuffer::pick_next_sample(size_t diff) { + if (!HeapMonitoring::enabled()) { + return; + } + + if (bytes_until_sample() == 0) { + HeapMonitoring::pick_next_sample(bytes_until_sample_addr()); + } + + if (diff > 0) { + // Try to correct sample size by removing extra space from last allocation. + if (bytes_until_sample() > diff * HeapWordSize) { + set_bytes_until_sample(bytes_until_sample() - diff * HeapWordSize); + } + } + + set_sample_end(); log_trace(gc, tlab)("TLAB picked next sample: thread: " INTPTR_FORMAT " [id: %2d]" " start: %p top: %p end: %p actual_end: %p slow_path_end: %p", @@ -367,16 +390,23 @@ return; } - set_bytes_until_sample(bytes_until_sample() - size); + size_t size_in_bytes = size * HeapWordSize; + if (bytes_until_sample() > size_in_bytes) { + set_bytes_until_sample(bytes_until_sample() - size_in_bytes); + } else { + // Technically this is not exactly right, we probably should remember how many bytes are + // negative probably to then reduce our next sample size. + set_bytes_until_sample(0); + } // Should we sample now? - set_back_actual_end(); if (should_sample()) { HeapMonitoring::object_alloc_do_sample(thread, reinterpret_cast(result), - size); + size_in_bytes); + set_back_actual_end(); + pick_next_sample(); } - pick_next_sample(); } HeapWord* ThreadLocalAllocBuffer::hard_end() { --- old/src/hotspot/share/gc/shared/threadLocalAllocBuffer.hpp 2017-09-22 13:33:04.269857229 -0700 +++ new/src/hotspot/share/gc/shared/threadLocalAllocBuffer.hpp 2017-09-22 13:33:03.921858376 -0700 @@ -132,7 +132,6 @@ HeapWord* end() const { return _end; } HeapWord* slow_path_end() const { return _slow_path_end; } HeapWord* actual_end() const { return _actual_end; } - HeapWord* hard_end(); HeapWord* top() const { return _top; } HeapWord* pf_top() const { return _pf_top; } @@ -180,7 +179,8 @@ void fill(HeapWord* start, HeapWord* top, size_t new_size); void initialize(); - void pick_next_sample(); + void pick_next_sample(size_t diff = 0); + void set_sample_end(); void set_back_actual_end(); void handle_sample(Thread* thread, HeapWord* result, size_t size); size_t bytes_until_sample() { return _bytes_until_sample; } --- old/src/hotspot/share/prims/jvmti.xml 2017-09-22 13:33:05.165854275 -0700 +++ new/src/hotspot/share/prims/jvmti.xml 2017-09-22 13:33:04.817855423 -0700 @@ -11640,6 +11640,9 @@ The monitoring rate used for sampling. The sampler will use a statistical approach to provide in average sampling every allocated bytes. + + Note: a low monitoring rate will incur a higher overhead, therefore, the sampler should + only be used when knowing it may impact performance. --- old/src/hotspot/share/prims/jvmtiHeapTransition.hpp 2017-09-22 13:33:06.305850517 -0700 +++ new/src/hotspot/share/prims/jvmtiHeapTransition.hpp 2017-09-22 13:33:05.961851651 -0700 @@ -25,6 +25,8 @@ #ifndef SHARE_VM_PRIMS_JVMTIHEAPSAMPLING_HPP #define SHARE_VM_PRIMS_JVMTIHEAPSAMPLING_HPP +#include "runtime/interfaceSupport.hpp" + // A RAII class that handles transitions from the agent into the VM. class HeapThreadTransition : StackObj { private: --- old/src/hotspot/share/runtime/heapMonitoring.cpp 2017-09-22 13:33:07.101847893 -0700 +++ new/src/hotspot/share/runtime/heapMonitoring.cpp 2017-09-22 13:33:06.757849027 -0700 @@ -23,9 +23,11 @@ */ #include "precompiled.hpp" -#include "prims/forte.hpp" -#include "runtime/heapMonitoring.hpp" +#include "gc/shared/collectedHeap.hpp" +#include "memory/universe.hpp" +#include "runtime/heapMonitoring.hpp" +#include "runtime/vframe.hpp" static const int MaxStackDepth = 64; @@ -209,7 +211,8 @@ } static void reset_stack_trace_storage() { - delete internal_storage, internal_storage = NULL; + delete internal_storage; + internal_storage = NULL; } bool is_initialized() { @@ -407,8 +410,8 @@ 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->do_object_b(value)) { + if (Universe::heap()->is_in_reserved(value) + && 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)); @@ -647,20 +650,6 @@ StackTraceStorage::storage()->accumulate_sample_rate(rate); } -// Called from the interpreter and C1 -void HeapMonitoring::object_alloc_unsized(oopDesc* o) { - JavaThread *thread = static_cast(Thread::current()); - object_alloc_do_sample(thread, o, o->size() << LogHeapWordSize); -} - -void HeapMonitoring::object_alloc(oopDesc* o, intx byte_size) { - JavaThread *thread = static_cast(Thread::current()); - assert(o->size() << LogHeapWordSize == static_cast(byte_size), - "Object size is incorrect."); - 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); --- old/src/hotspot/share/runtime/heapMonitoring.hpp 2017-09-22 13:33:07.905845242 -0700 +++ new/src/hotspot/share/runtime/heapMonitoring.hpp 2017-09-22 13:33:07.557846389 -0700 @@ -68,24 +68,34 @@ } public: + /* + * General note: currently none of these methods are deemed thread-safe. + */ + + // First method called by user to start the profiler: + // - Note: the lower the monitoring rate, the higher the overhead incurred. + static void initialize_profiling(jint monitoring_rate, jint max_storage); + + // Pick the next sample for a given size_t pointer using a geometric variable + // with specified mean. The specified mean is provided via the + // initialize_profiling method. static void pick_next_sample(size_t *ptr); + // Get live/garbage traces and provide a method to release the traces. static void get_live_traces(jvmtiStackTraces* stack_traces); - static void get_sampling_statistics(jvmtiHeapSamplingStats* stats); 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 void get_sampling_statistics(jvmtiHeapSamplingStats* stats); static void stop_profiling(); + + // Is the profiler initialized and where is the address to the initialized + // boolean. 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. + // Called when o is to be sampled from a given thread and a given size. 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, --- /dev/null 2017-07-27 09:13:36.261507574 -0700 +++ new/test/hotspot/jtreg/serviceability/jvmti/HeapMonitor/MyPackage/HeapMonitorStatCorrectnessTest.java 2017-09-22 13:33:08.413843567 -0700 @@ -0,0 +1,105 @@ +/* + * 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 Statistics + * @build Frame + * @compile HeapMonitorStatCorrectnessTest.java + * @run main/othervm/native -agentlib:HeapMonitor MyPackage.HeapMonitorStatCorrectnessTest + */ + +import java.io.PrintStream; + +public class HeapMonitorStatCorrectnessTest { + + 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; + } + } + + // Do 100000 iterations and expect maxIteration / multiplier samples. + public static final int maxIteration = 100000; + public static int array[]; + + native static int statsNull(); + native static int statsHaveSamples(int expected, int percentError); + native static int enableSampling(int rate); + native static int disableSampling(); + + + private static void allocate(int size) { + System.out.println("With a size of " + size + ", execute " + maxIteration + " iterations"); + for (int j = 0; j < maxIteration; j++) { + array = new int[size]; + } + } + + public static void main(String[] args) { + int sizes[] = {1000, 10000, 100000}; + + for (int i = 0; i < sizes.length; i++) { + int currentSize = sizes[i]; + System.out.println("Testing size " + currentSize); + + // 111 is as good a number as any. + final int samplingMultiplier = 111; + enableSampling(samplingMultiplier * currentSize); + + if (statsNull() == 0) { + throw new RuntimeException("Statistics should be null to begin with."); + } + + allocate(currentSize); + + // For simplifications, we ignore the array memory usage for array internals (with the array + // sizes requested, it should be a negligible oversight). + // + // That means that with maxIterations, the loop in the method allocate requests: + // maxIterations * currentSize * 4 bytes (4 for integers) + // + // Via the enable sampling, the code requests a sample every samplingMultiplier * currentSize bytes. + // + // Therefore, the expected sample number is: + // (maxIterations * currentSize * 4) / (samplingMultiplier * currentSize); + double expected = maxIteration; + expected *= 4; + expected /= samplingMultiplier; + + // 10% error ensures a sanity test without becoming flaky. + if (statsHaveSamples((int) expected, 10) != 0) { + throw new RuntimeException("Statistics should show about " + expected + " samples."); + } + + disableSampling(); + } + } +} --- /dev/null 2017-07-27 09:13:36.261507574 -0700 +++ new/test/hotspot/jtreg/serviceability/jvmti/HeapMonitor/MyPackage/HeapMonitorStatSimpleTest.java 2017-09-22 13:33:09.233840864 -0700 @@ -0,0 +1,88 @@ +/* + * 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 Statistics + * @build Frame + * @compile HeapMonitorStatSimpleTest.java + * @run main/othervm/native -agentlib:HeapMonitor MyPackage.HeapMonitorStatSimpleTest + */ + +import java.io.PrintStream; + +public class HeapMonitorStatSimpleTest { + + 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 statsNull(); + native static int enableSampling(); + + 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 wrapper() { + int sum = 0; + for (int j = 0; j < 1000; j++) { + sum += helper(); + } + System.out.println(sum); + } + + public static void main(String[] args) { + if (statsNull() == 0) { + throw new RuntimeException("Statistics should be null to begin with."); + } + + enableSampling(); + wrapper(); + + if (statsNull() != 0) { + throw new RuntimeException("Statistics should not be null now."); + } + } +}