< prev index next >
src/hotspot/share/services/heapDumper.cpp
Print this page
rev 58893 : 8237354: Add option to jcmd to write a gzipped heap dump
Reviewed-by:
@@ -30,10 +30,11 @@
#include "classfile/symbolTable.hpp"
#include "classfile/systemDictionary.hpp"
#include "classfile/vmSymbols.hpp"
#include "gc/shared/gcLocker.hpp"
#include "gc/shared/gcVMOperations.hpp"
+#include "gc/shared/workgroup.hpp"
#include "jfr/jfrEvents.hpp"
#include "memory/allocation.inline.hpp"
#include "memory/resourceArea.hpp"
#include "memory/universe.hpp"
#include "oops/objArrayKlass.hpp"
@@ -375,64 +376,682 @@
enum {
STACK_TRACE_ID = 1,
INITIAL_CLASS_COUNT = 200
};
-// Supports I/O operations on a dump file
+class GzipBackend;
+class WriteWorkList;
+
+// Interface for a compression implementation.
+class AbstractCompressor : public CHeapObj<mtInternal> {
+public:
+ virtual ~AbstractCompressor() { }
+
+ // Initialized the compressor. Return a static error message in case of an error.
+ // Otherwise it initialized the needed out and tmp size for the given block size.
+ virtual char const* init(size_t block_size, size_t* needed_out_size, size_t* needed_tmp_size) = 0;
+
+ // Does the actual compression. Returns NULL on success and a static error message otherwise.
+ // Sets the 'compressed_size'.
+ virtual char const* compress(char* in, size_t in_size, char* out, size_t out_size, char* tmp, size_t tmp_size,
+ size_t* compressed_size) = 0;
+};
+
+// Interface for a writer implementation.
+class AbstractWriter : public CHeapObj<mtInternal> {
+public:
+ virtual ~AbstractWriter() { }
+
+ // Opens the writer. Returns NULL on success and a static error message otherwise.
+ virtual char const* open_writer() = 0;
+
+ // Does the write. Returns NULL on success and a static error message otherwise.
+ virtual char const* write_buf(char* buf, ssize_t size) = 0;
+};
+
+
+typedef char const* (*GzipInitFunc)(size_t, size_t*, size_t*, int);
+typedef size_t(*GzipFunc)(char*, size_t, char*, size_t, char*, size_t, int, char*, char const**);
+
+class GZipCompressor : public AbstractCompressor {
+private:
+ int _level;
+ size_t _block_size;
+ bool _is_first;
+
+ GzipInitFunc gzip_init_func;
+ GzipFunc gzip_func;
+
+ void* load_gzip_func(char const* name);
+
+public:
+ GZipCompressor(int level) : _level(level), _block_size(0), _is_first(false) {
+ }
+
+ virtual char const* init(size_t block_size, size_t* needed_out_size, size_t* needed_tmp_size);
+
+ virtual char const* compress(char* in, size_t in_size, char* out, size_t out_size, char* tmp, size_t tmp_size,
+ size_t* compressed_size);
+};
+
+void* GZipCompressor::load_gzip_func(char const* name) {
+ char path[JVM_MAXPATHLEN];
+ char ebuf[1024];
+ void* handle;
+
+ if (os::dll_locate_lib(path, sizeof(path), Arguments::get_dll_dir(), "zip")) {
+ handle = os::dll_load(path, ebuf, sizeof ebuf);
+
+ if (handle != NULL) {
+ return os::dll_lookup(handle, name);
+ }
+ }
+
+ return NULL;
+}
+
+char const* GZipCompressor::init(size_t block_size, size_t* needed_out_size, size_t* needed_tmp_size) {
+ _block_size = block_size;
+ _is_first = true;
+
+ gzip_func = (GzipFunc) load_gzip_func("ZIP_GZip_Fully");
+ char const* result;
+
+ if (gzip_func == NULL) {
+ result = "Cannot get ZIP_GZip_Fully function";
+ } else {
+ gzip_init_func = (GzipInitFunc) load_gzip_func("ZIP_GZip_InitParams");
+
+ if (gzip_init_func == NULL) {
+ result = "Cannot get ZIP_GZip_InitParams function";
+ } else {
+ result = gzip_init_func(block_size, needed_out_size, needed_tmp_size, _level);
+ needed_out_size += 1024; // Add extra space for the comment in the first chunk.
+ }
+ }
+
+ return result;
+}
+
+char const* GZipCompressor::compress(char* in, size_t in_size, char* out, size_t out_size, char* tmp,
+ size_t tmp_size, size_t* compressed_size) {
+ char const* msg = NULL;
+
+ if (_is_first) {
+ char buf[128];
+ // Write the block size used as a comment in the first gzip chunk, so the
+ // code used to read it later can make a good choice buffer sizes it uses.
+ jio_snprintf(buf, sizeof(buf), "HPROF BLOCKSIZE=" SIZE_FORMAT, _block_size);
+ *compressed_size = gzip_func(in, in_size, out, out_size, tmp, tmp_size, _level, buf, &msg);
+ _is_first = false;
+ } else {
+ *compressed_size = gzip_func(in, in_size, out, out_size, tmp, tmp_size, _level, NULL, &msg);
+ }
+
+ return msg;
+}
+
+
+// A writer for a file.
+class FileWriter : public AbstractWriter {
+private:
+ char const* _path;
+ int _fd;
+
+public:
+ FileWriter(char const* path) : _path(path), _fd(-1) { }
+
+ ~FileWriter();
+
+ // Opens the writer. Returns NULL on success and a static error message otherwise.
+ virtual char const* open_writer();
+
+ // Does the write. Returns NULL on success and a static error message otherwise.
+ virtual char const* write_buf(char* buf, ssize_t size);
+};
+
+char const* FileWriter::open_writer() {
+ assert(_fd < 0, "Must not already be open");
+
+ _fd = os::create_binary_file(_path, false); // don't replace existing file
+
+ if (_fd < 0) {
+ return os::strerror(errno);
+ }
+
+ return NULL;
+}
+
+FileWriter::~FileWriter() {
+ if (_fd >= 0) {
+ os::close(_fd);
+ _fd = -1;
+ }
+}
+
+char const* FileWriter::write_buf(char* buf, ssize_t size) {
+ assert(_fd >= 0, "Must be open");
+ assert(size > 0, "Must write at least one byte");
+
+ ssize_t n = (ssize_t) os::write(_fd, buf, (uint) size);
+
+ if (n <= 0) {
+ return os::strerror(errno);
+ }
+
+ return NULL;
+}
+
+// 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 works 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() ? NULL : _head._next; }
+
+ // Returns the last element.
+ WriteWork* last() { return is_empty() ? NULL : _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()); }
+};
+
+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 != NULL) {
+ assert(work->_next != work, "Invalid next");
+ assert(work->_prev != work, "Invalid prev");
+ work->_prev->_next = work->_next;;
+ work->_next->_prev = work->_prev;
+ work->_next = NULL;
+ work->_prev = NULL;
+ }
+
+ 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);
+ }
+}
+
+// This class is used to 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 returns 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);
+
+ WriteWork* get_work();
+ void do_compress(WriteWork* work);
+ void finish_work(WriteWork* work);
+
+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 maxiumum 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; }
+
+ // 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);
+
+ // The entry point for a worker thread. If single_run is true, we only handle one work entry.
+ void thread_loop(bool single_run);
+
+ // Shuts down the backend, releasing all threads.
+ void deactivate();
+};
+
+CompressionBackend::CompressionBackend(AbstractWriter* writer, AbstractCompressor* compressor,
+ size_t block_size, size_t max_waste) :
+ _active(false),
+ _err(NULL),
+ _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::leaf, "HProf Compression Backend",
+ true, Mutex::_safepoint_check_never)) {
+ if (_writer == NULL) {
+ set_error("Could not allocate writer");
+ } else if (_lock == NULL) {
+ set_error("Could not allocate lock");
+ } else {
+ set_error(_writer->open_writer());
+ }
+
+ if (_compressor != NULL) {
+ set_error(_compressor->init(_in_size, &_out_size, &_tmp_size));
+ }
+
+ _current = allocate_work(_in_size, _out_size, _tmp_size);
+
+ if (_current == NULL) {
+ set_error("Could not allocate memory for buffer");
+ }
+
+ _active = (_err == NULL);
+}
+
+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::deactivate() {
+ assert(_active, "Must be active");
+
+ MonitorLocker ml(_lock, Mutex::_no_safepoint_check_flag);
+
+ // Make sure we write a partially filled buffer.
+ if ((_current != NULL) && (_current->_in_used > 0)) {
+ _current->_id = _next_id++;
+ _to_compress.add_last(_current);
+ _current = NULL;
+ ml.notify_all();
+ }
+
+ // Wait for the threads to drain the compression work list.
+ while (!_to_compress.is_empty()) {
+ // If we have no threads, compress the current one itself.
+ if (_nr_of_threads == 0) {
+ MutexUnlocker mu(_lock, Mutex::_no_safepoint_check_flag);
+ thread_loop(true);
+ } else {
+ ml.wait();
+ }
+ }
+
+ _active = false;
+ ml.notify_all();
+}
+
+void CompressionBackend::thread_loop(bool single_run) {
+ // Register if this is a worker thread.
+ if (!single_run) {
+ MonitorLocker ml(_lock, Mutex::_no_safepoint_check_flag);
+ _nr_of_threads++;
+ }
+
+ while (true) {
+ WriteWork* work = get_work();
+
+ if (work == NULL) {
+ assert(!single_run, "Should never happen for single thread");
+ MonitorLocker ml(_lock, Mutex::_no_safepoint_check_flag);
+ _nr_of_threads--;
+ assert(_nr_of_threads >= 0, "Too many threads finished");
+ ml.notify_all();
+
+ return;
+ } else {
+ do_compress(work);
+ finish_work(work);
+ }
+
+ if (single_run) {
+ return;
+ }
+ }
+}
+
+void CompressionBackend::set_error(char const* new_error) {
+ if ((new_error != NULL) && (_err == NULL)) {
+ _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 == NULL) {
+ _work_creation_failed = true;
+ return NULL;
+ }
+
+ _works_created++;
+ result->_in = (char*) os::malloc(in_size, mtInternal);
+ result->_in_max = in_size;
+ result->_in_used = 0;
+ result->_out = NULL;
+ result->_tmp = NULL;
+
+ if (result->_in == NULL) {
+ 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 == NULL) {
+ goto fail;
+ }
+ }
+
+ if (tmp_size > 0) {
+ result->_tmp = (char*) os::malloc(tmp_size, mtInternal);
+ result->_tmp_max = tmp_size;
+
+ if (result->_tmp == NULL) {
+ goto fail;
+ }
+ }
+
+ return result;
+
+fail:
+ free_work(result);
+ _work_creation_failed = true;
+ return NULL;
+}
+
+void CompressionBackend::free_work(WriteWork* work) {
+ if (work != NULL) {
+ 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());
+ }
+}
+
+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::get_new_buffer(char** buffer, size_t* used, size_t* max) {
+ if (_active) {
+ MonitorLocker ml(_lock, Mutex::_no_safepoint_check_flag);
+
+ if (*used > 0) {
+ _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) {
+ _current->_id = _next_id++;
+ _to_compress.add_last(_current);
+ _current = NULL;
+ ml.notify_all();
+ } else {
+ *buffer = _current->_in + _current->_in_used;
+ *used = 0;
+ *max = _current->_in_max - _current->_in_used;
+
+ return;
+ }
+ }
+
+ while ((_current == NULL) && _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 != NULL) {
+ _unused.add_first(work);
+ }
+ } else if (!_to_compress.is_empty() && (_nr_of_threads == 0)) {
+ // If we have no threads, compress the current one itself.
+ MutexUnlocker mu(_lock, Mutex::_no_safepoint_check_flag);
+ thread_loop(true);
+ } else {
+ ml.wait();
+ }
+ }
+
+ if (_current == NULL) {
+ _current = _unused.remove_first();
+ }
+
+ if (_current != NULL) {
+ _current->_in_used = 0;
+ _current->_out_used = 0;
+ *buffer = _current->_in;
+ *used = 0;
+ *max = _current->_in_max;
+
+ return;
+ }
+ }
+
+ *buffer = NULL;
+ *used = 0;
+ *max = 0;
+
+ return;
+}
+
+void CompressionBackend::do_compress(WriteWork* work) {
+ if (_compressor != NULL) {
+ char const* msg = _compressor->compress(work->_in, work->_in_used, work->_out, work->_out_max,
+ work->_tmp, _tmp_size, &work->_out_used);
+
+ if (msg != NULL) {
+ 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 left = _compressor == NULL ? to_write->_in_used : to_write->_out_used;
+ char* p = _compressor == NULL ? to_write->_in : to_write->_out;
+ char const* msg = NULL;
+
+ if (_err == NULL) {
+ _written += left;
+ MutexUnlocker mu(_lock, Mutex::_no_safepoint_check_flag);
+ msg = _writer->write_buf(p, (ssize_t) left);
+ }
+
+ set_error(msg);
+ _unused.add_first(to_write);
+ _id_to_write++;
+ }
+
+ ml.notify_all();
+}
+
class DumpWriter : public StackObj {
private:
enum {
- io_buffer_max_size = 8*M,
- io_buffer_min_size = 64*K,
+ io_buffer_max_size = 1*M,
+ io_buffer_max_waste = 10*K,
dump_segment_header_size = 9
};
- int _fd; // file descriptor (-1 if dump file not open)
- julong _bytes_written; // number of byte written to dump file
-
char* _buffer; // internal buffer
size_t _size;
size_t _pos;
bool _in_dump_segment; // Are we currently in a dump segment?
bool _is_huge_sub_record; // Are we writing a sub-record larger than the buffer size?
DEBUG_ONLY(size_t _sub_record_left;) // The bytes not written for the current sub-record.
DEBUG_ONLY(bool _sub_record_ended;) // True if we have called the end_sub_record().
- char* _error; // error message when I/O fails
-
- void set_file_descriptor(int fd) { _fd = fd; }
- int file_descriptor() const { return _fd; }
+ CompressionBackend _backend; // Does the actual writing.
- bool is_open() const { return file_descriptor() >= 0; }
void flush();
char* buffer() const { return _buffer; }
size_t buffer_size() const { return _size; }
size_t position() const { return _pos; }
void set_position(size_t pos) { _pos = pos; }
- void set_error(const char* error) { _error = (char*)os::strdup(error); }
+ // Can be called if we have enough room in the buffer.
+ void write_fast(void* s, size_t len);
- // all I/O go through this function
- void write_internal(void* s, size_t len);
+ // Returns true if we have enough room in the buffer for 'len' bytes.
+ bool can_write_fast(size_t len);
public:
- DumpWriter(const char* path);
- ~DumpWriter();
+ // Takes ownership of the writer and compressor.
+ DumpWriter(AbstractWriter* writer, AbstractCompressor* compressor);
- void close();
+ ~DumpWriter();
// total number of bytes written to the disk
- julong bytes_written() const { return _bytes_written; }
+ julong bytes_written() const { return (julong) _backend.get_written(); }
- char* error() const { return _error; }
+ char const* error() const { return _backend.error(); }
// writer functions
void write_raw(void* s, size_t len);
- void write_u1(u1 x) { write_raw((void*)&x, 1); }
+ void write_u1(u1 x);
void write_u2(u2 x);
void write_u4(u4 x);
void write_u8(u8 x);
void write_objectID(oop o);
void write_symbolID(Symbol* o);
@@ -443,120 +1062,94 @@
void start_sub_record(u1 tag, u4 len);
// Ends the current sub-record.
void end_sub_record();
// Finishes the current dump segment if not already finished.
void finish_dump_segment();
-};
-DumpWriter::DumpWriter(const char* path) : _fd(-1), _bytes_written(0), _pos(0),
- _in_dump_segment(false), _error(NULL) {
- // try to allocate an I/O buffer of io_buffer_size. If there isn't
- // sufficient memory then reduce size until we can allocate something.
- _size = io_buffer_max_size;
- do {
- _buffer = (char*)os::malloc(_size, mtInternal);
- if (_buffer == NULL) {
- _size = _size >> 1;
- }
- } while (_buffer == NULL && _size >= io_buffer_min_size);
-
- if (_buffer == NULL) {
- set_error("Could not allocate buffer memory for heap dump");
- } else {
- _fd = os::create_binary_file(path, false); // don't replace existing file
+ // Called by threads used for parallel writing.
+ void writer_loop() { _backend.thread_loop(false); }
+ // Called when finished to release the threads.
+ void deactivate() { flush(); _backend.deactivate(); }
+};
- // if the open failed we record the error
- if (_fd < 0) {
- set_error(os::strerror(errno));
- }
- }
+// Check for error after constructing the object and destroy it in case of an error.
+DumpWriter::DumpWriter(AbstractWriter* writer, AbstractCompressor* compressor) :
+ _buffer(NULL),
+ _size(0),
+ _pos(0),
+ _in_dump_segment(false),
+ _backend(writer, compressor, io_buffer_max_size, io_buffer_max_waste) {
+ flush();
}
DumpWriter::~DumpWriter() {
- close();
- os::free(_buffer);
- os::free(_error);
-}
-
-// closes dump file (if open)
-void DumpWriter::close() {
- // flush and close dump file
- if (is_open()) {
flush();
- os::close(file_descriptor());
- set_file_descriptor(-1);
- }
}
-// write directly to the file
-void DumpWriter::write_internal(void* s, size_t len) {
- if (is_open()) {
- const char* pos = (char*)s;
- ssize_t n = 0;
- while (len > 0) {
- uint tmp = (uint)MIN2(len, (size_t)INT_MAX);
- n = os::write(file_descriptor(), pos, tmp);
-
- if (n < 0) {
- // EINTR cannot happen here, os::write will take care of that
- set_error(os::strerror(errno));
- os::close(file_descriptor());
- set_file_descriptor(-1);
- return;
- }
+void DumpWriter::write_fast(void* s, size_t len) {
+ assert(!_in_dump_segment || (_sub_record_left >= len), "sub-record too large");
+ assert(buffer_size() - position() >= len, "Must fit");
+ debug_only(_sub_record_left -= len);
- _bytes_written += n;
- pos += n;
- len -= n;
- }
- }
+ memcpy(buffer() + position(), s, len);
+ set_position(position() + len);
+}
+
+bool DumpWriter::can_write_fast(size_t len) {
+ return buffer_size() - position() >= len;
}
// write raw bytes
void DumpWriter::write_raw(void* s, size_t len) {
assert(!_in_dump_segment || (_sub_record_left >= len), "sub-record too large");
debug_only(_sub_record_left -= len);
- // flush buffer to make room
- if (len > buffer_size() - position()) {
+ // flush buffer to make room.
+ while (len > buffer_size() - position()) {
assert(!_in_dump_segment || _is_huge_sub_record, "Cannot overflow in non-huge sub-record.");
- flush();
-
- // If larger than the buffer, just write it directly.
- if (len > buffer_size()) {
- write_internal(s, len);
- return;
- }
+ size_t to_write = buffer_size() - position();
+ memcpy(buffer() + position(), s, to_write);
+ s = (void*) ((char*) s + to_write);
+ len -= to_write;
+ set_position(position() + to_write);
+ flush();
}
memcpy(buffer() + position(), s, len);
set_position(position() + len);
}
// flush any buffered bytes to the file
void DumpWriter::flush() {
- write_internal(buffer(), position());
- set_position(0);
+ _backend.get_new_buffer(&_buffer, &_pos, &_size);
+}
+
+// Makes sure we inline the fast write into the write_u* functions. This is a big speedup.
+#define WRITE_KNOWN_TYPE(p, len) do { if (can_write_fast((len))) write_fast((p), (len)); \
+ else write_raw((p), (len)); } while (0)
+
+void DumpWriter::write_u1(u1 x) {
+ WRITE_KNOWN_TYPE((void*) &x, 1);
}
void DumpWriter::write_u2(u2 x) {
u2 v;
Bytes::put_Java_u2((address)&v, x);
- write_raw((void*)&v, 2);
+ WRITE_KNOWN_TYPE((void*)&v, 2);
}
void DumpWriter::write_u4(u4 x) {
u4 v;
Bytes::put_Java_u4((address)&v, x);
- write_raw((void*)&v, 4);
+ WRITE_KNOWN_TYPE((void*)&v, 4);
}
void DumpWriter::write_u8(u8 x) {
u8 v;
Bytes::put_Java_u8((address)&v, x);
- write_raw((void*)&v, 8);
+ WRITE_KNOWN_TYPE((void*)&v, 8);
}
void DumpWriter::write_objectID(oop o) {
address a = cast_from_oop<address>(o);
#ifdef _LP64
@@ -607,13 +1200,14 @@
void DumpWriter::start_sub_record(u1 tag, u4 len) {
if (!_in_dump_segment) {
if (position() > 0) {
flush();
- assert(position() == 0, "Must be at the start");
}
+ assert(position() == 0, "Must be at the start");
+
write_u1(HPROF_HEAP_DUMP_SEGMENT);
write_u4(0); // timestamp
// Will be fixed up later if we add more sub-records. If this is a huge sub-record,
// this is already the correct length, since we don't add more sub-records.
write_u4(len);
@@ -1501,11 +2095,11 @@
DumperSupport::dump_prim_array(writer(), typeArrayOop(o));
}
}
// The VM operation that performs the heap dump
-class VM_HeapDumper : public VM_GC_Operation {
+class VM_HeapDumper : public VM_GC_Operation, public AbstractGangTask {
private:
static VM_HeapDumper* _global_dumper;
static DumpWriter* _global_writer;
DumpWriter* _local_writer;
JavaThread* _oome_thread;
@@ -1557,11 +2151,12 @@
public:
VM_HeapDumper(DumpWriter* writer, bool gc_before_heap_dump, bool oome) :
VM_GC_Operation(0 /* total collections, dummy, ignored */,
GCCause::_heap_dump /* GC Cause */,
0 /* total full collections, dummy, ignored */,
- gc_before_heap_dump) {
+ gc_before_heap_dump),
+ AbstractGangTask("dump heap") {
_local_writer = writer;
_gc_before_heap_dump = gc_before_heap_dump;
_klass_map = new (ResourceObj::C_HEAP, mtInternal) GrowableArray<Klass*>(INITIAL_CLASS_COUNT, true);
_stack_traces = NULL;
_num_threads = 0;
@@ -1588,12 +2183,14 @@
delete _klass_map;
}
VMOp_Type type() const { return VMOp_HeapDumper; }
void doit();
+ void work(uint worker_id);
};
+
VM_HeapDumper* VM_HeapDumper::_global_dumper = NULL;
DumpWriter* VM_HeapDumper::_global_writer = NULL;
bool VM_HeapDumper::skip_operation() const {
return false;
@@ -1818,12 +2415,31 @@
// At this point we should be the only dumper active, so
// the following should be safe.
set_global_dumper();
set_global_writer();
- // Write the file header - we always use 1.0.2
- size_t used = ch->used();
+ // Shenandoah does not support iterating the heap in a GC worker thread.
+ WorkGang* gang = UseShenandoahGC ? NULL : ch->get_safepoint_workers();
+
+ if (gang == NULL) {
+ work(0);
+ } else {
+ gang->run_task(this);
+ }
+
+ // Now we clear the global variables, so that a future dumper might run.
+ clear_global_dumper();
+ clear_global_writer();
+}
+
+void VM_HeapDumper::work(uint worker_id) {
+ if (worker_id != 0) {
+ writer()->writer_loop();
+ return;
+ }
+
+ // Write the file header - we always use 1.0.
const char* header = "JAVA PROFILE 1.0.2";
// header is few bytes long - no chance to overflow int
writer()->write_raw((void*)header, (int)strlen(header));
writer()->write_u1(0); // terminator
@@ -1882,13 +2498,12 @@
ClassLoaderData::the_null_class_loader_data()->classes_do(&class_dumper);
// Writes the HPROF_HEAP_DUMP_END record.
DumperSupport::end_of_dump(writer());
- // Now we clear the global variables, so that a future dumper might run.
- clear_global_dumper();
- clear_global_writer();
+ // We are done with writing. Release the worker threads.
+ writer()->deactivate();
}
void VM_HeapDumper::dump_stack_traces() {
// write a HPROF_TRACE record without any frames to be referenced as object alloc sites
DumperSupport::write_header(writer(), HPROF_TRACE, 3*sizeof(u4));
@@ -1900,10 +2515,11 @@
int frame_serial_num = 0;
for (JavaThreadIteratorWithHandle jtiwh; JavaThread *thread = jtiwh.next(); ) {
oop threadObj = thread->threadObj();
if (threadObj != NULL && !thread->is_exiting() && !thread->is_hidden_from_external_view()) {
// dump thread stack trace
+ ResourceMark rm;
ThreadStackTrace* stack_trace = new ThreadStackTrace(thread, false);
stack_trace->dump_stack_at_safepoint(-1);
_stack_traces[_num_threads++] = stack_trace;
// write HPROF_FRAME records for this thread's stack trace
@@ -1942,11 +2558,11 @@
}
}
}
// dump the heap to given path.
-int HeapDumper::dump(const char* path, outputStream* out) {
+int HeapDumper::dump(const char* path, outputStream* out, int compression) {
assert(path != NULL && strlen(path) > 0, "path missing");
// print message in interactive case
if (out != NULL) {
out->print_cr("Dumping heap to %s ...", path);
@@ -1954,12 +2570,23 @@
}
// create JFR event
EventHeapDump event;
- // create the dump writer. If the file can be opened then bail
- DumpWriter writer(path);
+ AbstractCompressor* compressor = NULL;
+
+ if (compression > 0) {
+ compressor = new (std::nothrow) GZipCompressor(compression);
+
+ if (compressor == NULL) {
+ set_error("Could not allocate gzip compressor");
+ return -1;
+ }
+ }
+
+ DumpWriter writer(new (std::nothrow) FileWriter(path), compressor);
+
if (writer.error() != NULL) {
set_error(writer.error());
if (out != NULL) {
out->print_cr("Unable to create %s: %s", path,
(error() != NULL) ? error() : "reason unknown");
@@ -1974,12 +2601,11 @@
dumper.doit();
} else {
VMThread::execute(&dumper);
}
- // close dump file and record any error that the writer may have encountered
- writer.close();
+ // record any error that the writer may have encountered
set_error(writer.error());
// emit JFR event
if (error() == NULL) {
event.set_destination(path);
@@ -2022,11 +2648,11 @@
return NULL;
}
}
// set the error string
-void HeapDumper::set_error(char* error) {
+void HeapDumper::set_error(char const* error) {
if (_error != NULL) {
os::free(_error);
}
if (error == NULL) {
_error = NULL;
< prev index next >