< prev index next >
src/hotspot/share/services/heapDumper.cpp
Print this page
rev 58388 : 8237354: Add option to jcmd to write a gzipped heap dump
Reviewed-by:
*** 30,39 ****
--- 30,40 ----
#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,438 ****
enum {
STACK_TRACE_ID = 1,
INITIAL_CLASS_COUNT = 200
};
! // Supports I/O operations on a dump file
class DumpWriter : public StackObj {
private:
enum {
! io_buffer_max_size = 8*M,
! io_buffer_min_size = 64*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; }
- 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); }
! // all I/O go through this function
! void write_internal(void* s, size_t len);
public:
! DumpWriter(const char* path);
! ~DumpWriter();
! void close();
// total number of bytes written to the disk
! julong bytes_written() const { return _bytes_written; }
! char* error() const { return _error; }
// writer functions
void write_raw(void* s, size_t len);
! void write_u1(u1 x) { write_raw((void*)&x, 1); }
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);
--- 376,1047 ----
enum {
STACK_TRACE_ID = 1,
INITIAL_CLASS_COUNT = 200
};
! 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 initized 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 GZipComressor : 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:
! GZipComressor(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* GZipComressor::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* GZipComressor::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");
!
! if (gzip_func == NULL) {
! return "Cannot get ZIP_GZip_Fully function";
! } else {
! gzip_init_func = (GzipInitFunc) load_gzip_func("ZIP_GZip_InitParams");
!
! if (gzip_init_func == NULL) {
! return "Cannot get ZIP_GZip_InitParams function";
! } else {
! return gzip_init_func(block_size, needed_out_size, needed_tmp_size, _level);
! }
! }
! }
!
! char const* GZipComressor::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];
! 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 is empty.
! WriteWork* remove_first() { return remove(first()); }
!
! // Removes the last element. Returns NULL is 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);
! }
! }
!
! // The backend used to write data (and optionally compress it).
! 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* _writer;
! AbstractCompressor* _compressor;
!
! Monitor* _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 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 don 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 = 1*M,
! io_buffer_max_waste = 10*K,
dump_segment_header_size = 9
};
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().
! CompressionBackend _backend; // Does the actual writing.
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; }
! // Can be called if we have enough room in the buffer.
! void write_fast(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:
! // Takes ownership of the writer and compressor.
! DumpWriter(AbstractWriter* writer, AbstractCompressor* compressor);
! ~DumpWriter();
// total number of bytes written to the disk
! julong bytes_written() const { return (julong) _backend.get_written(); }
! char const* error() const { return _backend.error(); }
// writer functions
void write_raw(void* s, size_t len);
! 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,562 ****
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
! // if the open failed we record the error
! if (_fd < 0) {
! set_error(os::strerror(errno));
! }
! }
}
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;
! }
! _bytes_written += n;
! pos += n;
! len -= n;
! }
! }
}
// 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()) {
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;
! }
}
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);
}
void DumpWriter::write_u2(u2 x) {
u2 v;
Bytes::put_Java_u2((address)&v, x);
! write_raw((void*)&v, 2);
}
void DumpWriter::write_u4(u4 x) {
u4 v;
Bytes::put_Java_u4((address)&v, x);
! write_raw((void*)&v, 4);
}
void DumpWriter::write_u8(u8 x) {
u8 v;
Bytes::put_Java_u8((address)&v, x);
! write_raw((void*)&v, 8);
}
void DumpWriter::write_objectID(oop o) {
address a = cast_from_oop<address>(o);
#ifdef _LP64
--- 1052,1144 ----
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();
! // Called by threads used for parallel writing.
! void writer_loop() { _backend.thread_loop(false); }
! // Called when finished to release the threads.
! void deactivate() { _backend.deactivate(); }
! };
! 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() {
flush();
}
! 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);
! 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.
! while (len > buffer_size() - position()) {
assert(!_in_dump_segment || _is_huge_sub_record, "Cannot overflow in non-huge sub-record.");
! 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() {
! _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_KNOWN_TYPE((void*)&v, 2);
}
void DumpWriter::write_u4(u4 x) {
u4 v;
Bytes::put_Java_u4((address)&v, x);
! WRITE_KNOWN_TYPE((void*)&v, 4);
}
void DumpWriter::write_u8(u8 x) {
u8 v;
Bytes::put_Java_u8((address)&v, x);
! WRITE_KNOWN_TYPE((void*)&v, 8);
}
void DumpWriter::write_objectID(oop o) {
address a = cast_from_oop<address>(o);
#ifdef _LP64
*** 607,619 ****
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");
}
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);
--- 1189,1202 ----
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");
+
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,1511 ****
DumperSupport::dump_prim_array(writer(), typeArrayOop(o));
}
}
// The VM operation that performs the heap dump
! class VM_HeapDumper : public VM_GC_Operation {
private:
static VM_HeapDumper* _global_dumper;
static DumpWriter* _global_writer;
DumpWriter* _local_writer;
JavaThread* _oome_thread;
--- 2084,2094 ----
DumperSupport::dump_prim_array(writer(), typeArrayOop(o));
}
}
// The VM operation that performs the heap dump
! 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,1567 ****
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) {
_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;
--- 2140,2151 ----
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),
! 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,1599 ****
--- 2172,2185 ----
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,1829 ****
// 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();
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
--- 2404,2433 ----
// At this point we should be the only dumper active, so
// the following should be safe.
set_global_dumper();
set_global_writer();
! 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,1894 ****
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();
}
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));
--- 2486,2497 ----
ClassLoaderData::the_null_class_loader_data()->classes_do(&class_dumper);
// Writes the HPROF_HEAP_DUMP_END record.
DumperSupport::end_of_dump(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,1909 ****
--- 2503,2513 ----
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,1952 ****
}
}
}
// dump the heap to given path.
! int HeapDumper::dump(const char* path, outputStream* out) {
assert(path != NULL && strlen(path) > 0, "path missing");
// print message in interactive case
if (out != NULL) {
out->print_cr("Dumping heap to %s ...", path);
--- 2546,2556 ----
}
}
}
// dump the heap to given path.
! 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,1965 ****
}
// create JFR event
EventHeapDump event;
! // create the dump writer. If the file can be opened then bail
! DumpWriter writer(path);
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");
--- 2558,2580 ----
}
// create JFR event
EventHeapDump event;
! AbstractCompressor* compressor = NULL;
!
! if (compression > 0) {
! compressor = new (std::nothrow) GZipComressor(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,1985 ****
dumper.doit();
} else {
VMThread::execute(&dumper);
}
! // close dump file and record any error that the writer may have encountered
! writer.close();
set_error(writer.error());
// emit JFR event
if (error() == NULL) {
event.set_destination(path);
--- 2589,2599 ----
dumper.doit();
} else {
VMThread::execute(&dumper);
}
! // record any error that the writer may have encountered
set_error(writer.error());
// emit JFR event
if (error() == NULL) {
event.set_destination(path);
*** 2022,2032 ****
return NULL;
}
}
// set the error string
! void HeapDumper::set_error(char* error) {
if (_error != NULL) {
os::free(_error);
}
if (error == NULL) {
_error = NULL;
--- 2636,2646 ----
return NULL;
}
}
// set the error string
! void HeapDumper::set_error(char const* error) {
if (_error != NULL) {
os::free(_error);
}
if (error == NULL) {
_error = NULL;
< prev index next >