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