From fe4c0a2f04cc45e6e27e6d8a8941bbbf9a8def8c Mon Sep 17 00:00:00 2001 From: Matias Saavedra Silva Date: Thu, 7 Dec 2023 19:32:55 +0000 Subject: [PATCH 01/57] 8302790: Set FileMapRegion::mapped_base() to null if mapping fails Reviewed-by: iklam, ccheung --- src/hotspot/share/cds/filemap.cpp | 35 +++++++++++++++++-------------- src/hotspot/share/cds/filemap.hpp | 2 +- 2 files changed, 20 insertions(+), 17 deletions(-) diff --git a/src/hotspot/share/cds/filemap.cpp b/src/hotspot/share/cds/filemap.cpp index 99acbb4ace8..3b50e014797 100644 --- a/src/hotspot/share/cds/filemap.cpp +++ b/src/hotspot/share/cds/filemap.cpp @@ -1465,7 +1465,7 @@ BitMapView FileMapRegion::ptrmap_view() { return bitmap_view(false); } -bool FileMapRegion::check_region_crc() const { +bool FileMapRegion::check_region_crc(char* base) const { // This function should be called after the region has been properly // loaded into memory via FileMapInfo::map_region() or FileMapInfo::read_region(). // I.e., this->mapped_base() must be valid. @@ -1474,8 +1474,8 @@ bool FileMapRegion::check_region_crc() const { return true; } - assert(mapped_base() != nullptr, "must be initialized"); - int crc = ClassLoader::crc32(0, mapped_base(), (jint)sz); + assert(base != nullptr, "must be initialized"); + int crc = ClassLoader::crc32(0, base, (jint)sz); if (crc != this->crc()) { log_warning(cds)("Checksum verification failed."); return false; @@ -1760,13 +1760,13 @@ bool FileMapInfo::read_region(int i, char* base, size_t size, bool do_commit) { return false; } - r->set_mapped_from_file(false); - r->set_mapped_base(base); - - if (VerifySharedSpaces && !r->check_region_crc()) { + if (VerifySharedSpaces && !r->check_region_crc(base)) { return false; } + r->set_mapped_from_file(false); + r->set_mapped_base(base); + return true; } @@ -1803,6 +1803,7 @@ MapArchiveResult FileMapInfo::map_region(int i, intx addr_delta, char* mapped_ba return MAP_ARCHIVE_OTHER_FAILURE; // oom or I/O error. } else { assert(r->mapped_base() != nullptr, "must be initialized"); + return MAP_ARCHIVE_SUCCESS; } } else { // Note that this may either be a "fresh" mapping into unreserved address @@ -1817,15 +1818,16 @@ MapArchiveResult FileMapInfo::map_region(int i, intx addr_delta, char* mapped_ba _memory_mapping_failed = true; return MAP_ARCHIVE_MMAP_FAILURE; } + + if (VerifySharedSpaces && !r->check_region_crc(requested_addr)) { + return MAP_ARCHIVE_OTHER_FAILURE; + } + r->set_mapped_from_file(true); r->set_mapped_base(requested_addr); - } - if (VerifySharedSpaces && !r->check_region_crc()) { - return MAP_ARCHIVE_OTHER_FAILURE; + return MAP_ARCHIVE_SUCCESS; } - - return MAP_ARCHIVE_SUCCESS; } // The return value is the location of the archive relocation bitmap. @@ -1843,8 +1845,7 @@ char* FileMapInfo::map_bitmap_region() { return nullptr; } - r->set_mapped_base(bitmap_base); - if (VerifySharedSpaces && !r->check_region_crc()) { + if (VerifySharedSpaces && !r->check_region_crc(bitmap_base)) { log_error(cds)("relocation bitmap CRC error"); if (!os::unmap_memory(bitmap_base, r->used_aligned())) { fatal("os::unmap_memory of relocation bitmap failed"); @@ -1853,6 +1854,7 @@ char* FileMapInfo::map_bitmap_region() { } r->set_mapped_from_file(true); + r->set_mapped_base(bitmap_base); log_info(cds)("Mapped %s region #%d at base " INTPTR_FORMAT " top " INTPTR_FORMAT " (%s)", is_static() ? "static " : "dynamic", MetaspaceShared::bm, p2i(r->mapped_base()), p2i(r->mapped_end()), @@ -2128,13 +2130,14 @@ bool FileMapInfo::map_heap_region_impl() { return false; } - r->set_mapped_base(base); - if (VerifySharedSpaces && !r->check_region_crc()) { + if (VerifySharedSpaces && !r->check_region_crc(base)) { dealloc_heap_region(); log_info(cds)("UseSharedSpaces: mapped heap region is corrupt"); return false; } + r->set_mapped_base(base); + // If the requested range is different from the range allocated by GC, then // the pointers need to be patched. address mapped_start = (address) _mapped_heap_memregion.start(); diff --git a/src/hotspot/share/cds/filemap.hpp b/src/hotspot/share/cds/filemap.hpp index 7ad7b62b760..3d2062093c6 100644 --- a/src/hotspot/share/cds/filemap.hpp +++ b/src/hotspot/share/cds/filemap.hpp @@ -170,7 +170,7 @@ class FileMapRegion: private CDSFileMapRegion { BitMapView ptrmap_view(); bool has_ptrmap() { return _ptrmap_size_in_bits != 0; } - bool check_region_crc() const; + bool check_region_crc(char* base) const; void print(outputStream* st, int region_index); }; From 4ed38f5ad5f822ab948257ed39717ea919fd32ed Mon Sep 17 00:00:00 2001 From: Naoto Sato Date: Thu, 7 Dec 2023 19:46:18 +0000 Subject: [PATCH 02/57] 8321409: Console read line with zero out should zero out underlying buffer in JLine (redux) Reviewed-by: alanb --- .../jdk/internal/org/jline/JdkConsoleProviderImpl.java | 2 +- .../classes/jdk/internal/org/jline/reader/LineReader.java | 5 +++++ .../jdk/internal/org/jline/reader/impl/LineReaderImpl.java | 6 ++++++ 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/JdkConsoleProviderImpl.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/JdkConsoleProviderImpl.java index 81ff4ae3077..3621a9efea8 100644 --- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/JdkConsoleProviderImpl.java +++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/JdkConsoleProviderImpl.java @@ -114,7 +114,7 @@ public char[] readPassword(String fmt, Object ... args) { } catch (EndOfFileException eofe) { return null; } finally { - jline.getBuffer().zeroOut(); + jline.zeroOut(); } } diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/LineReader.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/LineReader.java index 03729853ca5..bc719e9b46a 100644 --- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/LineReader.java +++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/LineReader.java @@ -750,4 +750,9 @@ enum SuggestionType { void setAutosuggestion(SuggestionType type); SuggestionType getAutosuggestion(); + + // JDK specific modification + default void zeroOut() { + throw new UnsupportedOperationException(); + } } diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/impl/LineReaderImpl.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/impl/LineReaderImpl.java index ee8a434e1a2..a41f7d19bf8 100644 --- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/impl/LineReaderImpl.java +++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/reader/impl/LineReaderImpl.java @@ -6250,4 +6250,10 @@ private void rebind(KeyMap keyMap, String operation, String prevBinding } } + // JDK specific modification + @Override + public void zeroOut() { + buf.zeroOut(); + parsedLine = null; + } } From 959a443a9eb8f1ebc1335fdd86d421154bd71491 Mon Sep 17 00:00:00 2001 From: Phil Race Date: Thu, 7 Dec 2023 21:05:38 +0000 Subject: [PATCH 03/57] 8288712: Typo in javadoc in javax.imageio.ImageReader.java Reviewed-by: iris --- src/java.desktop/share/classes/javax/imageio/ImageReader.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/java.desktop/share/classes/javax/imageio/ImageReader.java b/src/java.desktop/share/classes/javax/imageio/ImageReader.java index 97092ec7de9..3488f60b9b7 100644 --- a/src/java.desktop/share/classes/javax/imageio/ImageReader.java +++ b/src/java.desktop/share/classes/javax/imageio/ImageReader.java @@ -2582,7 +2582,7 @@ protected static Rectangle getSourceRegion(ImageReadParam param, * width or height of 0, an {@code IllegalArgumentException} * is thrown. * - *

The {@link #getSourceRegion getSourceRegion>} + *

The {@link #getSourceRegion getSourceRegion} * method may be used if only source clipping is desired. * * @param param an {@code ImageReadParam}, or {@code null}. From 354ea4c28f1449479f71e89831c64047c50e1a61 Mon Sep 17 00:00:00 2001 From: Alex Menkov Date: Thu, 7 Dec 2023 23:18:23 +0000 Subject: [PATCH 04/57] 8299426: Heap dump does not contain virtual Thread stack references Reviewed-by: cjplummer, sspitsyn, lmesnik --- src/hotspot/share/services/heapDumper.cpp | 401 +++++++++++------- .../dcmd/gc/HeapDumpParallelTest.java | 1 - .../vthread/HeapDump/VThreadInHeapDump.java | 4 +- 3 files changed, 240 insertions(+), 166 deletions(-) diff --git a/src/hotspot/share/services/heapDumper.cpp b/src/hotspot/share/services/heapDumper.cpp index 18b91b6f289..9f48e794ea6 100644 --- a/src/hotspot/share/services/heapDumper.cpp +++ b/src/hotspot/share/services/heapDumper.cpp @@ -1473,6 +1473,25 @@ void SymbolTableDumper::do_symbol(Symbol** p) { } } +// Support class used to generate HPROF_GC_CLASS_DUMP records + +class ClassDumper : public KlassClosure { + private: + AbstractDumpWriter* _writer; + AbstractDumpWriter* writer() const { return _writer; } + + public: + ClassDumper(AbstractDumpWriter* writer) : _writer(writer) {} + + void do_klass(Klass* k) { + if (k->is_instance_klass()) { + DumperSupport::dump_instance_class(writer(), k); + } else { + DumperSupport::dump_array_class(writer(), k); + } + } +}; + // Support class used to generate HPROF_GC_ROOT_JNI_LOCAL records class JNILocalsDumper : public OopClosure { @@ -1860,21 +1879,25 @@ vframe* ThreadDumper::get_top_frame() const { return nullptr; } +// Callback to dump thread-related data for unmounted virtual threads; +// implemented by VM_HeapDumper. +class UnmountedVThreadDumper { + public: + virtual void dump_vthread(oop vt, AbstractDumpWriter* segment_writer) = 0; +}; -class VM_HeapDumper; - -// Support class using when iterating over the heap. +// Support class used when iterating over the heap. class HeapObjectDumper : public ObjectClosure { private: AbstractDumpWriter* _writer; AbstractDumpWriter* writer() { return _writer; } + UnmountedVThreadDumper* _vthread_dumper; DumperClassCacheTable _class_cache; public: - HeapObjectDumper(AbstractDumpWriter* writer) { - _writer = writer; - } + HeapObjectDumper(AbstractDumpWriter* writer, UnmountedVThreadDumper* vthread_dumper) + : _writer(writer), _vthread_dumper(vthread_dumper) {} // called for each object in the heap void do_object(oop o); @@ -1895,6 +1918,9 @@ void HeapObjectDumper::do_object(oop o) { if (o->is_instance()) { // create a HPROF_GC_INSTANCE record for each object DumperSupport::dump_instance(writer(), o, &_class_cache); + if (java_lang_VirtualThread::is_instance(o) && ThreadDumper::should_dump_vthread(o)) { + _vthread_dumper->dump_vthread(o, writer()); + } } else if (o->is_objArray()) { // create a HPROF_GC_OBJ_ARRAY_DUMP record for each object array DumperSupport::dump_object_array(writer(), objArrayOop(o)); @@ -1908,16 +1934,52 @@ void HeapObjectDumper::do_object(oop o) { class DumperController : public CHeapObj { private: Monitor* _lock; + Mutex* _global_writer_lock; + const uint _dumper_number; uint _complete_number; + bool _started; // VM dumper started and acquired global writer lock + public: DumperController(uint number) : - _lock(new (std::nothrow) PaddedMonitor(Mutex::safepoint, "DumperController_lock")), + // _lock and _global_writer_lock are used for synchronization between GC worker threads inside safepoint, + // so we lock with _no_safepoint_check_flag. + // signal_start() acquires _lock when global writer is locked, + // its rank must be less than _global_writer_lock rank. + _lock(new (std::nothrow) PaddedMonitor(Mutex::nosafepoint - 1, "DumperController_lock")), + _global_writer_lock(new (std::nothrow) Mutex(Mutex::nosafepoint, "DumpWriter_lock")), _dumper_number(number), - _complete_number(0) { } + _complete_number(0), + _started(false) + {} + + ~DumperController() { + delete _lock; + delete _global_writer_lock; + } + + // parallel (non VM) dumpers must wait until VM dumper acquires global writer lock + void wait_for_start_signal() { + MonitorLocker ml(_lock, Mutex::_no_safepoint_check_flag); + while (_started == false) { + ml.wait(); + } + } - ~DumperController() { delete _lock; } + void signal_start() { + MonitorLocker ml(_lock, Mutex::_no_safepoint_check_flag); + _started = true; + ml.notify_all(); + } + + void lock_global_writer() { + _global_writer_lock->lock_without_safepoint_check(); + } + + void unlock_global_writer() { + _global_writer_lock->unlock(); + } void dumper_complete(DumpWriter* local_writer, DumpWriter* global_writer) { MonitorLocker ml(_lock, Mutex::_no_safepoint_check_flag); @@ -1946,7 +2008,7 @@ class DumpMerger : public StackObj { int _dump_seq; private: - void merge_file(char* path); + void merge_file(const char* path); void merge_done(); void set_error(const char* msg); @@ -1958,8 +2020,28 @@ class DumpMerger : public StackObj { _dump_seq(dump_seq) {} void do_merge(); + + // returns path for the parallel DumpWriter (resource allocated) + static char* get_writer_path(const char* base_path, int seq); + }; +char* DumpMerger::get_writer_path(const char* base_path, int seq) { + // approximate required buffer size + size_t buf_size = strlen(base_path) + + 2 // ".p" + + 10 // number (that's enough for 2^32 parallel dumpers) + + 1; // '\0' + + char* path = NEW_RESOURCE_ARRAY(char, buf_size); + memset(path, 0, buf_size); + + os::snprintf(path, buf_size, "%s.p%d", base_path, seq); + + return path; +} + + void DumpMerger::merge_done() { // Writes the HPROF_HEAP_DUMP_END record. if (!_has_error) { @@ -1980,7 +2062,7 @@ void DumpMerger::set_error(const char* msg) { // Merge segmented heap files via sendfile, it's more efficient than the // read+write combination, which would require transferring data to and from // user space. -void DumpMerger::merge_file(char* path) { +void DumpMerger::merge_file(const char* path) { assert(!SafepointSynchronize::is_at_safepoint(), "merging happens outside safepoint"); TraceTime timer("Merge segmented heap file directly", TRACETIME_LOG(Info, heapdump)); @@ -2018,7 +2100,7 @@ void DumpMerger::merge_file(char* path) { } #else // Generic implementation using read+write -void DumpMerger::merge_file(char* path) { +void DumpMerger::merge_file(const char* path) { assert(!SafepointSynchronize::is_at_safepoint(), "merging happens outside safepoint"); TraceTime timer("Merge segmented heap file", TRACETIME_LOG(Info, heapdump)); @@ -2054,10 +2136,9 @@ void DumpMerger::do_merge() { // Merge the content of the remaining files into base file. Regardless of whether // the merge process is successful or not, these segmented files will be deleted. - char path[JVM_MAXPATHLEN]; for (int i = 0; i < _dump_seq; i++) { - memset(path, 0, JVM_MAXPATHLEN); - os::snprintf(path, JVM_MAXPATHLEN, "%s.p%d", _path, i); + ResourceMark rm; + const char* path = get_writer_path(_path, i); if (!_has_error) { merge_file(path); } @@ -2087,7 +2168,7 @@ class VM_HeapDumpMerge : public VM_Operation { }; // The VM operation that performs the heap dump -class VM_HeapDumper : public VM_GC_Operation, public WorkerTask { +class VM_HeapDumper : public VM_GC_Operation, public WorkerTask, public UnmountedVThreadDumper { private: static VM_HeapDumper* _global_dumper; static DumpWriter* _global_writer; @@ -2107,10 +2188,15 @@ class VM_HeapDumper : public VM_GC_Operation, public WorkerTask { uint _num_dumper_threads; DumperController* _dumper_controller; ParallelObjectIterator* _poi; - // worker id of VMDumper thread. - static const size_t VMDumperWorkerId = 0; + + // Dumper id of VMDumper thread. + static const int VMDumperId = 0; // VM dumper dumps both heap and non-heap data, other dumpers dump heap-only data. - static bool is_vm_dumper(uint worker_id) { return worker_id == VMDumperWorkerId; } + static bool is_vm_dumper(int dumper_id) { return dumper_id == VMDumperId; } + // the 1st dumper calling get_next_dumper_id becomes VM dumper + int get_next_dumper_id() { + return Atomic::fetch_then_add(&_dump_seq, 1); + } // accessors and setters static VM_HeapDumper* dumper() { assert(_global_dumper != nullptr, "Error"); return _global_dumper; } @@ -2129,17 +2215,11 @@ class VM_HeapDumper : public VM_GC_Operation, public WorkerTask { bool skip_operation() const; - // create dump writer for every parallel dump thread - DumpWriter* create_local_writer(); - - // writes a HPROF_LOAD_CLASS record + // writes a HPROF_LOAD_CLASS record to global writer static void do_load_class(Klass* k); - // writes a HPROF_GC_CLASS_DUMP record for the given class - static void do_class_dump(Klass* k); - // HPROF_GC_ROOT_THREAD_OBJ records for platform and mounted virtual threads - void dump_threads(); + void dump_threads(AbstractDumpWriter* writer); void add_class_serial_number(Klass* k, int serial_num) { _klass_map->at_put_grow(serial_num, k); @@ -2150,7 +2230,7 @@ class VM_HeapDumper : public VM_GC_Operation, public WorkerTask { } // HPROF_TRACE and HPROF_FRAME records for platform and mounted virtual threads - void dump_stack_traces(); + void dump_stack_traces(AbstractDumpWriter* writer); public: VM_HeapDumper(DumpWriter* writer, bool gc_before_heap_dump, bool oome, uint num_dump_threads) : @@ -2168,7 +2248,7 @@ class VM_HeapDumper : public VM_GC_Operation, public WorkerTask { _thread_serial_num = 1; _frame_serial_num = 1; - _dump_seq = 0; + _dump_seq = VMDumperId; _num_dumper_threads = num_dump_threads; _dumper_controller = nullptr; _poi = nullptr; @@ -2202,12 +2282,15 @@ class VM_HeapDumper : public VM_GC_Operation, public WorkerTask { } int dump_seq() { return _dump_seq; } bool is_parallel_dump() { return _num_dumper_threads > 1; } - bool can_parallel_dump(WorkerThreads* workers); + void prepare_parallel_dump(WorkerThreads* workers); VMOp_Type type() const { return VMOp_HeapDumper; } virtual bool doit_prologue(); void doit(); void work(uint worker_id); + + // UnmountedVThreadDumper implementation + void dump_vthread(oop vt, AbstractDumpWriter* segment_writer); }; VM_HeapDumper* VM_HeapDumper::_global_dumper = nullptr; @@ -2251,22 +2334,13 @@ void VM_HeapDumper::do_load_class(Klass* k) { writer()->write_symbolID(name); } -// writes a HPROF_GC_CLASS_DUMP record for the given class -void VM_HeapDumper::do_class_dump(Klass* k) { - if (k->is_instance_klass()) { - DumperSupport::dump_instance_class(writer(), k); - } else { - DumperSupport::dump_array_class(writer(), k); - } -} - // Write a HPROF_GC_ROOT_THREAD_OBJ record for platform/carrier and mounted virtual threads. // Then walk the stack so that locals and JNI locals are dumped. -void VM_HeapDumper::dump_threads() { - for (int i = 0; i < _thread_dumpers_count; i++) { - _thread_dumpers[i]->dump_thread_obj(writer()); - _thread_dumpers[i]->dump_stack_refs(writer()); - } +void VM_HeapDumper::dump_threads(AbstractDumpWriter* writer) { + for (int i = 0; i < _thread_dumpers_count; i++) { + _thread_dumpers[i]->dump_thread_obj(writer); + _thread_dumpers[i]->dump_stack_refs(writer); + } } bool VM_HeapDumper::doit_prologue() { @@ -2280,31 +2354,21 @@ bool VM_HeapDumper::doit_prologue() { return VM_GC_Operation::doit_prologue(); } -bool VM_HeapDumper::can_parallel_dump(WorkerThreads* workers) { - bool can_parallel = true; +void VM_HeapDumper::prepare_parallel_dump(WorkerThreads* workers) { uint num_active_workers = workers != nullptr ? workers->active_workers() : 0; uint num_requested_dump_threads = _num_dumper_threads; // check if we can dump in parallel based on requested and active threads if (num_active_workers <= 1 || num_requested_dump_threads <= 1) { _num_dumper_threads = 1; - can_parallel = false; } else { - // check if we have extra path room to accommodate segmented heap files - const char* base_path = writer()->get_file_path(); - assert(base_path != nullptr, "sanity check"); - if ((strlen(base_path) + 7/*.p\d\d\d\d\0*/) >= JVM_MAXPATHLEN) { - _num_dumper_threads = 1; - can_parallel = false; - } else { - _num_dumper_threads = clamp(num_requested_dump_threads, 2U, num_active_workers); - } + _num_dumper_threads = clamp(num_requested_dump_threads, 2U, num_active_workers); } - + _dumper_controller = new (std::nothrow) DumperController(_num_dumper_threads); + bool can_parallel = _num_dumper_threads > 1; log_info(heapdump)("Requested dump threads %u, active dump threads %u, " "actual dump threads %u, parallelism %s", num_requested_dump_threads, num_active_workers, _num_dumper_threads, can_parallel ? "true" : "false"); - return can_parallel; } // The VM operation that dumps the heap. The dump consists of the following @@ -2352,11 +2416,11 @@ void VM_HeapDumper::doit() { set_global_writer(); WorkerThreads* workers = ch->safepoint_workers(); - if (!can_parallel_dump(workers)) { - work(VMDumperWorkerId); + prepare_parallel_dump(workers); + + if (!is_parallel_dump()) { + work(VMDumperId); } else { - uint heap_only_dumper_threads = _num_dumper_threads - 1 /* VMDumper thread */; - _dumper_controller = new (std::nothrow) DumperController(heap_only_dumper_threads); ParallelObjectIterator poi(_num_dumper_threads); _poi = &poi; workers->run_task(this, _num_dumper_threads); @@ -2368,26 +2432,19 @@ void VM_HeapDumper::doit() { clear_global_writer(); } -// prepare DumpWriter for every parallel dump thread -DumpWriter* VM_HeapDumper::create_local_writer() { - char* path = NEW_RESOURCE_ARRAY(char, JVM_MAXPATHLEN); - memset(path, 0, JVM_MAXPATHLEN); - - // generate segmented heap file path - const char* base_path = writer()->get_file_path(); - // share global compressor, local DumpWriter is not responsible for its life cycle - AbstractCompressor* compressor = writer()->compressor(); - int seq = Atomic::fetch_then_add(&_dump_seq, 1); - os::snprintf(path, JVM_MAXPATHLEN, "%s.p%d", base_path, seq); - - // create corresponding writer for that - DumpWriter* local_writer = new DumpWriter(path, writer()->is_overwrite(), compressor); - return local_writer; -} - void VM_HeapDumper::work(uint worker_id) { // VM Dumper works on all non-heap data dumping and part of heap iteration. - if (is_vm_dumper(worker_id)) { + int dumper_id = get_next_dumper_id(); + + if (is_vm_dumper(dumper_id)) { + // lock global writer, it will be unlocked after VM Dumper finishes with non-heap data + _dumper_controller->lock_global_writer(); + _dumper_controller->signal_start(); + } else { + _dumper_controller->wait_for_start_signal(); + } + + if (is_vm_dumper(dumper_id)) { TraceTime timer("Dump non-objects", TRACETIME_LOG(Info, heapdump)); // Write the file header - we always use 1.0.2 const char* header = "JAVA PROFILE 1.0.2"; @@ -2409,79 +2466,82 @@ void VM_HeapDumper::work(uint worker_id) { // write HPROF_FRAME and HPROF_TRACE records // this must be called after _klass_map is built when iterating the classes above. - dump_stack_traces(); + dump_stack_traces(writer()); - // HPROF_HEAP_DUMP/HPROF_HEAP_DUMP_SEGMENT starts here + // unlock global writer, so parallel dumpers can dump stack traces of unmounted virtual threads + _dumper_controller->unlock_global_writer(); + } - // Writes HPROF_GC_CLASS_DUMP records - { - LockedClassesDo locked_dump_class(&do_class_dump); - ClassLoaderDataGraph::classes_do(&locked_dump_class); - } - - // HPROF_GC_ROOT_THREAD_OBJ + frames + jni locals - dump_threads(); - - // HPROF_GC_ROOT_JNI_GLOBAL - JNIGlobalsDumper jni_dumper(writer()); - JNIHandles::oops_do(&jni_dumper); - // technically not jni roots, but global roots - // for things like preallocated throwable backtraces - Universe::vm_global()->oops_do(&jni_dumper); - // HPROF_GC_ROOT_STICKY_CLASS - // These should be classes in the null class loader data, and not all classes - // if !ClassUnloading - StickyClassDumper class_dumper(writer()); - ClassLoaderData::the_null_class_loader_data()->classes_do(&class_dumper); - } - - // Heap iteration. - // writes HPROF_GC_INSTANCE_DUMP records. - // After each sub-record is written check_segment_length will be invoked - // to check if the current segment exceeds a threshold. If so, a new - // segment is started. - // The HPROF_GC_CLASS_DUMP and HPROF_GC_INSTANCE_DUMP are the vast bulk - // of the heap dump. - if (!is_parallel_dump()) { - assert(is_vm_dumper(worker_id), "must be"); - // == Serial dump - ResourceMark rm; - TraceTime timer("Dump heap objects", TRACETIME_LOG(Info, heapdump)); - HeapObjectDumper obj_dumper(writer()); - Universe::heap()->object_iterate(&obj_dumper); - writer()->finish_dump_segment(); - // Writes the HPROF_HEAP_DUMP_END record because merge does not happen in serial dump - DumperSupport::end_of_dump(writer()); - writer()->flush(); - } else { - // == Parallel dump - ResourceMark rm; - TraceTime timer("Dump heap objects in parallel", TRACETIME_LOG(Info, heapdump)); - DumpWriter* local_writer = is_vm_dumper(worker_id) ? writer() : create_local_writer(); - if (!local_writer->has_error()) { - HeapObjectDumper obj_dumper(local_writer); - _poi->object_iterate(&obj_dumper, worker_id); - local_writer->finish_dump_segment(); - local_writer->flush(); - } - if (is_vm_dumper(worker_id)) { - _dumper_controller->wait_all_dumpers_complete(); + // HPROF_HEAP_DUMP/HPROF_HEAP_DUMP_SEGMENT starts here + + ResourceMark rm; + // share global compressor, local DumpWriter is not responsible for its life cycle + DumpWriter segment_writer(DumpMerger::get_writer_path(writer()->get_file_path(), dumper_id), + writer()->is_overwrite(), writer()->compressor()); + if (!segment_writer.has_error()) { + if (is_vm_dumper(dumper_id)) { + // dump some non-heap subrecords to heap dump segment + TraceTime timer("Dump non-objects (part 2)", TRACETIME_LOG(Info, heapdump)); + // Writes HPROF_GC_CLASS_DUMP records + ClassDumper class_dumper(&segment_writer); + ClassLoaderDataGraph::classes_do(&class_dumper); + + // HPROF_GC_ROOT_THREAD_OBJ + frames + jni locals + dump_threads(&segment_writer); + + // HPROF_GC_ROOT_JNI_GLOBAL + JNIGlobalsDumper jni_dumper(&segment_writer); + JNIHandles::oops_do(&jni_dumper); + // technically not jni roots, but global roots + // for things like preallocated throwable backtraces + Universe::vm_global()->oops_do(&jni_dumper); + // HPROF_GC_ROOT_STICKY_CLASS + // These should be classes in the null class loader data, and not all classes + // if !ClassUnloading + StickyClassDumper stiky_class_dumper(&segment_writer); + ClassLoaderData::the_null_class_loader_data()->classes_do(&stiky_class_dumper); + } + + // Heap iteration. + // writes HPROF_GC_INSTANCE_DUMP records. + // After each sub-record is written check_segment_length will be invoked + // to check if the current segment exceeds a threshold. If so, a new + // segment is started. + // The HPROF_GC_CLASS_DUMP and HPROF_GC_INSTANCE_DUMP are the vast bulk + // of the heap dump. + + TraceTime timer(is_parallel_dump() ? "Dump heap objects in parallel" : "Dump heap objects", TRACETIME_LOG(Info, heapdump)); + HeapObjectDumper obj_dumper(&segment_writer, this); + if (!is_parallel_dump()) { + Universe::heap()->object_iterate(&obj_dumper); } else { - _dumper_controller->dumper_complete(local_writer, writer()); - delete local_writer; - return; + // == Parallel dump + _poi->object_iterate(&obj_dumper, worker_id); } + + segment_writer.finish_dump_segment(); + segment_writer.flush(); + } + + _dumper_controller->dumper_complete(&segment_writer, writer()); + + if (is_vm_dumper(dumper_id)) { + _dumper_controller->wait_all_dumpers_complete(); + + // flush global writer + writer()->flush(); + + // At this point, all fragments of the heapdump have been written to separate files. + // We need to merge them into a complete heapdump and write HPROF_HEAP_DUMP_END at that time. } - // At this point, all fragments of the heapdump have been written to separate files. - // We need to merge them into a complete heapdump and write HPROF_HEAP_DUMP_END at that time. } -void VM_HeapDumper::dump_stack_traces() { +void VM_HeapDumper::dump_stack_traces(AbstractDumpWriter* writer) { // write a HPROF_TRACE record without any frames to be referenced as object alloc sites - DumperSupport::write_header(writer(), HPROF_TRACE, 3 * sizeof(u4)); - writer()->write_u4((u4)STACK_TRACE_ID); - writer()->write_u4(0); // thread number - writer()->write_u4(0); // frame count + DumperSupport::write_header(writer, HPROF_TRACE, 3 * sizeof(u4)); + writer->write_u4((u4)STACK_TRACE_ID); + writer->write_u4(0); // thread number + writer->write_u4(0); // frame count // max number if every platform thread is carrier with mounted virtual thread _thread_dumpers = NEW_C_HEAP_ARRAY(ThreadDumper*, Threads::number_of_threads() * 2, mtInternal); @@ -2505,7 +2565,7 @@ void VM_HeapDumper::dump_stack_traces() { add_oom_frame = false; } thread_dumper->init_serial_nums(&_thread_serial_num, &_frame_serial_num); - thread_dumper->dump_stack_traces(writer(), _klass_map); + thread_dumper->dump_stack_traces(writer, _klass_map); } // platform or carrier thread @@ -2515,11 +2575,27 @@ void VM_HeapDumper::dump_stack_traces() { thread_dumper->add_oom_frame(_oome_constructor); } thread_dumper->init_serial_nums(&_thread_serial_num, &_frame_serial_num); - thread_dumper->dump_stack_traces(writer(), _klass_map); + thread_dumper->dump_stack_traces(writer, _klass_map); } } } +void VM_HeapDumper::dump_vthread(oop vt, AbstractDumpWriter* segment_writer) { + // unmounted vthread has no JavaThread + ThreadDumper thread_dumper(ThreadDumper::ThreadType::UnmountedVirtual, nullptr, vt); + thread_dumper.init_serial_nums(&_thread_serial_num, &_frame_serial_num); + + // write HPROF_TRACE/HPROF_FRAME records to global writer + _dumper_controller->lock_global_writer(); + thread_dumper.dump_stack_traces(writer(), _klass_map); + _dumper_controller->unlock_global_writer(); + + // write HPROF_GC_ROOT_THREAD_OBJ/HPROF_GC_ROOT_JAVA_FRAME/HPROF_GC_ROOT_JNI_LOCAL subrecord + // to segment writer + thread_dumper.dump_thread_obj(segment_writer); + thread_dumper.dump_stack_refs(segment_writer); +} + // dump the heap to given path. int HeapDumper::dump(const char* path, outputStream* out, int compression, bool overwrite, uint num_dump_threads) { assert(path != nullptr && strlen(path) > 0, "path missing"); @@ -2561,28 +2637,27 @@ int HeapDumper::dump(const char* path, outputStream* out, int compression, bool // record any error that the writer may have encountered set_error(writer.error()); - // For serial dump, once VM_HeapDumper completes, the whole heap dump process - // is done, no further phases needed. For parallel dump, the whole heap dump - // process is done in two phases + // Heap dump process is done in two phases // // Phase 1: Concurrent threads directly write heap data to multiple heap files. // This is done by VM_HeapDumper, which is performed within safepoint. // // Phase 2: Merge multiple heap files into one complete heap dump file. // This is done by DumpMerger, which is performed outside safepoint - if (dumper.is_parallel_dump()) { - DumpMerger merger(path, &writer, dumper.dump_seq()); - Thread* current_thread = Thread::current(); - if (current_thread->is_AttachListener_thread()) { - // perform heapdump file merge operation in the current thread prevents us - // from occupying the VM Thread, which in turn affects the occurrence of - // GC and other VM operations. - merger.do_merge(); - } else { - // otherwise, performs it by VM thread - VM_HeapDumpMerge op(&merger); - VMThread::execute(&op); - } + + DumpMerger merger(path, &writer, dumper.dump_seq()); + Thread* current_thread = Thread::current(); + if (current_thread->is_AttachListener_thread()) { + // perform heapdump file merge operation in the current thread prevents us + // from occupying the VM Thread, which in turn affects the occurrence of + // GC and other VM operations. + merger.do_merge(); + } else { + // otherwise, performs it by VM thread + VM_HeapDumpMerge op(&merger); + VMThread::execute(&op); + } + if (writer.error() != nullptr) { set_error(writer.error()); } diff --git a/test/hotspot/jtreg/serviceability/dcmd/gc/HeapDumpParallelTest.java b/test/hotspot/jtreg/serviceability/dcmd/gc/HeapDumpParallelTest.java index 2ea09dd1597..0010c5bdd78 100644 --- a/test/hotspot/jtreg/serviceability/dcmd/gc/HeapDumpParallelTest.java +++ b/test/hotspot/jtreg/serviceability/dcmd/gc/HeapDumpParallelTest.java @@ -66,7 +66,6 @@ private static void checkAndVerify(OutputAnalyzer dcmdOut, LingeredApp app, File appOut.shouldContain("Merge heap files complete"); } else { appOut.shouldNotContain("Dump heap objects in parallel"); - appOut.shouldNotContain("Merge heap files complete"); } HprofParser.parseAndVerify(heapDumpFile); diff --git a/test/hotspot/jtreg/serviceability/jvmti/vthread/HeapDump/VThreadInHeapDump.java b/test/hotspot/jtreg/serviceability/jvmti/vthread/HeapDump/VThreadInHeapDump.java index 89a9a0514bf..7fe5abb800f 100644 --- a/test/hotspot/jtreg/serviceability/jvmti/vthread/HeapDump/VThreadInHeapDump.java +++ b/test/hotspot/jtreg/serviceability/jvmti/vthread/HeapDump/VThreadInHeapDump.java @@ -183,6 +183,7 @@ private static void createDump(File dumpFile, String[] extraOptions) throws Exce List extraVMArgs = new ArrayList<>(); extraVMArgs.add("-Djdk.virtualThreadScheduler.parallelism=1"); + extraVMArgs.add("-Xlog:heapdump"); extraVMArgs.addAll(Arrays.asList(extraOptions)); LingeredApp.startApp(theApp, extraVMArgs.toArray(new String[0])); @@ -252,8 +253,7 @@ private static void verifyDump(File dumpFile) throws Exception { // Verify objects from thread stacks are dumped. test(snapshot, VThreadInHeapDumpTarg.VThreadMountedReferenced.class); test(snapshot, VThreadInHeapDumpTarg.PThreadReferenced.class); - // Dumping of unmounted vthreads is not implemented yet - //test(snapshot, VThreadInHeapDumpTarg.VThreadUnmountedReferenced.class); + test(snapshot, VThreadInHeapDumpTarg.VThreadUnmountedReferenced.class); } } From 11e4a925bec3c1f79e03045d48def53188b655e6 Mon Sep 17 00:00:00 2001 From: Weijun Wang Date: Thu, 7 Dec 2023 23:25:56 +0000 Subject: [PATCH 05/57] 8320597: RSA signature verification fails on signed data that does not encode params correctly Reviewed-by: mullan, valeriep --- .../sun/security/rsa/RSASignature.java | 9 +++ .../classes/sun/security/rsa/RSAUtil.java | 33 ++++------- test/jdk/sun/security/rsa/WithoutNULL.java | 57 +++++++++++++++++++ 3 files changed, 76 insertions(+), 23 deletions(-) create mode 100644 test/jdk/sun/security/rsa/WithoutNULL.java diff --git a/src/java.base/share/classes/sun/security/rsa/RSASignature.java b/src/java.base/share/classes/sun/security/rsa/RSASignature.java index df74e3e75b4..d8380343fff 100644 --- a/src/java.base/share/classes/sun/security/rsa/RSASignature.java +++ b/src/java.base/share/classes/sun/security/rsa/RSASignature.java @@ -217,8 +217,17 @@ protected boolean engineVerify(byte[] sigBytes) throws SignatureException { byte[] decrypted = RSACore.rsa(sigBytes, publicKey); byte[] digest = getDigestValue(); + byte[] encoded = RSAUtil.encodeSignature(digestOID, digest); byte[] padded = padding.pad(encoded); + if (MessageDigest.isEqual(padded, decrypted)) { + return true; + } + + // Some vendors might omit the NULL params in digest algorithm + // identifier. Try again. + encoded = RSAUtil.encodeSignatureWithoutNULL(digestOID, digest); + padded = padding.pad(encoded); return MessageDigest.isEqual(padded, decrypted); } catch (javax.crypto.BadPaddingException e) { return false; diff --git a/src/java.base/share/classes/sun/security/rsa/RSAUtil.java b/src/java.base/share/classes/sun/security/rsa/RSAUtil.java index 93f5d2f215e..080703ae555 100644 --- a/src/java.base/share/classes/sun/security/rsa/RSAUtil.java +++ b/src/java.base/share/classes/sun/security/rsa/RSAUtil.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2023, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -180,28 +180,15 @@ public static byte[] encodeSignature(ObjectIdentifier oid, byte[] digest) { } /** - * Decode the signature data. Verify that the object identifier matches - * and return the message digest. + * Encode the digest without the NULL params, return the to-be-signed data. + * This is only used by SunRsaSign. */ - public static byte[] decodeSignature(ObjectIdentifier oid, byte[] sig) - throws IOException { - // Enforce strict DER checking for signatures - DerInputStream in = new DerInputStream(sig, 0, sig.length, false); - DerValue[] values = in.getSequence(2); - if ((values.length != 2) || (in.available() != 0)) { - throw new IOException("SEQUENCE length error"); - } - AlgorithmId algId = AlgorithmId.parse(values[0]); - if (!algId.getOID().equals(oid)) { - throw new IOException("ObjectIdentifier mismatch: " - + algId.getOID()); - } - if (algId.getEncodedParams() != null) { - throw new IOException("Unexpected AlgorithmId parameters"); - } - if (values[1].isConstructed()) { - throw new IOException("Unexpected constructed digest value"); - } - return values[1].getOctetString(); + static byte[] encodeSignatureWithoutNULL(ObjectIdentifier oid, byte[] digest) { + DerOutputStream out = new DerOutputStream(); + out.write(DerValue.tag_Sequence, new DerOutputStream().putOID(oid)); + out.putOctetString(digest); + DerValue result = + new DerValue(DerValue.tag_Sequence, out.toByteArray()); + return result.toByteArray(); } } diff --git a/test/jdk/sun/security/rsa/WithoutNULL.java b/test/jdk/sun/security/rsa/WithoutNULL.java new file mode 100644 index 00000000000..64cf831099d --- /dev/null +++ b/test/jdk/sun/security/rsa/WithoutNULL.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * @test + * @bug 8320597 + * @summary Verify RSA signature with omitted digest params (should be encoded as NULL) + * for backward compatibility + */ +import java.security.KeyFactory; +import java.security.Signature; +import java.security.spec.X509EncodedKeySpec; +import java.util.Base64; + +public class WithoutNULL { + public static void main(String[] args) throws Exception { + + // A 1024-bit RSA public key + byte[] key = Base64.getMimeDecoder().decode(""" + MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrfTrEm4KvdFSpGAM7InrFEzALTKdphT9fK6Gu + eVjHtKsuCSEaULCdjhJvPpFK40ONr1JEC1Ywp1UYrfBBdKunnbDZqNZL1cFv+IzF4Yj6JO6pOeHi + 1Zpur1GaQRRlYTvzmyWY/AATQDh8JfKObNnDVwXeezFODUG8h5+XL1ZXZQIDAQAB"""); + + // A SHA1withRSA signature on an empty input where the digestAlgorithm + // inside DigestInfo does not have a parameters field. + byte[] sig = Base64.getMimeDecoder().decode(""" + D1FpiT44WEXlDfYK880bdorLO+e9qJVXZWiBgqs9dfK7lYQwyEt9dL23mbUAKm5TVEj2ZxtHkEvk + b8oaWkxk069jDTM1RhllPJZkAjeQRbw4gkg4N6wKZz9B/jdSRMNJg/b9QdRYZOHOBxsEHMbUREPV + DoCOLaxB8eIXX0EWkiE="""); + + Signature s = Signature.getInstance("SHA1withRSA", "SunRsaSign"); + s.initVerify(KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(key))); + if (!s.verify(sig)) { + throw new RuntimeException("Does not verify"); + } + } +} From 25dc4762b419a6b09d17d9055b1f75e4b531458a Mon Sep 17 00:00:00 2001 From: Phil Race Date: Thu, 7 Dec 2023 23:33:56 +0000 Subject: [PATCH 06/57] 8286827: BogusColorSpace methods return wrong array Reviewed-by: bpb, serb --- .../com/sun/imageio/plugins/common/BogusColorSpace.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/java.desktop/share/classes/com/sun/imageio/plugins/common/BogusColorSpace.java b/src/java.desktop/share/classes/com/sun/imageio/plugins/common/BogusColorSpace.java index a62008dfbd4..5a064f4f4c5 100644 --- a/src/java.desktop/share/classes/com/sun/imageio/plugins/common/BogusColorSpace.java +++ b/src/java.desktop/share/classes/com/sun/imageio/plugins/common/BogusColorSpace.java @@ -90,7 +90,7 @@ public float[] toRGB(float[] colorvalue) { System.arraycopy(colorvalue, 0, rgbvalue, 0, Math.min(3, getNumComponents())); - return colorvalue; + return rgbvalue; } public float[] fromRGB(float[] rgbvalue) { @@ -104,7 +104,7 @@ public float[] fromRGB(float[] rgbvalue) { System.arraycopy(rgbvalue, 0, colorvalue, 0, Math.min(3, colorvalue.length)); - return rgbvalue; + return colorvalue; } public float[] toCIEXYZ(float[] colorvalue) { @@ -118,7 +118,7 @@ public float[] toCIEXYZ(float[] colorvalue) { System.arraycopy(colorvalue, 0, xyzvalue, 0, Math.min(3, getNumComponents())); - return colorvalue; + return xyzvalue; } public float[] fromCIEXYZ(float[] xyzvalue) { @@ -132,6 +132,6 @@ public float[] fromCIEXYZ(float[] xyzvalue) { System.arraycopy(xyzvalue, 0, colorvalue, 0, Math.min(3, colorvalue.length)); - return xyzvalue; + return colorvalue; } } From cb7e3d263a6ed65257b316ffcbbd5e19142eb8d1 Mon Sep 17 00:00:00 2001 From: Alex Menkov Date: Fri, 8 Dec 2023 01:24:25 +0000 Subject: [PATCH 07/57] 8321560: [BACKOUT] 8299426: Heap dump does not contain virtual Thread stack references Reviewed-by: cjplummer, dholmes --- src/hotspot/share/services/heapDumper.cpp | 401 +++++++----------- .../dcmd/gc/HeapDumpParallelTest.java | 1 + .../vthread/HeapDump/VThreadInHeapDump.java | 4 +- 3 files changed, 166 insertions(+), 240 deletions(-) diff --git a/src/hotspot/share/services/heapDumper.cpp b/src/hotspot/share/services/heapDumper.cpp index 9f48e794ea6..18b91b6f289 100644 --- a/src/hotspot/share/services/heapDumper.cpp +++ b/src/hotspot/share/services/heapDumper.cpp @@ -1473,25 +1473,6 @@ void SymbolTableDumper::do_symbol(Symbol** p) { } } -// Support class used to generate HPROF_GC_CLASS_DUMP records - -class ClassDumper : public KlassClosure { - private: - AbstractDumpWriter* _writer; - AbstractDumpWriter* writer() const { return _writer; } - - public: - ClassDumper(AbstractDumpWriter* writer) : _writer(writer) {} - - void do_klass(Klass* k) { - if (k->is_instance_klass()) { - DumperSupport::dump_instance_class(writer(), k); - } else { - DumperSupport::dump_array_class(writer(), k); - } - } -}; - // Support class used to generate HPROF_GC_ROOT_JNI_LOCAL records class JNILocalsDumper : public OopClosure { @@ -1879,25 +1860,21 @@ vframe* ThreadDumper::get_top_frame() const { return nullptr; } -// Callback to dump thread-related data for unmounted virtual threads; -// implemented by VM_HeapDumper. -class UnmountedVThreadDumper { - public: - virtual void dump_vthread(oop vt, AbstractDumpWriter* segment_writer) = 0; -}; -// Support class used when iterating over the heap. +class VM_HeapDumper; + +// Support class using when iterating over the heap. class HeapObjectDumper : public ObjectClosure { private: AbstractDumpWriter* _writer; AbstractDumpWriter* writer() { return _writer; } - UnmountedVThreadDumper* _vthread_dumper; DumperClassCacheTable _class_cache; public: - HeapObjectDumper(AbstractDumpWriter* writer, UnmountedVThreadDumper* vthread_dumper) - : _writer(writer), _vthread_dumper(vthread_dumper) {} + HeapObjectDumper(AbstractDumpWriter* writer) { + _writer = writer; + } // called for each object in the heap void do_object(oop o); @@ -1918,9 +1895,6 @@ void HeapObjectDumper::do_object(oop o) { if (o->is_instance()) { // create a HPROF_GC_INSTANCE record for each object DumperSupport::dump_instance(writer(), o, &_class_cache); - if (java_lang_VirtualThread::is_instance(o) && ThreadDumper::should_dump_vthread(o)) { - _vthread_dumper->dump_vthread(o, writer()); - } } else if (o->is_objArray()) { // create a HPROF_GC_OBJ_ARRAY_DUMP record for each object array DumperSupport::dump_object_array(writer(), objArrayOop(o)); @@ -1934,52 +1908,16 @@ void HeapObjectDumper::do_object(oop o) { class DumperController : public CHeapObj { private: Monitor* _lock; - Mutex* _global_writer_lock; - const uint _dumper_number; uint _complete_number; - bool _started; // VM dumper started and acquired global writer lock - public: DumperController(uint number) : - // _lock and _global_writer_lock are used for synchronization between GC worker threads inside safepoint, - // so we lock with _no_safepoint_check_flag. - // signal_start() acquires _lock when global writer is locked, - // its rank must be less than _global_writer_lock rank. - _lock(new (std::nothrow) PaddedMonitor(Mutex::nosafepoint - 1, "DumperController_lock")), - _global_writer_lock(new (std::nothrow) Mutex(Mutex::nosafepoint, "DumpWriter_lock")), + _lock(new (std::nothrow) PaddedMonitor(Mutex::safepoint, "DumperController_lock")), _dumper_number(number), - _complete_number(0), - _started(false) - {} - - ~DumperController() { - delete _lock; - delete _global_writer_lock; - } - - // parallel (non VM) dumpers must wait until VM dumper acquires global writer lock - void wait_for_start_signal() { - MonitorLocker ml(_lock, Mutex::_no_safepoint_check_flag); - while (_started == false) { - ml.wait(); - } - } + _complete_number(0) { } - void signal_start() { - MonitorLocker ml(_lock, Mutex::_no_safepoint_check_flag); - _started = true; - ml.notify_all(); - } - - void lock_global_writer() { - _global_writer_lock->lock_without_safepoint_check(); - } - - void unlock_global_writer() { - _global_writer_lock->unlock(); - } + ~DumperController() { delete _lock; } void dumper_complete(DumpWriter* local_writer, DumpWriter* global_writer) { MonitorLocker ml(_lock, Mutex::_no_safepoint_check_flag); @@ -2008,7 +1946,7 @@ class DumpMerger : public StackObj { int _dump_seq; private: - void merge_file(const char* path); + void merge_file(char* path); void merge_done(); void set_error(const char* msg); @@ -2020,28 +1958,8 @@ class DumpMerger : public StackObj { _dump_seq(dump_seq) {} void do_merge(); - - // returns path for the parallel DumpWriter (resource allocated) - static char* get_writer_path(const char* base_path, int seq); - }; -char* DumpMerger::get_writer_path(const char* base_path, int seq) { - // approximate required buffer size - size_t buf_size = strlen(base_path) - + 2 // ".p" - + 10 // number (that's enough for 2^32 parallel dumpers) - + 1; // '\0' - - char* path = NEW_RESOURCE_ARRAY(char, buf_size); - memset(path, 0, buf_size); - - os::snprintf(path, buf_size, "%s.p%d", base_path, seq); - - return path; -} - - void DumpMerger::merge_done() { // Writes the HPROF_HEAP_DUMP_END record. if (!_has_error) { @@ -2062,7 +1980,7 @@ void DumpMerger::set_error(const char* msg) { // Merge segmented heap files via sendfile, it's more efficient than the // read+write combination, which would require transferring data to and from // user space. -void DumpMerger::merge_file(const char* path) { +void DumpMerger::merge_file(char* path) { assert(!SafepointSynchronize::is_at_safepoint(), "merging happens outside safepoint"); TraceTime timer("Merge segmented heap file directly", TRACETIME_LOG(Info, heapdump)); @@ -2100,7 +2018,7 @@ void DumpMerger::merge_file(const char* path) { } #else // Generic implementation using read+write -void DumpMerger::merge_file(const char* path) { +void DumpMerger::merge_file(char* path) { assert(!SafepointSynchronize::is_at_safepoint(), "merging happens outside safepoint"); TraceTime timer("Merge segmented heap file", TRACETIME_LOG(Info, heapdump)); @@ -2136,9 +2054,10 @@ void DumpMerger::do_merge() { // Merge the content of the remaining files into base file. Regardless of whether // the merge process is successful or not, these segmented files will be deleted. + char path[JVM_MAXPATHLEN]; for (int i = 0; i < _dump_seq; i++) { - ResourceMark rm; - const char* path = get_writer_path(_path, i); + memset(path, 0, JVM_MAXPATHLEN); + os::snprintf(path, JVM_MAXPATHLEN, "%s.p%d", _path, i); if (!_has_error) { merge_file(path); } @@ -2168,7 +2087,7 @@ class VM_HeapDumpMerge : public VM_Operation { }; // The VM operation that performs the heap dump -class VM_HeapDumper : public VM_GC_Operation, public WorkerTask, public UnmountedVThreadDumper { +class VM_HeapDumper : public VM_GC_Operation, public WorkerTask { private: static VM_HeapDumper* _global_dumper; static DumpWriter* _global_writer; @@ -2188,15 +2107,10 @@ class VM_HeapDumper : public VM_GC_Operation, public WorkerTask, public Unmounte uint _num_dumper_threads; DumperController* _dumper_controller; ParallelObjectIterator* _poi; - - // Dumper id of VMDumper thread. - static const int VMDumperId = 0; + // worker id of VMDumper thread. + static const size_t VMDumperWorkerId = 0; // VM dumper dumps both heap and non-heap data, other dumpers dump heap-only data. - static bool is_vm_dumper(int dumper_id) { return dumper_id == VMDumperId; } - // the 1st dumper calling get_next_dumper_id becomes VM dumper - int get_next_dumper_id() { - return Atomic::fetch_then_add(&_dump_seq, 1); - } + static bool is_vm_dumper(uint worker_id) { return worker_id == VMDumperWorkerId; } // accessors and setters static VM_HeapDumper* dumper() { assert(_global_dumper != nullptr, "Error"); return _global_dumper; } @@ -2215,11 +2129,17 @@ class VM_HeapDumper : public VM_GC_Operation, public WorkerTask, public Unmounte bool skip_operation() const; - // writes a HPROF_LOAD_CLASS record to global writer + // create dump writer for every parallel dump thread + DumpWriter* create_local_writer(); + + // writes a HPROF_LOAD_CLASS record static void do_load_class(Klass* k); + // writes a HPROF_GC_CLASS_DUMP record for the given class + static void do_class_dump(Klass* k); + // HPROF_GC_ROOT_THREAD_OBJ records for platform and mounted virtual threads - void dump_threads(AbstractDumpWriter* writer); + void dump_threads(); void add_class_serial_number(Klass* k, int serial_num) { _klass_map->at_put_grow(serial_num, k); @@ -2230,7 +2150,7 @@ class VM_HeapDumper : public VM_GC_Operation, public WorkerTask, public Unmounte } // HPROF_TRACE and HPROF_FRAME records for platform and mounted virtual threads - void dump_stack_traces(AbstractDumpWriter* writer); + void dump_stack_traces(); public: VM_HeapDumper(DumpWriter* writer, bool gc_before_heap_dump, bool oome, uint num_dump_threads) : @@ -2248,7 +2168,7 @@ class VM_HeapDumper : public VM_GC_Operation, public WorkerTask, public Unmounte _thread_serial_num = 1; _frame_serial_num = 1; - _dump_seq = VMDumperId; + _dump_seq = 0; _num_dumper_threads = num_dump_threads; _dumper_controller = nullptr; _poi = nullptr; @@ -2282,15 +2202,12 @@ class VM_HeapDumper : public VM_GC_Operation, public WorkerTask, public Unmounte } int dump_seq() { return _dump_seq; } bool is_parallel_dump() { return _num_dumper_threads > 1; } - void prepare_parallel_dump(WorkerThreads* workers); + bool can_parallel_dump(WorkerThreads* workers); VMOp_Type type() const { return VMOp_HeapDumper; } virtual bool doit_prologue(); void doit(); void work(uint worker_id); - - // UnmountedVThreadDumper implementation - void dump_vthread(oop vt, AbstractDumpWriter* segment_writer); }; VM_HeapDumper* VM_HeapDumper::_global_dumper = nullptr; @@ -2334,13 +2251,22 @@ void VM_HeapDumper::do_load_class(Klass* k) { writer()->write_symbolID(name); } +// writes a HPROF_GC_CLASS_DUMP record for the given class +void VM_HeapDumper::do_class_dump(Klass* k) { + if (k->is_instance_klass()) { + DumperSupport::dump_instance_class(writer(), k); + } else { + DumperSupport::dump_array_class(writer(), k); + } +} + // Write a HPROF_GC_ROOT_THREAD_OBJ record for platform/carrier and mounted virtual threads. // Then walk the stack so that locals and JNI locals are dumped. -void VM_HeapDumper::dump_threads(AbstractDumpWriter* writer) { - for (int i = 0; i < _thread_dumpers_count; i++) { - _thread_dumpers[i]->dump_thread_obj(writer); - _thread_dumpers[i]->dump_stack_refs(writer); - } +void VM_HeapDumper::dump_threads() { + for (int i = 0; i < _thread_dumpers_count; i++) { + _thread_dumpers[i]->dump_thread_obj(writer()); + _thread_dumpers[i]->dump_stack_refs(writer()); + } } bool VM_HeapDumper::doit_prologue() { @@ -2354,21 +2280,31 @@ bool VM_HeapDumper::doit_prologue() { return VM_GC_Operation::doit_prologue(); } -void VM_HeapDumper::prepare_parallel_dump(WorkerThreads* workers) { +bool VM_HeapDumper::can_parallel_dump(WorkerThreads* workers) { + bool can_parallel = true; uint num_active_workers = workers != nullptr ? workers->active_workers() : 0; uint num_requested_dump_threads = _num_dumper_threads; // check if we can dump in parallel based on requested and active threads if (num_active_workers <= 1 || num_requested_dump_threads <= 1) { _num_dumper_threads = 1; + can_parallel = false; } else { - _num_dumper_threads = clamp(num_requested_dump_threads, 2U, num_active_workers); + // check if we have extra path room to accommodate segmented heap files + const char* base_path = writer()->get_file_path(); + assert(base_path != nullptr, "sanity check"); + if ((strlen(base_path) + 7/*.p\d\d\d\d\0*/) >= JVM_MAXPATHLEN) { + _num_dumper_threads = 1; + can_parallel = false; + } else { + _num_dumper_threads = clamp(num_requested_dump_threads, 2U, num_active_workers); + } } - _dumper_controller = new (std::nothrow) DumperController(_num_dumper_threads); - bool can_parallel = _num_dumper_threads > 1; + log_info(heapdump)("Requested dump threads %u, active dump threads %u, " "actual dump threads %u, parallelism %s", num_requested_dump_threads, num_active_workers, _num_dumper_threads, can_parallel ? "true" : "false"); + return can_parallel; } // The VM operation that dumps the heap. The dump consists of the following @@ -2416,11 +2352,11 @@ void VM_HeapDumper::doit() { set_global_writer(); WorkerThreads* workers = ch->safepoint_workers(); - prepare_parallel_dump(workers); - - if (!is_parallel_dump()) { - work(VMDumperId); + if (!can_parallel_dump(workers)) { + work(VMDumperWorkerId); } else { + uint heap_only_dumper_threads = _num_dumper_threads - 1 /* VMDumper thread */; + _dumper_controller = new (std::nothrow) DumperController(heap_only_dumper_threads); ParallelObjectIterator poi(_num_dumper_threads); _poi = &poi; workers->run_task(this, _num_dumper_threads); @@ -2432,19 +2368,26 @@ void VM_HeapDumper::doit() { clear_global_writer(); } -void VM_HeapDumper::work(uint worker_id) { - // VM Dumper works on all non-heap data dumping and part of heap iteration. - int dumper_id = get_next_dumper_id(); +// prepare DumpWriter for every parallel dump thread +DumpWriter* VM_HeapDumper::create_local_writer() { + char* path = NEW_RESOURCE_ARRAY(char, JVM_MAXPATHLEN); + memset(path, 0, JVM_MAXPATHLEN); - if (is_vm_dumper(dumper_id)) { - // lock global writer, it will be unlocked after VM Dumper finishes with non-heap data - _dumper_controller->lock_global_writer(); - _dumper_controller->signal_start(); - } else { - _dumper_controller->wait_for_start_signal(); - } + // generate segmented heap file path + const char* base_path = writer()->get_file_path(); + // share global compressor, local DumpWriter is not responsible for its life cycle + AbstractCompressor* compressor = writer()->compressor(); + int seq = Atomic::fetch_then_add(&_dump_seq, 1); + os::snprintf(path, JVM_MAXPATHLEN, "%s.p%d", base_path, seq); + + // create corresponding writer for that + DumpWriter* local_writer = new DumpWriter(path, writer()->is_overwrite(), compressor); + return local_writer; +} - if (is_vm_dumper(dumper_id)) { +void VM_HeapDumper::work(uint worker_id) { + // VM Dumper works on all non-heap data dumping and part of heap iteration. + if (is_vm_dumper(worker_id)) { TraceTime timer("Dump non-objects", TRACETIME_LOG(Info, heapdump)); // Write the file header - we always use 1.0.2 const char* header = "JAVA PROFILE 1.0.2"; @@ -2466,82 +2409,79 @@ void VM_HeapDumper::work(uint worker_id) { // write HPROF_FRAME and HPROF_TRACE records // this must be called after _klass_map is built when iterating the classes above. - dump_stack_traces(writer()); - - // unlock global writer, so parallel dumpers can dump stack traces of unmounted virtual threads - _dumper_controller->unlock_global_writer(); - } + dump_stack_traces(); - // HPROF_HEAP_DUMP/HPROF_HEAP_DUMP_SEGMENT starts here + // HPROF_HEAP_DUMP/HPROF_HEAP_DUMP_SEGMENT starts here - ResourceMark rm; - // share global compressor, local DumpWriter is not responsible for its life cycle - DumpWriter segment_writer(DumpMerger::get_writer_path(writer()->get_file_path(), dumper_id), - writer()->is_overwrite(), writer()->compressor()); - if (!segment_writer.has_error()) { - if (is_vm_dumper(dumper_id)) { - // dump some non-heap subrecords to heap dump segment - TraceTime timer("Dump non-objects (part 2)", TRACETIME_LOG(Info, heapdump)); - // Writes HPROF_GC_CLASS_DUMP records - ClassDumper class_dumper(&segment_writer); - ClassLoaderDataGraph::classes_do(&class_dumper); - - // HPROF_GC_ROOT_THREAD_OBJ + frames + jni locals - dump_threads(&segment_writer); - - // HPROF_GC_ROOT_JNI_GLOBAL - JNIGlobalsDumper jni_dumper(&segment_writer); - JNIHandles::oops_do(&jni_dumper); - // technically not jni roots, but global roots - // for things like preallocated throwable backtraces - Universe::vm_global()->oops_do(&jni_dumper); - // HPROF_GC_ROOT_STICKY_CLASS - // These should be classes in the null class loader data, and not all classes - // if !ClassUnloading - StickyClassDumper stiky_class_dumper(&segment_writer); - ClassLoaderData::the_null_class_loader_data()->classes_do(&stiky_class_dumper); - } - - // Heap iteration. - // writes HPROF_GC_INSTANCE_DUMP records. - // After each sub-record is written check_segment_length will be invoked - // to check if the current segment exceeds a threshold. If so, a new - // segment is started. - // The HPROF_GC_CLASS_DUMP and HPROF_GC_INSTANCE_DUMP are the vast bulk - // of the heap dump. - - TraceTime timer(is_parallel_dump() ? "Dump heap objects in parallel" : "Dump heap objects", TRACETIME_LOG(Info, heapdump)); - HeapObjectDumper obj_dumper(&segment_writer, this); - if (!is_parallel_dump()) { - Universe::heap()->object_iterate(&obj_dumper); - } else { - // == Parallel dump + // Writes HPROF_GC_CLASS_DUMP records + { + LockedClassesDo locked_dump_class(&do_class_dump); + ClassLoaderDataGraph::classes_do(&locked_dump_class); + } + + // HPROF_GC_ROOT_THREAD_OBJ + frames + jni locals + dump_threads(); + + // HPROF_GC_ROOT_JNI_GLOBAL + JNIGlobalsDumper jni_dumper(writer()); + JNIHandles::oops_do(&jni_dumper); + // technically not jni roots, but global roots + // for things like preallocated throwable backtraces + Universe::vm_global()->oops_do(&jni_dumper); + // HPROF_GC_ROOT_STICKY_CLASS + // These should be classes in the null class loader data, and not all classes + // if !ClassUnloading + StickyClassDumper class_dumper(writer()); + ClassLoaderData::the_null_class_loader_data()->classes_do(&class_dumper); + } + + // Heap iteration. + // writes HPROF_GC_INSTANCE_DUMP records. + // After each sub-record is written check_segment_length will be invoked + // to check if the current segment exceeds a threshold. If so, a new + // segment is started. + // The HPROF_GC_CLASS_DUMP and HPROF_GC_INSTANCE_DUMP are the vast bulk + // of the heap dump. + if (!is_parallel_dump()) { + assert(is_vm_dumper(worker_id), "must be"); + // == Serial dump + ResourceMark rm; + TraceTime timer("Dump heap objects", TRACETIME_LOG(Info, heapdump)); + HeapObjectDumper obj_dumper(writer()); + Universe::heap()->object_iterate(&obj_dumper); + writer()->finish_dump_segment(); + // Writes the HPROF_HEAP_DUMP_END record because merge does not happen in serial dump + DumperSupport::end_of_dump(writer()); + writer()->flush(); + } else { + // == Parallel dump + ResourceMark rm; + TraceTime timer("Dump heap objects in parallel", TRACETIME_LOG(Info, heapdump)); + DumpWriter* local_writer = is_vm_dumper(worker_id) ? writer() : create_local_writer(); + if (!local_writer->has_error()) { + HeapObjectDumper obj_dumper(local_writer); _poi->object_iterate(&obj_dumper, worker_id); + local_writer->finish_dump_segment(); + local_writer->flush(); + } + if (is_vm_dumper(worker_id)) { + _dumper_controller->wait_all_dumpers_complete(); + } else { + _dumper_controller->dumper_complete(local_writer, writer()); + delete local_writer; + return; } - - segment_writer.finish_dump_segment(); - segment_writer.flush(); - } - - _dumper_controller->dumper_complete(&segment_writer, writer()); - - if (is_vm_dumper(dumper_id)) { - _dumper_controller->wait_all_dumpers_complete(); - - // flush global writer - writer()->flush(); - - // At this point, all fragments of the heapdump have been written to separate files. - // We need to merge them into a complete heapdump and write HPROF_HEAP_DUMP_END at that time. } + // At this point, all fragments of the heapdump have been written to separate files. + // We need to merge them into a complete heapdump and write HPROF_HEAP_DUMP_END at that time. } -void VM_HeapDumper::dump_stack_traces(AbstractDumpWriter* 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)); - writer->write_u4((u4)STACK_TRACE_ID); - writer->write_u4(0); // thread number - writer->write_u4(0); // frame count + DumperSupport::write_header(writer(), HPROF_TRACE, 3 * sizeof(u4)); + writer()->write_u4((u4)STACK_TRACE_ID); + writer()->write_u4(0); // thread number + writer()->write_u4(0); // frame count // max number if every platform thread is carrier with mounted virtual thread _thread_dumpers = NEW_C_HEAP_ARRAY(ThreadDumper*, Threads::number_of_threads() * 2, mtInternal); @@ -2565,7 +2505,7 @@ void VM_HeapDumper::dump_stack_traces(AbstractDumpWriter* writer) { add_oom_frame = false; } thread_dumper->init_serial_nums(&_thread_serial_num, &_frame_serial_num); - thread_dumper->dump_stack_traces(writer, _klass_map); + thread_dumper->dump_stack_traces(writer(), _klass_map); } // platform or carrier thread @@ -2575,27 +2515,11 @@ void VM_HeapDumper::dump_stack_traces(AbstractDumpWriter* writer) { thread_dumper->add_oom_frame(_oome_constructor); } thread_dumper->init_serial_nums(&_thread_serial_num, &_frame_serial_num); - thread_dumper->dump_stack_traces(writer, _klass_map); + thread_dumper->dump_stack_traces(writer(), _klass_map); } } } -void VM_HeapDumper::dump_vthread(oop vt, AbstractDumpWriter* segment_writer) { - // unmounted vthread has no JavaThread - ThreadDumper thread_dumper(ThreadDumper::ThreadType::UnmountedVirtual, nullptr, vt); - thread_dumper.init_serial_nums(&_thread_serial_num, &_frame_serial_num); - - // write HPROF_TRACE/HPROF_FRAME records to global writer - _dumper_controller->lock_global_writer(); - thread_dumper.dump_stack_traces(writer(), _klass_map); - _dumper_controller->unlock_global_writer(); - - // write HPROF_GC_ROOT_THREAD_OBJ/HPROF_GC_ROOT_JAVA_FRAME/HPROF_GC_ROOT_JNI_LOCAL subrecord - // to segment writer - thread_dumper.dump_thread_obj(segment_writer); - thread_dumper.dump_stack_refs(segment_writer); -} - // dump the heap to given path. int HeapDumper::dump(const char* path, outputStream* out, int compression, bool overwrite, uint num_dump_threads) { assert(path != nullptr && strlen(path) > 0, "path missing"); @@ -2637,27 +2561,28 @@ int HeapDumper::dump(const char* path, outputStream* out, int compression, bool // record any error that the writer may have encountered set_error(writer.error()); - // Heap dump process is done in two phases + // For serial dump, once VM_HeapDumper completes, the whole heap dump process + // is done, no further phases needed. For parallel dump, the whole heap dump + // process is done in two phases // // Phase 1: Concurrent threads directly write heap data to multiple heap files. // This is done by VM_HeapDumper, which is performed within safepoint. // // Phase 2: Merge multiple heap files into one complete heap dump file. // This is done by DumpMerger, which is performed outside safepoint - - DumpMerger merger(path, &writer, dumper.dump_seq()); - Thread* current_thread = Thread::current(); - if (current_thread->is_AttachListener_thread()) { - // perform heapdump file merge operation in the current thread prevents us - // from occupying the VM Thread, which in turn affects the occurrence of - // GC and other VM operations. - merger.do_merge(); - } else { - // otherwise, performs it by VM thread - VM_HeapDumpMerge op(&merger); - VMThread::execute(&op); - } - if (writer.error() != nullptr) { + if (dumper.is_parallel_dump()) { + DumpMerger merger(path, &writer, dumper.dump_seq()); + Thread* current_thread = Thread::current(); + if (current_thread->is_AttachListener_thread()) { + // perform heapdump file merge operation in the current thread prevents us + // from occupying the VM Thread, which in turn affects the occurrence of + // GC and other VM operations. + merger.do_merge(); + } else { + // otherwise, performs it by VM thread + VM_HeapDumpMerge op(&merger); + VMThread::execute(&op); + } set_error(writer.error()); } diff --git a/test/hotspot/jtreg/serviceability/dcmd/gc/HeapDumpParallelTest.java b/test/hotspot/jtreg/serviceability/dcmd/gc/HeapDumpParallelTest.java index 0010c5bdd78..2ea09dd1597 100644 --- a/test/hotspot/jtreg/serviceability/dcmd/gc/HeapDumpParallelTest.java +++ b/test/hotspot/jtreg/serviceability/dcmd/gc/HeapDumpParallelTest.java @@ -66,6 +66,7 @@ private static void checkAndVerify(OutputAnalyzer dcmdOut, LingeredApp app, File appOut.shouldContain("Merge heap files complete"); } else { appOut.shouldNotContain("Dump heap objects in parallel"); + appOut.shouldNotContain("Merge heap files complete"); } HprofParser.parseAndVerify(heapDumpFile); diff --git a/test/hotspot/jtreg/serviceability/jvmti/vthread/HeapDump/VThreadInHeapDump.java b/test/hotspot/jtreg/serviceability/jvmti/vthread/HeapDump/VThreadInHeapDump.java index 7fe5abb800f..89a9a0514bf 100644 --- a/test/hotspot/jtreg/serviceability/jvmti/vthread/HeapDump/VThreadInHeapDump.java +++ b/test/hotspot/jtreg/serviceability/jvmti/vthread/HeapDump/VThreadInHeapDump.java @@ -183,7 +183,6 @@ private static void createDump(File dumpFile, String[] extraOptions) throws Exce List extraVMArgs = new ArrayList<>(); extraVMArgs.add("-Djdk.virtualThreadScheduler.parallelism=1"); - extraVMArgs.add("-Xlog:heapdump"); extraVMArgs.addAll(Arrays.asList(extraOptions)); LingeredApp.startApp(theApp, extraVMArgs.toArray(new String[0])); @@ -253,7 +252,8 @@ private static void verifyDump(File dumpFile) throws Exception { // Verify objects from thread stacks are dumped. test(snapshot, VThreadInHeapDumpTarg.VThreadMountedReferenced.class); test(snapshot, VThreadInHeapDumpTarg.PThreadReferenced.class); - test(snapshot, VThreadInHeapDumpTarg.VThreadUnmountedReferenced.class); + // Dumping of unmounted vthreads is not implemented yet + //test(snapshot, VThreadInHeapDumpTarg.VThreadUnmountedReferenced.class); } } From af5c49226c3416a9c3bdde06cac2076acf9de5c3 Mon Sep 17 00:00:00 2001 From: Alan Bateman Date: Fri, 8 Dec 2023 07:10:20 +0000 Subject: [PATCH 08/57] 8320532: Remove Thread/ThreadGroup suspend/resume Reviewed-by: dholmes, jpai, sspitsyn, smarks --- .../share/classes/java/lang/Thread.java | 40 +---- .../share/classes/java/lang/ThreadGroup.java | 24 +-- .../doc-files/threadPrimitiveDeprecation.html | 170 +----------------- .../java/lang/management/ThreadInfo.java | 7 +- ...thodsThrowUOE.java => ThreadStopTest.java} | 69 +++---- .../java/lang/Thread/virtual/ThreadAPI.java | 58 ------ .../jdk/java/lang/ThreadGroup/BasicTests.java | 12 -- .../SocketChannel/SendUrgentData.java | 6 +- 8 files changed, 36 insertions(+), 350 deletions(-) rename test/jdk/java/lang/Thread/{DegradedMethodsThrowUOE.java => ThreadStopTest.java} (57%) diff --git a/src/java.base/share/classes/java/lang/Thread.java b/src/java.base/share/classes/java/lang/Thread.java index ec578efd8ab..4236368d287 100644 --- a/src/java.base/share/classes/java/lang/Thread.java +++ b/src/java.base/share/classes/java/lang/Thread.java @@ -1647,7 +1647,7 @@ private void exit() { * interrupt the wait. * For more information, see * Why - * are Thread.stop, Thread.suspend and Thread.resume Deprecated?. + * is Thread.stop deprecated and the ability to stop a thread removed?. */ @Deprecated(since="1.2", forRemoval=true) public final void stop() { @@ -1788,44 +1788,6 @@ boolean alive() { return eetop != 0; } - /** - * Throws {@code UnsupportedOperationException}. - * - * @throws UnsupportedOperationException always - * - * @deprecated This method was originally specified to suspend a thread. - * It was inherently deadlock-prone. If the target thread held a lock on - * a monitor protecting a critical system resource when it was suspended, - * no thread could access the resource until the target thread was resumed. - * If the thread intending to resume the target thread attempted to lock - * the monitor prior to calling {@code resume}, deadlock would result. - * Such deadlocks typically manifested themselves as "frozen" processes. - * For more information, see - * Why - * are Thread.stop, Thread.suspend and Thread.resume Deprecated?. - */ - @Deprecated(since="1.2", forRemoval=true) - public final void suspend() { - throw new UnsupportedOperationException(); - } - - /** - * Throws {@code UnsupportedOperationException}. - * - * @throws UnsupportedOperationException always - * - * @deprecated This method was originally specified to resume a thread - * suspended with {@link #suspend()}. Suspending a thread was - * inherently deadlock-prone. - * For more information, see - * Why - * are Thread.stop, Thread.suspend and Thread.resume Deprecated?. - */ - @Deprecated(since="1.2", forRemoval=true) - public final void resume() { - throw new UnsupportedOperationException(); - } - /** * Changes the priority of this thread. * diff --git a/src/java.base/share/classes/java/lang/ThreadGroup.java b/src/java.base/share/classes/java/lang/ThreadGroup.java index 48467f86e1e..e9ddff7ed3d 100644 --- a/src/java.base/share/classes/java/lang/ThreadGroup.java +++ b/src/java.base/share/classes/java/lang/ThreadGroup.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1995, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1995, 2023, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -587,28 +587,6 @@ public final void interrupt() { } } - /** - * Throws {@code UnsupportedOperationException}. - * - * @deprecated This method was originally specified to suspend all threads - * in the thread group. - */ - @Deprecated(since="1.2", forRemoval=true) - public final void suspend() { - throw new UnsupportedOperationException(); - } - - /** - * Throws {@code UnsupportedOperationException}. - * - * @deprecated This method was originally specified to resume all threads - * in the thread group. - */ - @Deprecated(since="1.2", forRemoval=true) - public final void resume() { - throw new UnsupportedOperationException(); - } - /** * Does nothing. * diff --git a/src/java.base/share/classes/java/lang/doc-files/threadPrimitiveDeprecation.html b/src/java.base/share/classes/java/lang/doc-files/threadPrimitiveDeprecation.html index fd5ced83fc0..6172a859ba6 100644 --- a/src/java.base/share/classes/java/lang/doc-files/threadPrimitiveDeprecation.html +++ b/src/java.base/share/classes/java/lang/doc-files/threadPrimitiveDeprecation.html @@ -1,6 +1,6 @@