8306441: Two phase segmented heap dump

Co-authored-by: Kevin Walls <kevinw@openjdk.org>
Reviewed-by: amenkov, kevinw
This commit is contained in:
Yi Yang 2023-08-09 01:58:57 +00:00
parent 515add88ed
commit 31a307f2fb
16 changed files with 582 additions and 1063 deletions

View File

@ -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) \

View File

@ -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

View File

@ -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) \

View File

@ -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) \
\ \

View File

@ -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);

View File

@ -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;

View File

@ -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) :

View File

@ -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

View File

@ -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

View File

@ -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();
}

View File

@ -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

View File

@ -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; }
}

View File

@ -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) {

View File

@ -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);
} }
} }

View File

@ -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());
}
}
}