8306441: Two phase segmented heap dump
Co-authored-by: Kevin Walls <kevinw@openjdk.org> Reviewed-by: amenkov, kevinw
This commit is contained in:
parent
515add88ed
commit
31a307f2fb
@ -90,6 +90,7 @@ class outputStream;
|
|||||||
LOG_TAG(handshake) \
|
LOG_TAG(handshake) \
|
||||||
LOG_TAG(hashtables) \
|
LOG_TAG(hashtables) \
|
||||||
LOG_TAG(heap) \
|
LOG_TAG(heap) \
|
||||||
|
LOG_TAG(heapdump) \
|
||||||
NOT_PRODUCT(LOG_TAG(heapsampling)) \
|
NOT_PRODUCT(LOG_TAG(heapsampling)) \
|
||||||
LOG_TAG(humongous) \
|
LOG_TAG(humongous) \
|
||||||
LOG_TAG(ihop) \
|
LOG_TAG(ihop) \
|
||||||
|
@ -321,6 +321,7 @@ class Thread: public ThreadShadow {
|
|||||||
virtual bool is_Named_thread() const { return false; }
|
virtual bool is_Named_thread() const { return false; }
|
||||||
virtual bool is_Worker_thread() const { return false; }
|
virtual bool is_Worker_thread() const { return false; }
|
||||||
virtual bool is_JfrSampler_thread() const { return false; }
|
virtual bool is_JfrSampler_thread() const { return false; }
|
||||||
|
virtual bool is_AttachListener_thread() const { return false; }
|
||||||
virtual bool is_monitor_deflation_thread() const { return false; }
|
virtual bool is_monitor_deflation_thread() const { return false; }
|
||||||
|
|
||||||
// Can this thread make Java upcalls
|
// Can this thread make Java upcalls
|
||||||
|
@ -48,6 +48,7 @@
|
|||||||
template(ZombieAll) \
|
template(ZombieAll) \
|
||||||
template(Verify) \
|
template(Verify) \
|
||||||
template(HeapDumper) \
|
template(HeapDumper) \
|
||||||
|
template(HeapDumpMerge) \
|
||||||
template(CollectForMetadataAllocation) \
|
template(CollectForMetadataAllocation) \
|
||||||
template(CollectForCodeCacheAllocation) \
|
template(CollectForCodeCacheAllocation) \
|
||||||
template(GC_HeapInspection) \
|
template(GC_HeapInspection) \
|
||||||
|
@ -110,6 +110,7 @@
|
|||||||
#include "runtime/vframeArray.hpp"
|
#include "runtime/vframeArray.hpp"
|
||||||
#include "runtime/vmStructs.hpp"
|
#include "runtime/vmStructs.hpp"
|
||||||
#include "runtime/vm_version.hpp"
|
#include "runtime/vm_version.hpp"
|
||||||
|
#include "services/attachListener.hpp"
|
||||||
#include "utilities/globalDefinitions.hpp"
|
#include "utilities/globalDefinitions.hpp"
|
||||||
#include "utilities/macros.hpp"
|
#include "utilities/macros.hpp"
|
||||||
#include "utilities/vmError.hpp"
|
#include "utilities/vmError.hpp"
|
||||||
@ -1262,6 +1263,7 @@
|
|||||||
declare_type(NotificationThread, JavaThread) \
|
declare_type(NotificationThread, JavaThread) \
|
||||||
declare_type(CompilerThread, JavaThread) \
|
declare_type(CompilerThread, JavaThread) \
|
||||||
declare_type(StringDedupThread, JavaThread) \
|
declare_type(StringDedupThread, JavaThread) \
|
||||||
|
declare_type(AttachListenerThread, JavaThread) \
|
||||||
declare_toplevel_type(OSThread) \
|
declare_toplevel_type(OSThread) \
|
||||||
declare_toplevel_type(JavaFrameAnchor) \
|
declare_toplevel_type(JavaFrameAnchor) \
|
||||||
\
|
\
|
||||||
|
@ -244,15 +244,12 @@ jint dump_heap(AttachOperation* op, outputStream* out) {
|
|||||||
return JNI_ERR;
|
return JNI_ERR;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Parallel thread number for heap dump, initialize based on active processor count.
|
|
||||||
// Note the real number of threads used is also determined by active workers and compression
|
|
||||||
// backend thread number. See heapDumper.cpp.
|
|
||||||
uint parallel_thread_num = MAX2<uint>(1, (uint)os::initial_active_processor_count() * 3 / 8);
|
|
||||||
// Request a full GC before heap dump if live_objects_only = true
|
// Request a full GC before heap dump if live_objects_only = true
|
||||||
// This helps reduces the amount of unreachable objects in the dump
|
// This helps reduces the amount of unreachable objects in the dump
|
||||||
// and makes it easier to browse.
|
// and makes it easier to browse.
|
||||||
HeapDumper dumper(live_objects_only /* request GC */);
|
HeapDumper dumper(live_objects_only /* request GC */);
|
||||||
dumper.dump(path, out, (int)level, false, (uint)parallel_thread_num);
|
dumper.dump(path, out, (int)level, false, HeapDumper::default_num_of_dump_threads());
|
||||||
}
|
}
|
||||||
return JNI_OK;
|
return JNI_OK;
|
||||||
}
|
}
|
||||||
@ -375,7 +372,7 @@ static AttachOperationFunctionInfo funcs[] = {
|
|||||||
// from the queue, examines the operation name (command), and dispatches
|
// from the queue, examines the operation name (command), and dispatches
|
||||||
// to the corresponding function to perform the operation.
|
// to the corresponding function to perform the operation.
|
||||||
|
|
||||||
static void attach_listener_thread_entry(JavaThread* thread, TRAPS) {
|
void AttachListenerThread::thread_entry(JavaThread* thread, TRAPS) {
|
||||||
os::set_priority(thread, NearMaxPriority);
|
os::set_priority(thread, NearMaxPriority);
|
||||||
|
|
||||||
assert(thread == Thread::current(), "Must be");
|
assert(thread == Thread::current(), "Must be");
|
||||||
@ -460,7 +457,7 @@ void AttachListener::init() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
JavaThread* thread = new JavaThread(&attach_listener_thread_entry);
|
JavaThread* thread = new AttachListenerThread();
|
||||||
JavaThread::vm_exit_on_osthread_failure(thread);
|
JavaThread::vm_exit_on_osthread_failure(thread);
|
||||||
|
|
||||||
JavaThread::start_internal_daemon(THREAD, thread, thread_oop, NoPriority);
|
JavaThread::start_internal_daemon(THREAD, thread, thread_oop, NoPriority);
|
||||||
|
@ -28,6 +28,7 @@
|
|||||||
#include "memory/allStatic.hpp"
|
#include "memory/allStatic.hpp"
|
||||||
#include "runtime/atomic.hpp"
|
#include "runtime/atomic.hpp"
|
||||||
#include "runtime/globals.hpp"
|
#include "runtime/globals.hpp"
|
||||||
|
#include "runtime/javaThread.inline.hpp"
|
||||||
#include "utilities/debug.hpp"
|
#include "utilities/debug.hpp"
|
||||||
#include "utilities/exceptions.hpp"
|
#include "utilities/exceptions.hpp"
|
||||||
#include "utilities/globalDefinitions.hpp"
|
#include "utilities/globalDefinitions.hpp"
|
||||||
@ -58,6 +59,15 @@ enum AttachListenerState {
|
|||||||
AL_INITIALIZED
|
AL_INITIALIZED
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class AttachListenerThread : public JavaThread {
|
||||||
|
private:
|
||||||
|
static void thread_entry(JavaThread* thread, TRAPS);
|
||||||
|
|
||||||
|
public:
|
||||||
|
AttachListenerThread() : JavaThread(&AttachListenerThread::thread_entry) {}
|
||||||
|
bool is_AttachListener_thread() const { return true; }
|
||||||
|
};
|
||||||
|
|
||||||
class AttachListener: AllStatic {
|
class AttachListener: AllStatic {
|
||||||
public:
|
public:
|
||||||
static void vm_start() NOT_SERVICES_RETURN;
|
static void vm_start() NOT_SERVICES_RETURN;
|
||||||
|
@ -469,15 +469,20 @@ HeapDumpDCmd::HeapDumpDCmd(outputStream* output, bool heap) :
|
|||||||
"using the given compression level. 1 (recommended) is the fastest, "
|
"using the given compression level. 1 (recommended) is the fastest, "
|
||||||
"9 the strongest compression.", "INT", false, "1"),
|
"9 the strongest compression.", "INT", false, "1"),
|
||||||
_overwrite("-overwrite", "If specified, the dump file will be overwritten if it exists",
|
_overwrite("-overwrite", "If specified, the dump file will be overwritten if it exists",
|
||||||
"BOOLEAN", false, "false") {
|
"BOOLEAN", false, "false"),
|
||||||
|
_parallel("-parallel", "Number of parallel threads to use for heap dump. The VM "
|
||||||
|
"will try to use the specified number of threads, but might use fewer.",
|
||||||
|
"INT", false, "1") {
|
||||||
_dcmdparser.add_dcmd_option(&_all);
|
_dcmdparser.add_dcmd_option(&_all);
|
||||||
_dcmdparser.add_dcmd_argument(&_filename);
|
_dcmdparser.add_dcmd_argument(&_filename);
|
||||||
_dcmdparser.add_dcmd_option(&_gzip);
|
_dcmdparser.add_dcmd_option(&_gzip);
|
||||||
_dcmdparser.add_dcmd_option(&_overwrite);
|
_dcmdparser.add_dcmd_option(&_overwrite);
|
||||||
|
_dcmdparser.add_dcmd_option(&_parallel);
|
||||||
}
|
}
|
||||||
|
|
||||||
void HeapDumpDCmd::execute(DCmdSource source, TRAPS) {
|
void HeapDumpDCmd::execute(DCmdSource source, TRAPS) {
|
||||||
jlong level = -1; // -1 means no compression.
|
jlong level = -1; // -1 means no compression.
|
||||||
|
jlong parallel = HeapDumper::default_num_of_dump_threads();
|
||||||
|
|
||||||
if (_gzip.is_set()) {
|
if (_gzip.is_set()) {
|
||||||
level = _gzip.value();
|
level = _gzip.value();
|
||||||
@ -488,11 +493,23 @@ void HeapDumpDCmd::execute(DCmdSource source, TRAPS) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_parallel.is_set()) {
|
||||||
|
parallel = _parallel.value();
|
||||||
|
|
||||||
|
if (parallel < 0) {
|
||||||
|
output()->print_cr("Invalid number of parallel dump threads.");
|
||||||
|
return;
|
||||||
|
} else if (parallel == 0) {
|
||||||
|
// 0 implies to disable parallel heap dump, in such case, we use serial dump instead
|
||||||
|
parallel = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Request a full GC before heap dump if _all is false
|
// Request a full GC before heap dump if _all is false
|
||||||
// This helps reduces the amount of unreachable objects in the dump
|
// This helps reduces the amount of unreachable objects in the dump
|
||||||
// and makes it easier to browse.
|
// and makes it easier to browse.
|
||||||
HeapDumper dumper(!_all.value() /* request GC if _all is false*/);
|
HeapDumper dumper(!_all.value() /* request GC if _all is false*/);
|
||||||
dumper.dump(_filename.value(), output(), (int) level, _overwrite.value());
|
dumper.dump(_filename.value(), output(), (int) level, _overwrite.value(), (uint)parallel);
|
||||||
}
|
}
|
||||||
|
|
||||||
ClassHistogramDCmd::ClassHistogramDCmd(outputStream* output, bool heap) :
|
ClassHistogramDCmd::ClassHistogramDCmd(outputStream* output, bool heap) :
|
||||||
|
@ -320,8 +320,9 @@ protected:
|
|||||||
DCmdArgument<bool> _all;
|
DCmdArgument<bool> _all;
|
||||||
DCmdArgument<jlong> _gzip;
|
DCmdArgument<jlong> _gzip;
|
||||||
DCmdArgument<bool> _overwrite;
|
DCmdArgument<bool> _overwrite;
|
||||||
|
DCmdArgument<jlong> _parallel;
|
||||||
public:
|
public:
|
||||||
static int num_arguments() { return 4; }
|
static int num_arguments() { return 5; }
|
||||||
HeapDumpDCmd(outputStream* output, bool heap);
|
HeapDumpDCmd(outputStream* output, bool heap);
|
||||||
static const char* name() {
|
static const char* name() {
|
||||||
return "GC.heap_dump";
|
return "GC.heap_dump";
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -29,20 +29,9 @@
|
|||||||
#include "oops/oop.hpp"
|
#include "oops/oop.hpp"
|
||||||
#include "runtime/os.hpp"
|
#include "runtime/os.hpp"
|
||||||
|
|
||||||
// HeapDumper is used to dump the java heap to file in HPROF binary format:
|
|
||||||
//
|
|
||||||
// { HeapDumper dumper(true /* full GC before heap dump */);
|
|
||||||
// if (dumper.dump("/export/java.hprof")) {
|
|
||||||
// ResourceMark rm;
|
|
||||||
// tty->print_cr("Dump failed: %s", dumper.error_as_C_string());
|
|
||||||
// } else {
|
|
||||||
// // dump succeeded
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
|
|
||||||
class outputStream;
|
class outputStream;
|
||||||
|
|
||||||
|
// HeapDumper is used to dump the java heap to file in HPROF binary format
|
||||||
class HeapDumper : public StackObj {
|
class HeapDumper : public StackObj {
|
||||||
private:
|
private:
|
||||||
char* _error;
|
char* _error;
|
||||||
@ -80,6 +69,11 @@ class HeapDumper : public StackObj {
|
|||||||
static void dump_heap() NOT_SERVICES_RETURN;
|
static void dump_heap() NOT_SERVICES_RETURN;
|
||||||
|
|
||||||
static void dump_heap_from_oome() NOT_SERVICES_RETURN;
|
static void dump_heap_from_oome() NOT_SERVICES_RETURN;
|
||||||
|
|
||||||
|
// Parallel thread number for heap dump, initialize based on active processor count.
|
||||||
|
static uint default_num_of_dump_threads() {
|
||||||
|
return MAX2<uint>(1, (uint)os::initial_active_processor_count() * 3 / 8);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // SHARE_SERVICES_HEAPDUMPER_HPP
|
#endif // SHARE_SERVICES_HEAPDUMPER_HPP
|
||||||
|
@ -134,363 +134,3 @@ char const* GZipCompressor::compress(char* in, size_t in_size, char* out, size_t
|
|||||||
|
|
||||||
return msg;
|
return msg;
|
||||||
}
|
}
|
||||||
|
|
||||||
WorkList::WorkList() {
|
|
||||||
_head._next = &_head;
|
|
||||||
_head._prev = &_head;
|
|
||||||
}
|
|
||||||
|
|
||||||
void WorkList::insert(WriteWork* before, WriteWork* work) {
|
|
||||||
work->_prev = before;
|
|
||||||
work->_next = before->_next;
|
|
||||||
before->_next = work;
|
|
||||||
work->_next->_prev = work;
|
|
||||||
}
|
|
||||||
|
|
||||||
WriteWork* WorkList::remove(WriteWork* work) {
|
|
||||||
if (work != nullptr) {
|
|
||||||
assert(work->_next != work, "Invalid next");
|
|
||||||
assert(work->_prev != work, "Invalid prev");
|
|
||||||
work->_prev->_next = work->_next;;
|
|
||||||
work->_next->_prev = work->_prev;
|
|
||||||
work->_next = nullptr;
|
|
||||||
work->_prev = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
return work;
|
|
||||||
}
|
|
||||||
|
|
||||||
void WorkList::add_by_id(WriteWork* work) {
|
|
||||||
if (is_empty()) {
|
|
||||||
add_first(work);
|
|
||||||
} else {
|
|
||||||
WriteWork* last_curr = &_head;
|
|
||||||
WriteWork* curr = _head._next;
|
|
||||||
|
|
||||||
while (curr->_id < work->_id) {
|
|
||||||
last_curr = curr;
|
|
||||||
curr = curr->_next;
|
|
||||||
|
|
||||||
if (curr == &_head) {
|
|
||||||
add_last(work);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
insert(last_curr, work);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
CompressionBackend::CompressionBackend(AbstractWriter* writer,
|
|
||||||
AbstractCompressor* compressor, size_t block_size, size_t max_waste) :
|
|
||||||
_active(false),
|
|
||||||
_err(nullptr),
|
|
||||||
_nr_of_threads(0),
|
|
||||||
_works_created(0),
|
|
||||||
_work_creation_failed(false),
|
|
||||||
_id_to_write(0),
|
|
||||||
_next_id(0),
|
|
||||||
_in_size(block_size),
|
|
||||||
_max_waste(max_waste),
|
|
||||||
_out_size(0),
|
|
||||||
_tmp_size(0),
|
|
||||||
_written(0),
|
|
||||||
_writer(writer),
|
|
||||||
_compressor(compressor),
|
|
||||||
_lock(new (std::nothrow) PaddedMonitor(Mutex::nosafepoint, "HProfCompressionBackend_lock")) {
|
|
||||||
if (_writer == nullptr) {
|
|
||||||
set_error("Could not allocate writer");
|
|
||||||
} else if (_lock == nullptr) {
|
|
||||||
set_error("Could not allocate lock");
|
|
||||||
} else {
|
|
||||||
set_error(_writer->open_writer());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_compressor != nullptr) {
|
|
||||||
set_error(_compressor->init(_in_size, &_out_size, &_tmp_size));
|
|
||||||
}
|
|
||||||
|
|
||||||
_current = allocate_work(_in_size, _out_size, _tmp_size);
|
|
||||||
|
|
||||||
if (_current == nullptr) {
|
|
||||||
set_error("Could not allocate memory for buffer");
|
|
||||||
}
|
|
||||||
|
|
||||||
_active = (_err == nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
CompressionBackend::~CompressionBackend() {
|
|
||||||
assert(!_active, "Must not be active by now");
|
|
||||||
assert(_nr_of_threads == 0, "Must have no active threads");
|
|
||||||
assert(_to_compress.is_empty() && _finished.is_empty(), "Still work to do");
|
|
||||||
|
|
||||||
free_work_list(&_unused);
|
|
||||||
free_work(_current);
|
|
||||||
assert(_works_created == 0, "All work must have been freed");
|
|
||||||
|
|
||||||
delete _compressor;
|
|
||||||
delete _writer;
|
|
||||||
delete _lock;
|
|
||||||
}
|
|
||||||
|
|
||||||
void CompressionBackend::flush_buffer(MonitorLocker* ml) {
|
|
||||||
|
|
||||||
// Make sure we write the last partially filled buffer.
|
|
||||||
if ((_current != nullptr) && (_current->_in_used > 0)) {
|
|
||||||
_current->_id = _next_id++;
|
|
||||||
_to_compress.add_last(_current);
|
|
||||||
_current = nullptr;
|
|
||||||
ml->notify_all();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait for the threads to drain the compression work list and do some work yourself.
|
|
||||||
while (!_to_compress.is_empty()) {
|
|
||||||
do_foreground_work();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void CompressionBackend::flush_buffer() {
|
|
||||||
assert(_active, "Must be active");
|
|
||||||
|
|
||||||
MonitorLocker ml(_lock, Mutex::_no_safepoint_check_flag);
|
|
||||||
flush_buffer(&ml);
|
|
||||||
}
|
|
||||||
|
|
||||||
void CompressionBackend::deactivate() {
|
|
||||||
assert(_active, "Must be active");
|
|
||||||
|
|
||||||
MonitorLocker ml(_lock, Mutex::_no_safepoint_check_flag);
|
|
||||||
flush_buffer(&ml);
|
|
||||||
|
|
||||||
_active = false;
|
|
||||||
ml.notify_all();
|
|
||||||
}
|
|
||||||
|
|
||||||
void CompressionBackend::thread_loop() {
|
|
||||||
{
|
|
||||||
MonitorLocker ml(_lock, Mutex::_no_safepoint_check_flag);
|
|
||||||
_nr_of_threads++;
|
|
||||||
}
|
|
||||||
|
|
||||||
WriteWork* work;
|
|
||||||
while ((work = get_work()) != nullptr) {
|
|
||||||
do_compress(work);
|
|
||||||
finish_work(work);
|
|
||||||
}
|
|
||||||
|
|
||||||
MonitorLocker ml(_lock, Mutex::_no_safepoint_check_flag);
|
|
||||||
_nr_of_threads--;
|
|
||||||
assert(_nr_of_threads >= 0, "Too many threads finished");
|
|
||||||
}
|
|
||||||
|
|
||||||
void CompressionBackend::set_error(char const* new_error) {
|
|
||||||
if ((new_error != nullptr) && (_err == nullptr)) {
|
|
||||||
_err = new_error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
WriteWork* CompressionBackend::allocate_work(size_t in_size, size_t out_size,
|
|
||||||
size_t tmp_size) {
|
|
||||||
WriteWork* result = (WriteWork*) os::malloc(sizeof(WriteWork), mtInternal);
|
|
||||||
|
|
||||||
if (result == nullptr) {
|
|
||||||
_work_creation_failed = true;
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
_works_created++;
|
|
||||||
result->_in = (char*) os::malloc(in_size, mtInternal);
|
|
||||||
result->_in_max = in_size;
|
|
||||||
result->_in_used = 0;
|
|
||||||
result->_out = nullptr;
|
|
||||||
result->_tmp = nullptr;
|
|
||||||
|
|
||||||
if (result->_in == nullptr) {
|
|
||||||
goto fail;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (out_size > 0) {
|
|
||||||
result->_out = (char*) os::malloc(out_size, mtInternal);
|
|
||||||
result->_out_used = 0;
|
|
||||||
result->_out_max = out_size;
|
|
||||||
|
|
||||||
if (result->_out == nullptr) {
|
|
||||||
goto fail;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tmp_size > 0) {
|
|
||||||
result->_tmp = (char*) os::malloc(tmp_size, mtInternal);
|
|
||||||
result->_tmp_max = tmp_size;
|
|
||||||
|
|
||||||
if (result->_tmp == nullptr) {
|
|
||||||
goto fail;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
|
|
||||||
fail:
|
|
||||||
free_work(result);
|
|
||||||
_work_creation_failed = true;
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
void CompressionBackend::free_work(WriteWork* work) {
|
|
||||||
if (work != nullptr) {
|
|
||||||
os::free(work->_in);
|
|
||||||
os::free(work->_out);
|
|
||||||
os::free(work->_tmp);
|
|
||||||
os::free(work);
|
|
||||||
--_works_created;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void CompressionBackend::free_work_list(WorkList* list) {
|
|
||||||
while (!list->is_empty()) {
|
|
||||||
free_work(list->remove_first());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void CompressionBackend::do_foreground_work() {
|
|
||||||
assert(!_to_compress.is_empty(), "Must have work to do");
|
|
||||||
assert(_lock->owned_by_self(), "Must have the lock");
|
|
||||||
|
|
||||||
WriteWork* work = _to_compress.remove_first();
|
|
||||||
MutexUnlocker mu(_lock, Mutex::_no_safepoint_check_flag);
|
|
||||||
do_compress(work);
|
|
||||||
finish_work(work);
|
|
||||||
}
|
|
||||||
|
|
||||||
WriteWork* CompressionBackend::get_work() {
|
|
||||||
MonitorLocker ml(_lock, Mutex::_no_safepoint_check_flag);
|
|
||||||
|
|
||||||
while (_active && _to_compress.is_empty()) {
|
|
||||||
ml.wait();
|
|
||||||
}
|
|
||||||
|
|
||||||
return _to_compress.remove_first();
|
|
||||||
}
|
|
||||||
|
|
||||||
void CompressionBackend::flush_external_buffer(char* buffer, size_t used, size_t max) {
|
|
||||||
assert(buffer != nullptr && used != 0 && max != 0, "Invalid data send to compression backend");
|
|
||||||
assert(_active == true, "Backend must be active when flushing external buffer");
|
|
||||||
char* buf;
|
|
||||||
size_t tmp_used = 0;
|
|
||||||
size_t tmp_max = 0;
|
|
||||||
|
|
||||||
MonitorLocker ml(_lock, Mutex::_no_safepoint_check_flag);
|
|
||||||
// First try current buffer. Use it if empty.
|
|
||||||
if (_current->_in_used == 0) {
|
|
||||||
buf = _current->_in;
|
|
||||||
} else {
|
|
||||||
// If current buffer is not clean, flush it.
|
|
||||||
MutexUnlocker ml(_lock, Mutex::_no_safepoint_check_flag);
|
|
||||||
get_new_buffer(&buf, &tmp_used, &tmp_max, true);
|
|
||||||
}
|
|
||||||
assert (_current->_in != nullptr && _current->_in_max >= max &&
|
|
||||||
_current->_in_used == 0, "Invalid buffer from compression backend");
|
|
||||||
// Copy data to backend buffer.
|
|
||||||
memcpy(buf, buffer, used);
|
|
||||||
|
|
||||||
assert(_current->_in == buf, "Must be current");
|
|
||||||
_current->_in_used += used;
|
|
||||||
}
|
|
||||||
|
|
||||||
void CompressionBackend::get_new_buffer(char** buffer, size_t* used, size_t* max, bool force_reset) {
|
|
||||||
if (_active) {
|
|
||||||
MonitorLocker ml(_lock, Mutex::_no_safepoint_check_flag);
|
|
||||||
if (*used > 0 || force_reset) {
|
|
||||||
_current->_in_used += *used;
|
|
||||||
// Check if we do not waste more than _max_waste. If yes, write the buffer.
|
|
||||||
// Otherwise return the rest of the buffer as the new buffer.
|
|
||||||
if (_current->_in_max - _current->_in_used <= _max_waste || force_reset) {
|
|
||||||
_current->_id = _next_id++;
|
|
||||||
_to_compress.add_last(_current);
|
|
||||||
_current = nullptr;
|
|
||||||
ml.notify_all();
|
|
||||||
} else {
|
|
||||||
*buffer = _current->_in + _current->_in_used;
|
|
||||||
*used = 0;
|
|
||||||
*max = _current->_in_max - _current->_in_used;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
while ((_current == nullptr) && _unused.is_empty() && _active) {
|
|
||||||
// Add more work objects if needed.
|
|
||||||
if (!_work_creation_failed && (_works_created <= _nr_of_threads)) {
|
|
||||||
WriteWork* work = allocate_work(_in_size, _out_size, _tmp_size);
|
|
||||||
|
|
||||||
if (work != nullptr) {
|
|
||||||
_unused.add_first(work);
|
|
||||||
}
|
|
||||||
} else if (!_to_compress.is_empty() && (_nr_of_threads == 0)) {
|
|
||||||
do_foreground_work();
|
|
||||||
} else {
|
|
||||||
ml.wait();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_current == nullptr) {
|
|
||||||
_current = _unused.remove_first();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_current != nullptr) {
|
|
||||||
_current->_in_used = 0;
|
|
||||||
_current->_out_used = 0;
|
|
||||||
*buffer = _current->_in;
|
|
||||||
*used = 0;
|
|
||||||
*max = _current->_in_max;
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
*buffer = nullptr;
|
|
||||||
*used = 0;
|
|
||||||
*max = 0;
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
void CompressionBackend::do_compress(WriteWork* work) {
|
|
||||||
if (_compressor != nullptr) {
|
|
||||||
char const* msg = _compressor->compress(work->_in, work->_in_used, work->_out,
|
|
||||||
work->_out_max,
|
|
||||||
work->_tmp, _tmp_size, &work->_out_used);
|
|
||||||
|
|
||||||
if (msg != nullptr) {
|
|
||||||
MutexLocker ml(_lock, Mutex::_no_safepoint_check_flag);
|
|
||||||
set_error(msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void CompressionBackend::finish_work(WriteWork* work) {
|
|
||||||
MonitorLocker ml(_lock, Mutex::_no_safepoint_check_flag);
|
|
||||||
|
|
||||||
_finished.add_by_id(work);
|
|
||||||
|
|
||||||
// Write all finished works as far as we can.
|
|
||||||
while (!_finished.is_empty() && (_finished.first()->_id == _id_to_write)) {
|
|
||||||
WriteWork* to_write = _finished.remove_first();
|
|
||||||
size_t size = _compressor == nullptr ? to_write->_in_used : to_write->_out_used;
|
|
||||||
char* p = _compressor == nullptr ? to_write->_in : to_write->_out;
|
|
||||||
char const* msg = nullptr;
|
|
||||||
|
|
||||||
if (_err == nullptr) {
|
|
||||||
_written += size;
|
|
||||||
MutexUnlocker mu(_lock, Mutex::_no_safepoint_check_flag);
|
|
||||||
msg = _writer->write_buf(p, (ssize_t) size);
|
|
||||||
}
|
|
||||||
|
|
||||||
set_error(msg);
|
|
||||||
_unused.add_first(to_write);
|
|
||||||
_id_to_write++;
|
|
||||||
}
|
|
||||||
|
|
||||||
ml.notify_all();
|
|
||||||
}
|
|
||||||
|
@ -74,6 +74,10 @@ public:
|
|||||||
|
|
||||||
// Does the write. Returns null on success and a static error message otherwise.
|
// Does the write. Returns null on success and a static error message otherwise.
|
||||||
virtual char const* write_buf(char* buf, ssize_t size);
|
virtual char const* write_buf(char* buf, ssize_t size);
|
||||||
|
|
||||||
|
const char* get_file_path() { return _path; }
|
||||||
|
|
||||||
|
bool is_overwrite() const { return _overwrite; }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@ -97,145 +101,4 @@ public:
|
|||||||
char* tmp, size_t tmp_size, size_t* compressed_size);
|
char* tmp, size_t tmp_size, size_t* compressed_size);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
// The data needed to write a single buffer (and compress it optionally).
|
|
||||||
struct WriteWork {
|
|
||||||
// The id of the work.
|
|
||||||
int64_t _id;
|
|
||||||
|
|
||||||
// The input buffer where the raw data is
|
|
||||||
char* _in;
|
|
||||||
size_t _in_used;
|
|
||||||
size_t _in_max;
|
|
||||||
|
|
||||||
// The output buffer where the compressed data is. Is null when compression is disabled.
|
|
||||||
char* _out;
|
|
||||||
size_t _out_used;
|
|
||||||
size_t _out_max;
|
|
||||||
|
|
||||||
// The temporary space needed for compression. Is null when compression is disabled.
|
|
||||||
char* _tmp;
|
|
||||||
size_t _tmp_max;
|
|
||||||
|
|
||||||
// Used to link WriteWorks into lists.
|
|
||||||
WriteWork* _next;
|
|
||||||
WriteWork* _prev;
|
|
||||||
};
|
|
||||||
|
|
||||||
// A list for works.
|
|
||||||
class WorkList {
|
|
||||||
private:
|
|
||||||
WriteWork _head;
|
|
||||||
|
|
||||||
void insert(WriteWork* before, WriteWork* work);
|
|
||||||
WriteWork* remove(WriteWork* work);
|
|
||||||
|
|
||||||
public:
|
|
||||||
WorkList();
|
|
||||||
|
|
||||||
// Return true if the list is empty.
|
|
||||||
bool is_empty() { return _head._next == &_head; }
|
|
||||||
|
|
||||||
// Adds to the beginning of the list.
|
|
||||||
void add_first(WriteWork* work) { insert(&_head, work); }
|
|
||||||
|
|
||||||
// Adds to the end of the list.
|
|
||||||
void add_last(WriteWork* work) { insert(_head._prev, work); }
|
|
||||||
|
|
||||||
// Adds so the ids are ordered.
|
|
||||||
void add_by_id(WriteWork* work);
|
|
||||||
|
|
||||||
// Returns the first element.
|
|
||||||
WriteWork* first() { return is_empty() ? nullptr : _head._next; }
|
|
||||||
|
|
||||||
// Returns the last element.
|
|
||||||
WriteWork* last() { return is_empty() ? nullptr : _head._prev; }
|
|
||||||
|
|
||||||
// Removes the first element. Returns null if empty.
|
|
||||||
WriteWork* remove_first() { return remove(first()); }
|
|
||||||
|
|
||||||
// Removes the last element. Returns null if empty.
|
|
||||||
WriteWork* remove_last() { return remove(first()); }
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
class Monitor;
|
|
||||||
|
|
||||||
// This class is used by the DumpWriter class. It supplies the DumpWriter with
|
|
||||||
// chunks of memory to write the heap dump data into. When the DumpWriter needs a
|
|
||||||
// new memory chunk, it calls get_new_buffer(), which commits the old chunk used
|
|
||||||
// and returns a new chunk. The old chunk is then added to a queue to be compressed
|
|
||||||
// and then written in the background.
|
|
||||||
class CompressionBackend : StackObj {
|
|
||||||
bool _active;
|
|
||||||
char const * _err;
|
|
||||||
|
|
||||||
int _nr_of_threads;
|
|
||||||
int _works_created;
|
|
||||||
bool _work_creation_failed;
|
|
||||||
|
|
||||||
int64_t _id_to_write;
|
|
||||||
int64_t _next_id;
|
|
||||||
|
|
||||||
size_t _in_size;
|
|
||||||
size_t _max_waste;
|
|
||||||
size_t _out_size;
|
|
||||||
size_t _tmp_size;
|
|
||||||
|
|
||||||
size_t _written;
|
|
||||||
|
|
||||||
AbstractWriter* const _writer;
|
|
||||||
AbstractCompressor* const _compressor;
|
|
||||||
|
|
||||||
Monitor* const _lock;
|
|
||||||
|
|
||||||
WriteWork* _current;
|
|
||||||
WorkList _to_compress;
|
|
||||||
WorkList _unused;
|
|
||||||
WorkList _finished;
|
|
||||||
|
|
||||||
void set_error(char const* new_error);
|
|
||||||
|
|
||||||
WriteWork* allocate_work(size_t in_size, size_t out_size, size_t tmp_size);
|
|
||||||
void free_work(WriteWork* work);
|
|
||||||
void free_work_list(WorkList* list);
|
|
||||||
|
|
||||||
void do_foreground_work();
|
|
||||||
WriteWork* get_work();
|
|
||||||
void do_compress(WriteWork* work);
|
|
||||||
void finish_work(WriteWork* work);
|
|
||||||
void flush_buffer(MonitorLocker* ml);
|
|
||||||
|
|
||||||
public:
|
|
||||||
// compressor can be null if no compression is used.
|
|
||||||
// Takes ownership of the writer and compressor.
|
|
||||||
// block_size is the buffer size of a WriteWork.
|
|
||||||
// max_waste is the maximum number of bytes to leave
|
|
||||||
// empty in the buffer when it is written.
|
|
||||||
CompressionBackend(AbstractWriter* writer, AbstractCompressor* compressor,
|
|
||||||
size_t block_size, size_t max_waste);
|
|
||||||
|
|
||||||
~CompressionBackend();
|
|
||||||
|
|
||||||
size_t get_written() const { return _written; }
|
|
||||||
|
|
||||||
char const* error() const { return _err; }
|
|
||||||
|
|
||||||
// Sets up an internal buffer, fills with external buffer, and sends to compressor.
|
|
||||||
void flush_external_buffer(char* buffer, size_t used, size_t max);
|
|
||||||
|
|
||||||
// Commits the old buffer (using the value in *used) and sets up a new one.
|
|
||||||
void get_new_buffer(char** buffer, size_t* used, size_t* max, bool force_reset = false);
|
|
||||||
|
|
||||||
// The entry point for a worker thread.
|
|
||||||
void thread_loop();
|
|
||||||
|
|
||||||
// Shuts down the backend, releasing all threads.
|
|
||||||
void deactivate();
|
|
||||||
|
|
||||||
// Flush all compressed data in buffer to file
|
|
||||||
void flush_buffer();
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
#endif // SHARE_SERVICES_HEAPDUMPERCOMPRESSION_HPP
|
#endif // SHARE_SERVICES_HEAPDUMPERCOMPRESSION_HPP
|
||||||
|
@ -0,0 +1,41 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2023, Alibaba Group Holding Limited. 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 sun.jvm.hotspot.runtime;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
|
||||||
|
import sun.jvm.hotspot.debugger.Address;
|
||||||
|
|
||||||
|
public class AttachListenerThread extends JavaThread {
|
||||||
|
|
||||||
|
public AttachListenerThread (Address addr) {
|
||||||
|
super(addr);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isJavaThread() { return false; }
|
||||||
|
|
||||||
|
public boolean isAttachListenerThread() { return true; }
|
||||||
|
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (c) 2000, 2021, Oracle and/or its affiliates. All rights reserved.
|
* Copyright (c) 2000, 2023, Oracle and/or its affiliates. All rights reserved.
|
||||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
@ -85,6 +85,7 @@ public class Thread extends VMObject {
|
|||||||
public boolean isWatcherThread() { return false; }
|
public boolean isWatcherThread() { return false; }
|
||||||
public boolean isServiceThread() { return false; }
|
public boolean isServiceThread() { return false; }
|
||||||
public boolean isMonitorDeflationThread() { return false; }
|
public boolean isMonitorDeflationThread() { return false; }
|
||||||
|
public boolean isAttachListenerThread() { return false; }
|
||||||
|
|
||||||
/** Memory operations */
|
/** Memory operations */
|
||||||
public void oopsDo(AddressVisitor oopVisitor) {
|
public void oopsDo(AddressVisitor oopVisitor) {
|
||||||
|
@ -157,6 +157,7 @@ public class Threads {
|
|||||||
virtualConstructor.addMapping("MonitorDeflationThread", MonitorDeflationThread.class);
|
virtualConstructor.addMapping("MonitorDeflationThread", MonitorDeflationThread.class);
|
||||||
virtualConstructor.addMapping("NotificationThread", NotificationThread.class);
|
virtualConstructor.addMapping("NotificationThread", NotificationThread.class);
|
||||||
virtualConstructor.addMapping("StringDedupThread", StringDedupThread.class);
|
virtualConstructor.addMapping("StringDedupThread", StringDedupThread.class);
|
||||||
|
virtualConstructor.addMapping("AttachListenerThread", AttachListenerThread.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Threads() {
|
public Threads() {
|
||||||
@ -164,14 +165,15 @@ public class Threads {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** NOTE: this returns objects of type JavaThread, CompilerThread,
|
/** NOTE: this returns objects of type JavaThread, CompilerThread,
|
||||||
JvmtiAgentThread, NotificationThread, MonitorDeflationThread and ServiceThread.
|
JvmtiAgentThread, NotificationThread, MonitorDeflationThread,
|
||||||
The latter four are subclasses of the former. Most operations
|
StringDedupThread, AttachListenerThread and ServiceThread.
|
||||||
|
The latter seven subclasses of the former. Most operations
|
||||||
(fetching the top frame, etc.) are only allowed to be performed on
|
(fetching the top frame, etc.) are only allowed to be performed on
|
||||||
a "pure" JavaThread. For this reason, {@link
|
a "pure" JavaThread. For this reason, {@link
|
||||||
sun.jvm.hotspot.runtime.JavaThread#isJavaThread} has been
|
sun.jvm.hotspot.runtime.JavaThread#isJavaThread} has been
|
||||||
changed from the definition in the VM (which returns true for
|
changed from the definition in the VM (which returns true for
|
||||||
all of these thread types) to return true for JavaThreads and
|
all of these thread types) to return true for JavaThreads and
|
||||||
false for the four subclasses. FIXME: should reconsider the
|
false for the seven subclasses. FIXME: should reconsider the
|
||||||
inheritance hierarchy; see {@link
|
inheritance hierarchy; see {@link
|
||||||
sun.jvm.hotspot.runtime.JavaThread#isJavaThread}. */
|
sun.jvm.hotspot.runtime.JavaThread#isJavaThread}. */
|
||||||
public JavaThread getJavaThreadAt(int i) {
|
public JavaThread getJavaThreadAt(int i) {
|
||||||
@ -195,7 +197,8 @@ public class Threads {
|
|||||||
return thread;
|
return thread;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new RuntimeException("Unable to deduce type of thread from address " + threadAddr +
|
throw new RuntimeException("Unable to deduce type of thread from address " + threadAddr +
|
||||||
" (expected type JavaThread, CompilerThread, MonitorDeflationThread, ServiceThread or JvmtiAgentThread)", e);
|
" (expected type JavaThread, CompilerThread, MonitorDeflationThread, AttachListenerThread," +
|
||||||
|
" StringDedupThread, NotificationThread, ServiceThread or JvmtiAgentThread)", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,147 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2023, Alibaba Group Holding Limited. 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import jdk.test.lib.Asserts;
|
||||||
|
import jdk.test.lib.JDKToolLauncher;
|
||||||
|
import jdk.test.lib.Utils;
|
||||||
|
import jdk.test.lib.apps.LingeredApp;
|
||||||
|
import jdk.test.lib.dcmd.PidJcmdExecutor;
|
||||||
|
import jdk.test.lib.process.OutputAnalyzer;
|
||||||
|
import jdk.test.lib.process.ProcessTools;
|
||||||
|
|
||||||
|
import jdk.test.lib.hprof.HprofParser;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
* @bug 8306441
|
||||||
|
* @summary Verify the integrity of generated heap dump and capability of parallel dump
|
||||||
|
* @library /test/lib
|
||||||
|
* @run main HeapDumpParallelTest
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class HeapDumpParallelTest {
|
||||||
|
|
||||||
|
private static void checkAndVerify(OutputAnalyzer dcmdOut, LingeredApp app, File heapDumpFile, boolean expectSerial) throws IOException {
|
||||||
|
dcmdOut.shouldHaveExitValue(0);
|
||||||
|
dcmdOut.shouldContain("Heap dump file created");
|
||||||
|
OutputAnalyzer appOut = new OutputAnalyzer(app.getProcessStdout());
|
||||||
|
appOut.shouldContain("[heapdump]");
|
||||||
|
String opts = Arrays.asList(Utils.getTestJavaOpts()).toString();
|
||||||
|
if (opts.contains("-XX:+UseSerialGC") || opts.contains("-XX:+UseEpsilonGC")) {
|
||||||
|
System.out.println("UseSerialGC detected.");
|
||||||
|
expectSerial = true;
|
||||||
|
}
|
||||||
|
if (!expectSerial && Runtime.getRuntime().availableProcessors() > 1) {
|
||||||
|
appOut.shouldContain("Dump heap objects in parallel");
|
||||||
|
appOut.shouldContain("Merge heap files complete");
|
||||||
|
} else {
|
||||||
|
appOut.shouldNotContain("Dump heap objects in parallel");
|
||||||
|
appOut.shouldNotContain("Merge heap files complete");
|
||||||
|
}
|
||||||
|
verifyHeapDump(heapDumpFile);
|
||||||
|
if (heapDumpFile.exists()) {
|
||||||
|
heapDumpFile.delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static LingeredApp launchApp() throws IOException {
|
||||||
|
LingeredApp theApp = new LingeredApp();
|
||||||
|
LingeredApp.startApp(theApp, "-Xlog:heapdump", "-Xmx512m",
|
||||||
|
"-XX:-UseDynamicNumberOfGCThreads",
|
||||||
|
"-XX:ParallelGCThreads=2");
|
||||||
|
return theApp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) throws Exception {
|
||||||
|
String heapDumpFileName = "parallelHeapDump.bin";
|
||||||
|
|
||||||
|
File heapDumpFile = new File(heapDumpFileName);
|
||||||
|
if (heapDumpFile.exists()) {
|
||||||
|
heapDumpFile.delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
LingeredApp theApp = launchApp();
|
||||||
|
try {
|
||||||
|
// Expect error message
|
||||||
|
OutputAnalyzer out = attachJcmdHeapDump(heapDumpFile, theApp.getPid(), "-parallel=" + -1);
|
||||||
|
out.shouldContain("Invalid number of parallel dump threads.");
|
||||||
|
|
||||||
|
// Expect serial dump because 0 implies to disable parallel dump
|
||||||
|
test(heapDumpFile, "-parallel=" + 0, true);
|
||||||
|
|
||||||
|
// Expect serial dump
|
||||||
|
test(heapDumpFile, "-parallel=" + 1, true);
|
||||||
|
|
||||||
|
// Expect parallel dump
|
||||||
|
test(heapDumpFile, "-parallel=" + Integer.MAX_VALUE, false);
|
||||||
|
|
||||||
|
// Expect parallel dump
|
||||||
|
test(heapDumpFile, "-gz=9 -overwrite -parallel=" + Runtime.getRuntime().availableProcessors(), false);
|
||||||
|
} finally {
|
||||||
|
theApp.stopApp();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void test(File heapDumpFile, String arg, boolean expectSerial) throws Exception {
|
||||||
|
LingeredApp theApp = launchApp();
|
||||||
|
try {
|
||||||
|
OutputAnalyzer dcmdOut = attachJcmdHeapDump(heapDumpFile, theApp.getPid(), arg);
|
||||||
|
theApp.stopApp();
|
||||||
|
checkAndVerify(dcmdOut, theApp, heapDumpFile, expectSerial);
|
||||||
|
} finally {
|
||||||
|
theApp.stopApp();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static OutputAnalyzer attachJcmdHeapDump(File heapDumpFile, long lingeredAppPid, String arg) throws Exception {
|
||||||
|
// e.g. jcmd <pid> GC.heap_dump -parallel=cpucount <file_path>
|
||||||
|
System.out.println("Testing pid " + lingeredAppPid);
|
||||||
|
PidJcmdExecutor executor = new PidJcmdExecutor("" + lingeredAppPid);
|
||||||
|
return executor.execute("GC.heap_dump " + arg + " " + heapDumpFile.getAbsolutePath());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void verifyHeapDump(File dump) {
|
||||||
|
Asserts.assertTrue(dump.exists() && dump.isFile(), "Could not create dump file " + dump.getAbsolutePath());
|
||||||
|
try {
|
||||||
|
File out = HprofParser.parse(dump);
|
||||||
|
|
||||||
|
Asserts.assertTrue(out != null && out.exists() && out.isFile(), "Could not find hprof parser output file");
|
||||||
|
List<String> lines = Files.readAllLines(out.toPath());
|
||||||
|
Asserts.assertTrue(lines.size() > 0, "hprof parser output file is empty");
|
||||||
|
for (String line : lines) {
|
||||||
|
Asserts.assertFalse(line.matches(".*WARNING(?!.*Failed to resolve object.*constantPoolOop.*).*"));
|
||||||
|
}
|
||||||
|
|
||||||
|
out.delete();
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
Asserts.fail("Could not parse dump file " + dump.getAbsolutePath());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user