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