From fcccb86c65229f67e98c1fa01234be5f7037ac77 Mon Sep 17 00:00:00 2001 From: Ralf Schmelter Date: Thu, 7 May 2026 11:06:24 +0200 Subject: [PATCH 1/9] Added heap dump changes for buildpack. --- src/hotspot/share/runtime/globals.hpp | 32 ++++++++++ src/hotspot/share/services/heapDumper.cpp | 77 +++++++++++++++++++++-- 2 files changed, 105 insertions(+), 4 deletions(-) diff --git a/src/hotspot/share/runtime/globals.hpp b/src/hotspot/share/runtime/globals.hpp index 27f44314d01..6e0a5090bd7 100644 --- a/src/hotspot/share/runtime/globals.hpp +++ b/src/hotspot/share/runtime/globals.hpp @@ -716,6 +716,38 @@ const int ObjectAlignmentInBytes = 8; "compression. Otherwise the level must be between 1 and 9.") \ range(0, 9) \ \ + /* SapMachine 2026-05-06: Allow to overwrite the heap dump file. */ \ + product(bool, HeapDumpOverwrite, false, MANAGEABLE, \ + "If enabled, the heap dump of out of memory error can overwrite " \ + "an already existing heap dump file.") \ + \ + /* SapMachine 2026-05-06: Sets the parallelism of the heap dump. */ \ + product(uint, HeapDumpParallelism, 0, MANAGEABLE, \ + "Sets the parallelism of the heap dump creation. 0 means to let "\ + "the VM decide.") \ + \ + /* SAPJVM 2026-05-06: Allow to skip content of large arrays in dumps.*/ \ + product(bool, LimitPrimArrayContentInHeapDump, false, MANAGEABLE, \ + "If enabled, the content of primitive arrays in not completely " \ + "written to a heap dump for large arrays. Note that this only " \ + "really safes space, if the compression of the heap dump is " \ + "enabled too, since the skipped elements are written as " \ + "0 or false.") \ + \ + /* SAPJVM 2026-05-06: Allow to skip content of large arrays in dumps.*/ \ + product(int, StringLikeContentSizeLimitInHeapDump, 120, MANAGEABLE, \ + "The number of entries in a primitive char and bytes arrays to " \ + "not skip in a heap dump when ArrayContentSizeLimitInHeapDump " \ + "is enabled.") \ + range(0, 100000) \ + \ + /* SAPJVM 2026-05-06: Allow to skip content of large arrays in dumps.*/ \ + product(int, ArrayContentSizeLimitInHeapDump, 50, MANAGEABLE, \ + "The number of entries in a primitive array other than char and " \ + "byte arrays to not skip in a heap dump when " \ + "ArrayContentSizeLimitInHeapDump is enabled.") \ + range(0, 100000) \ + \ product(ccstr, NativeMemoryTracking, DEBUG_ONLY("summary") NOT_DEBUG("off"), \ "Native memory tracking options") \ \ diff --git a/src/hotspot/share/services/heapDumper.cpp b/src/hotspot/share/services/heapDumper.cpp index ca25e7387ad..93f67cd5847 100644 --- a/src/hotspot/share/services/heapDumper.cpp +++ b/src/hotspot/share/services/heapDumper.cpp @@ -441,6 +441,8 @@ class AbstractDumpWriter : public CHeapObj { void write_symbolID(Symbol* o); void write_classID(Klass* k); void write_id(u4 x); + // SAPJVM 2026-05-06: Writes zeros to the buffer. + void write_zero(size_t len); // Start a new sub-record. Starts a new heap dump segment if needed. void start_sub_record(u1 tag, u4 len); @@ -539,6 +541,26 @@ void AbstractDumpWriter::write_id(u4 x) { #endif } +// SAPJVM 2026-05-06: Writes zeros to the buffer. +void AbstractDumpWriter::write_zero(size_t len) { + assert(!_in_dump_segment || (_sub_record_left >= len), "sub-record too large"); + DEBUG_ONLY(_sub_record_left -= len); + + // flush buffer to make room. + while (len > buffer_size() - position()) { + assert(!_in_dump_segment || _is_huge_sub_record, + "Cannot overflow in non-huge sub-record."); + size_t to_write = buffer_size() - position(); + memset(buffer() + position(), 0, to_write); + len -= to_write; + set_position(position() + to_write); + flush(); + } + + memset(buffer() + position(), 0, len); + set_position(position() + len); +} + // We use java mirror as the class ID void AbstractDumpWriter::write_classID(Klass* k) { write_objectID(k->java_mirror()); @@ -1357,6 +1379,8 @@ void DumperSupport::dump_prim_array(AbstractDumpWriter* writer, typeArrayOop arr int length = calculate_array_max_length(writer, array, header_size); int type_size = type2aelembytes(type); + // SapMachine 2026-05-06 + int fill_with_zero = 0; u4 length_in_bytes = (u4)length * type_size; u4 size = header_size + length_in_bytes; @@ -1372,6 +1396,21 @@ void DumperSupport::dump_prim_array(AbstractDumpWriter* writer, typeArrayOop arr return; } + // SAPJVM 2026-05-06: If enabled, we don't dump the whole content of large arrays, but just the start. + if (LimitPrimArrayContentInHeapDump) { + int limit = ArrayContentSizeLimitInHeapDump; + + if (type == T_BYTE || type == T_CHAR) { + limit = StringLikeContentSizeLimitInHeapDump; + } + + if (length > limit) { + fill_with_zero = length - limit; + length = limit; + length_in_bytes = length * type_size; + } + } + // If the byte ordering is big endian then we can copy most types directly switch (type) { @@ -1439,6 +1478,11 @@ void DumperSupport::dump_prim_array(AbstractDumpWriter* writer, typeArrayOop arr default : ShouldNotReachHere(); } + // SAPJVM 2026-05-06: Fill with zeros, if we don't dump the whole content of the array. + if (fill_with_zero > 0) { + writer->write_zero(fill_with_zero * type_size); + } + writer->end_sub_record(); } @@ -2200,6 +2244,11 @@ void DumpMerger::merge_file(const char* path) { #endif void DumpMerger::do_merge() { + // SapMachine 2026-05-06: No need to merge a non-parallel heap dump. + if (_dump_seq <= 1) { + return; + } + TraceTime timer("Merge heap files complete", TRACETIME_LOG(Info, heapdump)); // Since contents in segmented heap file were already zipped, we don't need to zip @@ -2475,8 +2524,11 @@ void VM_HeapDumper::work(uint worker_id) { 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()); + // SapMachine 2026-05-06: Don't use segments if the dump is not parallel. This makes it + // possible to not use any disk space if dumping to a names pipe or a tty. + DumpWriter* parallel_writer = is_parallel_dump() ? new DumpWriter(DumpMerger::get_writer_path(writer()->get_file_path(), dumper_id), + writer()->is_overwrite(), writer()->compressor()) : nullptr; + DumpWriter& segment_writer = parallel_writer == nullptr ? *writer() : *parallel_writer; if (!segment_writer.has_error()) { if (is_vm_dumper(dumper_id)) { // dump some non-heap subrecords to heap dump segment @@ -2533,6 +2585,8 @@ void VM_HeapDumper::work(uint worker_id) { // 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. } + // SapMachine 2026-05-06 + delete parallel_writer; } void VM_HeapDumper::dump_stack_traces(AbstractDumpWriter* writer) { @@ -2584,6 +2638,17 @@ void VM_HeapDumper::dump_vthread(oop vt, AbstractDumpWriter* segment_writer) { ThreadDumper thread_dumper(ThreadDumper::ThreadType::UnmountedVirtual, nullptr, vt); thread_dumper.init_serial_nums(&_thread_serial_num, &_frame_serial_num); + // SapMachine 2026-05-06: If we don't do a parallel dump, we don't need the lock + // but have to end the current heap dump segment. + if (!is_parallel_dump()) { + segment_writer->finish_dump_segment(); + thread_dumper.dump_thread_obj(segment_writer); + thread_dumper.dump_stack_refs(segment_writer); + thread_dumper.dump_stack_traces(writer(), _klass_map); + + return; + } + // write HPROF_TRACE/HPROF_FRAME records to global writer _dumper_controller->lock_global_writer(); thread_dumper.dump_stack_traces(writer(), _klass_map); @@ -2734,7 +2799,9 @@ void HeapDumper::set_error(char const* error) { // outside of a JVM safepoint void HeapDumper::dump_heap_from_oome() { // SapMachine 2024-05-10: HeapDumpPath for jcmd - HeapDumper::dump_heap(false, true); + // SapMachine 2026-05-06: Handle HeapDumpOverwrite and HeapDumpParallelism. + HeapDumper::dump_heap(false, true, tty, .1, HeapDumpOverwrite, HeapDumpParallelism == 0 ? + HeapDumper::default_num_of_dump_threads(): HeapDumpParallelism); } // Called by error reporting by a single Java thread outside of a JVM safepoint, @@ -2744,7 +2811,9 @@ void HeapDumper::dump_heap_from_oome() { // inteference when updating the static variables base_path and dump_file_seq below. void HeapDumper::dump_heap() { // SapMachine 2024-05-10: HeapDumpPath for jcmd - HeapDumper::dump_heap(false, false); + // SapMachine 2026-05-06: Handle HeapDumpOverwrite and HeapDumpParallelism. + HeapDumper::dump_heap(false, false, tty, .1, HeapDumpOverwrite, HeapDumpParallelism == 0 ? + HeapDumper::default_num_of_dump_threads() : HeapDumpParallelism); } // SapMachine 2024-05-10: HeapDumpPath for jcmd From bbee3b9e53bf2b13331d2e47917e738e071d4e3a Mon Sep 17 00:00:00 2001 From: Ralf Schmelter Date: Thu, 7 May 2026 18:02:32 +0200 Subject: [PATCH 2/9] Added test for partial array dump. --- .../HeapDump/PartialArrayContentTest.java | 181 ++++++++++++++++++ 1 file changed, 181 insertions(+) create mode 100644 test/hotspot/jtreg/serviceability/HeapDump/PartialArrayContentTest.java diff --git a/test/hotspot/jtreg/serviceability/HeapDump/PartialArrayContentTest.java b/test/hotspot/jtreg/serviceability/HeapDump/PartialArrayContentTest.java new file mode 100644 index 00000000000..9a694dac24c --- /dev/null +++ b/test/hotspot/jtreg/serviceability/HeapDump/PartialArrayContentTest.java @@ -0,0 +1,181 @@ +/* + * Copyright (c) 2026 SAP SE. 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. + */ + +import java.io.File; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.concurrent.TimeUnit; + +import jdk.test.lib.Asserts; +import jdk.test.lib.JDKToolLauncher; +import jdk.test.lib.apps.LingeredApp; +import jdk.test.lib.process.ProcessTools; +import jdk.test.lib.hprof.model.*; +import jdk.test.lib.hprof.parser.Reader; + +/* + * @test + * @summary Checks if -XX:+LimitPrimArrayContentInHeapDump works. + * @library /test/lib + * @run driver PartialArrayContentTest + */ +class ArrayAllocApp extends LingeredApp { + public static int arraySize = 54321; + + public static void main(String[] args) { + boolean[] za = new boolean[arraySize]; + byte[] ba = new byte[arraySize]; + short[] sa = new short[arraySize]; + char[] ca = new char[arraySize]; + int[] ia = new int[arraySize]; + long[] ja = new long[arraySize]; + float[] fa = new float[arraySize]; + double[] da = new double[arraySize]; + + for (int i = 0; i < arraySize; ++i) { + za[i] = true; + ba[i] = (byte) 1; + sa[i] = (short) 1; + ca[i] = '1'; + ia[i] = 1; + ja[i] = 1; + fa[i] = 1.0f; + da[i] = 1.0; + } + LingeredApp.main(args); + } +} + +public class PartialArrayContentTest { + private static int charLikeLimit = 120; + private static int nonCharLikeLimit = 80; + + public static void main(String[] args) throws Exception { + File dumpFile = new File("partialarrays.hprof"); + createDump(dumpFile); + verifyDump(dumpFile); + } + + private static void createDump(File dumpFile) throws Exception { + LingeredApp theApp = null; + try { + theApp = new ArrayAllocApp(); + LingeredApp.startApp(theApp, "-XX:+LimitPrimArrayContentInHeapDump", + "-XX:StringLikeContentSizeLimitInHeapDump=" + charLikeLimit, + "-XX:ArrayContentSizeLimitInHeapDump=" + nonCharLikeLimit); + + //jcmd GC.heap_dump + JDKToolLauncher launcher = JDKToolLauncher + .createUsingTestJDK("jcmd") + .addToolArg(Long.toString(theApp.getPid())) + .addToolArg("GC.heap_dump") + .addToolArg(dumpFile.getAbsolutePath()); + Process p = ProcessTools.startProcess("jcmd", new ProcessBuilder(launcher.getCommand())); + + while (!p.waitFor(5, TimeUnit.SECONDS)) { + if (!theApp.getProcess().isAlive()) { + p.destroyForcibly(); + throw new Exception("Target VM died"); + } + } + + Asserts.assertEquals(p.exitValue(), 0); + } finally { + LingeredApp.stopApp(theApp); + } + } + + private static void verifyDump(File dumpFile) throws Exception { + Asserts.assertTrue(dumpFile.exists(), "Heap dump file not found."); + + try (Snapshot snapshot = Reader.readFile(dumpFile.getPath(), true, 0)) { + snapshot.resolve(true); + Enumeration things = snapshot.getThings(); + HashSet expectedTypes = new HashSet<>(); + expectedTypes.add('Z'); + expectedTypes.add('B'); + expectedTypes.add('S'); + expectedTypes.add('C'); + expectedTypes.add('I'); + expectedTypes.add('J'); + expectedTypes.add('F'); + expectedTypes.add('D'); + + while (things.hasMoreElements()) { + JavaHeapObject obj = things.nextElement(); + + if (obj instanceof JavaValueArray) { + JavaValueArray array = (JavaValueArray) obj; + + if (array.getLength() != ArrayAllocApp.arraySize) { + continue; + } + + char type = (char) array.getElementType(); + Asserts.assertTrue(expectedTypes.remove(type)); + int limit = ((type == 'B') || (type == 'C')) ? charLikeLimit : nonCharLikeLimit; + JavaThing[] values = array.getElements(); + + String exp1 = ""; + String exp2 = ""; + + switch (type) { + case 'Z': + exp1 = "true"; + exp2 = "false"; + break; + case 'B': + exp1 = "0x1"; + exp2 = "0x0"; + break; + case 'S': + case 'I': + case 'J': + exp1 = "1"; + exp2 = "0"; + break; + case 'C': + exp1 = "1"; + exp2 = "" + (char) 0; + break; + case 'F': + case 'D': + exp1 = "1.0"; + exp2 = "0.0"; + break; + } + + for (int i = 0; i < limit; ++i) { + Asserts.assertEquals(exp1, values[i].toString()); + } + + for (int i = limit; i < ArrayAllocApp.arraySize; ++i) { + Asserts.assertEquals(exp2, values[i].toString()); + } + } + } + + Asserts.assertTrue(expectedTypes.isEmpty()); + } + } +} \ No newline at end of file From 0c82382055692750976ec140eff65d50b34ff5ba Mon Sep 17 00:00:00 2001 From: Ralf Schmelter Date: Thu, 7 May 2026 20:41:33 +0200 Subject: [PATCH 3/9] Fixed wrong compression value. --- src/hotspot/share/services/heapDumper.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/hotspot/share/services/heapDumper.cpp b/src/hotspot/share/services/heapDumper.cpp index 93f67cd5847..56f796701ed 100644 --- a/src/hotspot/share/services/heapDumper.cpp +++ b/src/hotspot/share/services/heapDumper.cpp @@ -2800,7 +2800,7 @@ void HeapDumper::set_error(char const* error) { void HeapDumper::dump_heap_from_oome() { // SapMachine 2024-05-10: HeapDumpPath for jcmd // SapMachine 2026-05-06: Handle HeapDumpOverwrite and HeapDumpParallelism. - HeapDumper::dump_heap(false, true, tty, .1, HeapDumpOverwrite, HeapDumpParallelism == 0 ? + HeapDumper::dump_heap(false, true, tty, -1, HeapDumpOverwrite, HeapDumpParallelism == 0 ? HeapDumper::default_num_of_dump_threads(): HeapDumpParallelism); } @@ -2812,7 +2812,7 @@ void HeapDumper::dump_heap_from_oome() { void HeapDumper::dump_heap() { // SapMachine 2024-05-10: HeapDumpPath for jcmd // SapMachine 2026-05-06: Handle HeapDumpOverwrite and HeapDumpParallelism. - HeapDumper::dump_heap(false, false, tty, .1, HeapDumpOverwrite, HeapDumpParallelism == 0 ? + HeapDumper::dump_heap(false, false, tty, -1, HeapDumpOverwrite, HeapDumpParallelism == 0 ? HeapDumper::default_num_of_dump_threads() : HeapDumpParallelism); } From 67e311403a18577e9060344503c8e575fdbfac7a Mon Sep 17 00:00:00 2001 From: Ralf Schmelter Date: Mon, 11 May 2026 06:08:57 +0200 Subject: [PATCH 4/9] Fixed wrong dump order for vthreads. --- src/hotspot/share/services/heapDumper.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hotspot/share/services/heapDumper.cpp b/src/hotspot/share/services/heapDumper.cpp index 56f796701ed..179a936d237 100644 --- a/src/hotspot/share/services/heapDumper.cpp +++ b/src/hotspot/share/services/heapDumper.cpp @@ -2642,9 +2642,9 @@ void VM_HeapDumper::dump_vthread(oop vt, AbstractDumpWriter* segment_writer) { // but have to end the current heap dump segment. if (!is_parallel_dump()) { segment_writer->finish_dump_segment(); + thread_dumper.dump_stack_traces(writer(), _klass_map); thread_dumper.dump_thread_obj(segment_writer); thread_dumper.dump_stack_refs(segment_writer); - thread_dumper.dump_stack_traces(writer(), _klass_map); return; } From 81d0ba4b01402a5e89b45d6afaeca60371ccc682 Mon Sep 17 00:00:00 2001 From: Ralf Schmelter Date: Mon, 18 May 2026 08:15:47 +0200 Subject: [PATCH 5/9] Add missing tests. --- .../HeapDump/PartialArrayContentTest.java | 89 ++++++++++++++----- 1 file changed, 68 insertions(+), 21 deletions(-) diff --git a/test/hotspot/jtreg/serviceability/HeapDump/PartialArrayContentTest.java b/test/hotspot/jtreg/serviceability/HeapDump/PartialArrayContentTest.java index 9a694dac24c..0f328389731 100644 --- a/test/hotspot/jtreg/serviceability/HeapDump/PartialArrayContentTest.java +++ b/test/hotspot/jtreg/serviceability/HeapDump/PartialArrayContentTest.java @@ -22,6 +22,8 @@ */ import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; import java.util.Enumeration; import java.util.HashSet; import java.util.concurrent.TimeUnit; @@ -42,16 +44,16 @@ class ArrayAllocApp extends LingeredApp { public static int arraySize = 54321; - public static void main(String[] args) { - boolean[] za = new boolean[arraySize]; - byte[] ba = new byte[arraySize]; - short[] sa = new short[arraySize]; - char[] ca = new char[arraySize]; - int[] ia = new int[arraySize]; - long[] ja = new long[arraySize]; - float[] fa = new float[arraySize]; - double[] da = new double[arraySize]; + public static boolean[] za = new boolean[arraySize]; + public static byte[] ba = new byte[arraySize]; + public static short[] sa = new short[arraySize]; + public static char[] ca = new char[arraySize]; + public static int[] ia = new int[arraySize]; + public static long[] ja = new long[arraySize]; + public static float[] fa = new float[arraySize]; + public static double[] da = new double[arraySize]; + public static void allocArrays() { for (int i = 0; i < arraySize; ++i) { za[i] = true; ba[i] = (byte) 1; @@ -62,27 +64,66 @@ public static void main(String[] args) { fa[i] = 1.0f; da[i] = 1.0; } + } + public static void main(String[] args) { + allocArrays(); LingeredApp.main(args); } } +class ArrayAllocOOMApp extends ArrayAllocApp { + public static int arraySize = 54321; + + public static void main(String[] args) { + allocArrays(); + byte[] b = new byte[2000000000]; + } +} + public class PartialArrayContentTest { private static int charLikeLimit = 120; private static int nonCharLikeLimit = 80; public static void main(String[] args) throws Exception { - File dumpFile = new File("partialarrays.hprof"); - createDump(dumpFile); - verifyDump(dumpFile); + checkPartialContentWithJcmd(); + checkPartialContentWithOOM(); } - private static void createDump(File dumpFile) throws Exception { - LingeredApp theApp = null; - try { - theApp = new ArrayAllocApp(); - LingeredApp.startApp(theApp, "-XX:+LimitPrimArrayContentInHeapDump", + public static void checkPartialContentWithJcmd() throws Exception { + File dumpFile = new File("partialarrays_with_jcmd.hprof"); + createDump(dumpFile, true, + "-Xmx500M", + "-XX:+LimitPrimArrayContentInHeapDump", "-XX:StringLikeContentSizeLimitInHeapDump=" + charLikeLimit, "-XX:ArrayContentSizeLimitInHeapDump=" + nonCharLikeLimit); + verifyDump(dumpFile, true); + } + + public static void checkPartialContentWithOOM() throws Exception { + // Check -XX:+HeapDumpOverwrite and -XX:HeapDumpParallelism too. + File dumpFile = new File("partialarrays_with_oom.hprof"); + FileOutputStream fos = new FileOutputStream(dumpFile); + fos.close(); + File firstSegment = new File(dumpFile + ".p0"); + fos = new FileOutputStream(firstSegment); + fos.close(); + createDump(dumpFile, false, + "-Xmx500M", + "-XX:+HeapDumpOnOutOfMemoryError", + "-XX:HeapDumpPath=" + dumpFile, + "-XX:+HeapDumpOverwrite", + "-XX:HeapDumpParallelism=1"); + verifyDump(dumpFile, false); + Asserts.assertTrue(firstSegment.exists(), "Segment file should not have been created (parallelism=1)."); + Asserts.assertEquals(firstSegment.length(), 0L, "Segment should not be modified."); + } + + private static void createDump(File dumpFile, boolean useJcmd, String... vmArgs) throws Exception { + LingeredApp theApp = null; + try { + theApp = useJcmd ? new ArrayAllocApp() : new ArrayAllocOOMApp(); + LingeredApp.startApp(theApp, vmArgs); + Asserts.assertTrue(useJcmd, "startApp() should throw when OOM."); //jcmd GC.heap_dump JDKToolLauncher launcher = JDKToolLauncher @@ -100,12 +141,16 @@ private static void createDump(File dumpFile) throws Exception { } Asserts.assertEquals(p.exitValue(), 0); + } catch (IOException e) { + Asserts.assertFalse(useJcmd, "We expect to fail when throwing OOM."); } finally { - LingeredApp.stopApp(theApp); + if (useJcmd) { + LingeredApp.stopApp(theApp); + } } } - private static void verifyDump(File dumpFile) throws Exception { + private static void verifyDump(File dumpFile, boolean isPartial) throws Exception { Asserts.assertTrue(dumpFile.exists(), "Heap dump file not found."); try (Snapshot snapshot = Reader.readFile(dumpFile.getPath(), true, 0)) { @@ -165,11 +210,13 @@ private static void verifyDump(File dumpFile) throws Exception { break; } - for (int i = 0; i < limit; ++i) { + int fullPart = isPartial ? limit : values.length; + + for (int i = 0; i < fullPart; ++i) { Asserts.assertEquals(exp1, values[i].toString()); } - for (int i = limit; i < ArrayAllocApp.arraySize; ++i) { + for (int i = fullPart; i < ArrayAllocApp.arraySize; ++i) { Asserts.assertEquals(exp2, values[i].toString()); } } From f2305a2bc41da3fa75892997293750ca89c4c25b Mon Sep 17 00:00:00 2001 From: Ralf Schmelter Date: Mon, 18 May 2026 15:11:32 +0200 Subject: [PATCH 6/9] Fixed some descriptions. --- src/hotspot/share/runtime/globals.hpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/hotspot/share/runtime/globals.hpp b/src/hotspot/share/runtime/globals.hpp index 6e0a5090bd7..6a3afcd2b40 100644 --- a/src/hotspot/share/runtime/globals.hpp +++ b/src/hotspot/share/runtime/globals.hpp @@ -718,8 +718,8 @@ const int ObjectAlignmentInBytes = 8; \ /* SapMachine 2026-05-06: Allow to overwrite the heap dump file. */ \ product(bool, HeapDumpOverwrite, false, MANAGEABLE, \ - "If enabled, the heap dump of out of memory error can overwrite " \ - "an already existing heap dump file.") \ + "If enabled, the heap dump on out of memory error can " \ + "overwrite an already existing file.") \ \ /* SapMachine 2026-05-06: Sets the parallelism of the heap dump. */ \ product(uint, HeapDumpParallelism, 0, MANAGEABLE, \ @@ -736,8 +736,8 @@ const int ObjectAlignmentInBytes = 8; \ /* SAPJVM 2026-05-06: Allow to skip content of large arrays in dumps.*/ \ product(int, StringLikeContentSizeLimitInHeapDump, 120, MANAGEABLE, \ - "The number of entries in a primitive char and bytes arrays to " \ - "not skip in a heap dump when ArrayContentSizeLimitInHeapDump " \ + "The number of entries in primitive char and bytes arrays to " \ + "not skip in a heap dump when LimitPrimArrayContentInHeapDump " \ "is enabled.") \ range(0, 100000) \ \ @@ -745,7 +745,7 @@ const int ObjectAlignmentInBytes = 8; product(int, ArrayContentSizeLimitInHeapDump, 50, MANAGEABLE, \ "The number of entries in a primitive array other than char and " \ "byte arrays to not skip in a heap dump when " \ - "ArrayContentSizeLimitInHeapDump is enabled.") \ + "LimitPrimArrayContentInHeapDump is enabled.") \ range(0, 100000) \ \ product(ccstr, NativeMemoryTracking, DEBUG_ONLY("summary") NOT_DEBUG("off"), \ From 4909815fbc6616805cfd627ecf1a723273a110b8 Mon Sep 17 00:00:00 2001 From: Ralf Schmelter Date: Tue, 19 May 2026 11:01:58 +0200 Subject: [PATCH 7/9] Fixed integer overflow --- src/hotspot/share/services/heapDumper.cpp | 4 ++-- .../HeapDump/PartialArrayContentTest.java | 24 ++++++++++++++++--- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/src/hotspot/share/services/heapDumper.cpp b/src/hotspot/share/services/heapDumper.cpp index 179a936d237..7fc634b17d3 100644 --- a/src/hotspot/share/services/heapDumper.cpp +++ b/src/hotspot/share/services/heapDumper.cpp @@ -1407,7 +1407,7 @@ void DumperSupport::dump_prim_array(AbstractDumpWriter* writer, typeArrayOop arr if (length > limit) { fill_with_zero = length - limit; length = limit; - length_in_bytes = length * type_size; + length_in_bytes = (u4) length * type_size; } } @@ -1480,7 +1480,7 @@ void DumperSupport::dump_prim_array(AbstractDumpWriter* writer, typeArrayOop arr // SAPJVM 2026-05-06: Fill with zeros, if we don't dump the whole content of the array. if (fill_with_zero > 0) { - writer->write_zero(fill_with_zero * type_size); + writer->write_zero((u4) fill_with_zero * type_size); } writer->end_sub_record(); diff --git a/test/hotspot/jtreg/serviceability/HeapDump/PartialArrayContentTest.java b/test/hotspot/jtreg/serviceability/HeapDump/PartialArrayContentTest.java index 0f328389731..5b97a2f1ced 100644 --- a/test/hotspot/jtreg/serviceability/HeapDump/PartialArrayContentTest.java +++ b/test/hotspot/jtreg/serviceability/HeapDump/PartialArrayContentTest.java @@ -73,16 +73,20 @@ public static void main(String[] args) { class ArrayAllocOOMApp extends ArrayAllocApp { public static int arraySize = 54321; + // The size of the short array to be slightly larger than 2 GB. + public static int largestArraySize = Integer.MAX_VALUE / 2 + 100; + public static short[] largeArray; public static void main(String[] args) { allocArrays(); - byte[] b = new byte[2000000000]; + largeArray = new short[largestArraySize]; + byte[] b = new byte[largestArraySize]; } } public class PartialArrayContentTest { private static int charLikeLimit = 120; - private static int nonCharLikeLimit = 80; + private static int nonCharLikeLimit = 50; public static void main(String[] args) throws Exception { checkPartialContentWithJcmd(); @@ -116,6 +120,15 @@ public static void checkPartialContentWithOOM() throws Exception { verifyDump(dumpFile, false); Asserts.assertTrue(firstSegment.exists(), "Segment file should not have been created (parallelism=1)."); Asserts.assertEquals(firstSegment.length(), 0L, "Segment should not be modified."); + // Create a dump with an array > 2GB to check for integer overflows in the partial array code. + createDump(dumpFile, false, + "-Xmx2500M", // Ensures the 2 billion entry short array is in the heap dump. + "-XX:+HeapDumpOnOutOfMemoryError", + "-XX:HeapDumpPath=" + dumpFile, + "-XX:+HeapDumpOverwrite", + "-XX:+LimitPrimArrayContentInHeapDump"); + int largestArraySize = verifyDump(dumpFile, true); + Asserts.assertEquals(largestArraySize, ArrayAllocOOMApp.largestArraySize); } private static void createDump(File dumpFile, boolean useJcmd, String... vmArgs) throws Exception { @@ -150,8 +163,9 @@ private static void createDump(File dumpFile, boolean useJcmd, String... vmArgs) } } - private static void verifyDump(File dumpFile, boolean isPartial) throws Exception { + private static int verifyDump(File dumpFile, boolean isPartial) throws Exception { Asserts.assertTrue(dumpFile.exists(), "Heap dump file not found."); + int largestArraySize = 0; try (Snapshot snapshot = Reader.readFile(dumpFile.getPath(), true, 0)) { snapshot.resolve(true); @@ -172,6 +186,8 @@ private static void verifyDump(File dumpFile, boolean isPartial) throws Exceptio if (obj instanceof JavaValueArray) { JavaValueArray array = (JavaValueArray) obj; + largestArraySize = Math.max(largestArraySize, array.getLength()); + if (array.getLength() != ArrayAllocApp.arraySize) { continue; } @@ -224,5 +240,7 @@ private static void verifyDump(File dumpFile, boolean isPartial) throws Exceptio Asserts.assertTrue(expectedTypes.isEmpty()); } + + return largestArraySize; } } \ No newline at end of file From c5601131f107abb176f9d8647a704faae22f1602 Mon Sep 17 00:00:00 2001 From: Ralf Schmelter Date: Wed, 20 May 2026 15:25:56 +0200 Subject: [PATCH 8/9] Fixed spellings. --- src/hotspot/share/runtime/globals.hpp | 6 +++--- .../serviceability/HeapDump/PartialArrayContentTest.java | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/hotspot/share/runtime/globals.hpp b/src/hotspot/share/runtime/globals.hpp index 6a3afcd2b40..104d162895e 100644 --- a/src/hotspot/share/runtime/globals.hpp +++ b/src/hotspot/share/runtime/globals.hpp @@ -728,15 +728,15 @@ const int ObjectAlignmentInBytes = 8; \ /* SAPJVM 2026-05-06: Allow to skip content of large arrays in dumps.*/ \ product(bool, LimitPrimArrayContentInHeapDump, false, MANAGEABLE, \ - "If enabled, the content of primitive arrays in not completely " \ + "If enabled, the content of primitive arrays is not completely " \ "written to a heap dump for large arrays. Note that this only " \ - "really safes space, if the compression of the heap dump is " \ + "really saves space, if the compression of the heap dump is " \ "enabled too, since the skipped elements are written as " \ "0 or false.") \ \ /* SAPJVM 2026-05-06: Allow to skip content of large arrays in dumps.*/ \ product(int, StringLikeContentSizeLimitInHeapDump, 120, MANAGEABLE, \ - "The number of entries in primitive char and bytes arrays to " \ + "The number of entries in primitive char and byte arrays to " \ "not skip in a heap dump when LimitPrimArrayContentInHeapDump " \ "is enabled.") \ range(0, 100000) \ diff --git a/test/hotspot/jtreg/serviceability/HeapDump/PartialArrayContentTest.java b/test/hotspot/jtreg/serviceability/HeapDump/PartialArrayContentTest.java index 5b97a2f1ced..cd91355cf6d 100644 --- a/test/hotspot/jtreg/serviceability/HeapDump/PartialArrayContentTest.java +++ b/test/hotspot/jtreg/serviceability/HeapDump/PartialArrayContentTest.java @@ -243,4 +243,4 @@ private static int verifyDump(File dumpFile, boolean isPartial) throws Exception return largestArraySize; } -} \ No newline at end of file +} From 1822fef27a2be60b0050d14e4622dd8793af97ea Mon Sep 17 00:00:00 2001 From: Ralf Schmelter Date: Fri, 22 May 2026 18:45:11 +0200 Subject: [PATCH 9/9] Removed SAPJVM comments and change flag name. --- src/hotspot/share/runtime/globals.hpp | 14 +++++++------- src/hotspot/share/services/heapDumper.cpp | 10 +++++----- .../HeapDump/PartialArrayContentTest.java | 4 ++-- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/hotspot/share/runtime/globals.hpp b/src/hotspot/share/runtime/globals.hpp index 104d162895e..d94fa0f1778 100644 --- a/src/hotspot/share/runtime/globals.hpp +++ b/src/hotspot/share/runtime/globals.hpp @@ -726,26 +726,26 @@ const int ObjectAlignmentInBytes = 8; "Sets the parallelism of the heap dump creation. 0 means to let "\ "the VM decide.") \ \ - /* SAPJVM 2026-05-06: Allow to skip content of large arrays in dumps.*/ \ - product(bool, LimitPrimArrayContentInHeapDump, false, MANAGEABLE, \ + /* SapMachine 2026-05-06: Allow to skip content of arrays in dumps.*/ \ + product(bool, LimitPrimitiveArrayContentInHeapDump, false, MANAGEABLE, \ "If enabled, the content of primitive arrays is not completely " \ "written to a heap dump for large arrays. Note that this only " \ "really saves space, if the compression of the heap dump is " \ "enabled too, since the skipped elements are written as " \ "0 or false.") \ \ - /* SAPJVM 2026-05-06: Allow to skip content of large arrays in dumps.*/ \ + /* SapMachine 2026-05-06: Allow to skip content of arrays in dumps.*/ \ product(int, StringLikeContentSizeLimitInHeapDump, 120, MANAGEABLE, \ "The number of entries in primitive char and byte arrays to " \ - "not skip in a heap dump when LimitPrimArrayContentInHeapDump " \ - "is enabled.") \ + "not skip in a heap dump when " \ + "LimitPrimitiveArrayContentInHeapDump is enabled.") \ range(0, 100000) \ \ - /* SAPJVM 2026-05-06: Allow to skip content of large arrays in dumps.*/ \ + /* SapMachine 2026-05-06: Allow to skip contents of arrays in dumps.*/ \ product(int, ArrayContentSizeLimitInHeapDump, 50, MANAGEABLE, \ "The number of entries in a primitive array other than char and " \ "byte arrays to not skip in a heap dump when " \ - "LimitPrimArrayContentInHeapDump is enabled.") \ + "LimitPrimitiveArrayContentInHeapDump is enabled.") \ range(0, 100000) \ \ product(ccstr, NativeMemoryTracking, DEBUG_ONLY("summary") NOT_DEBUG("off"), \ diff --git a/src/hotspot/share/services/heapDumper.cpp b/src/hotspot/share/services/heapDumper.cpp index 7fc634b17d3..d6836648655 100644 --- a/src/hotspot/share/services/heapDumper.cpp +++ b/src/hotspot/share/services/heapDumper.cpp @@ -441,7 +441,7 @@ class AbstractDumpWriter : public CHeapObj { void write_symbolID(Symbol* o); void write_classID(Klass* k); void write_id(u4 x); - // SAPJVM 2026-05-06: Writes zeros to the buffer. + // SapMachine 2026-05-06: Writes zeros to the buffer. void write_zero(size_t len); // Start a new sub-record. Starts a new heap dump segment if needed. @@ -541,7 +541,7 @@ void AbstractDumpWriter::write_id(u4 x) { #endif } -// SAPJVM 2026-05-06: Writes zeros to the buffer. +// SapMachine 2026-05-06: Writes zeros to the buffer. void AbstractDumpWriter::write_zero(size_t len) { assert(!_in_dump_segment || (_sub_record_left >= len), "sub-record too large"); DEBUG_ONLY(_sub_record_left -= len); @@ -1396,8 +1396,8 @@ void DumperSupport::dump_prim_array(AbstractDumpWriter* writer, typeArrayOop arr return; } - // SAPJVM 2026-05-06: If enabled, we don't dump the whole content of large arrays, but just the start. - if (LimitPrimArrayContentInHeapDump) { + // SapMachine 2026-05-06: If enabled, we don't dump the whole content of large arrays, but just the start. + if (LimitPrimitiveArrayContentInHeapDump) { int limit = ArrayContentSizeLimitInHeapDump; if (type == T_BYTE || type == T_CHAR) { @@ -1478,7 +1478,7 @@ void DumperSupport::dump_prim_array(AbstractDumpWriter* writer, typeArrayOop arr default : ShouldNotReachHere(); } - // SAPJVM 2026-05-06: Fill with zeros, if we don't dump the whole content of the array. + // SapMachine 2026-05-06: Fill with zeros, if we don't dump the whole content of the array. if (fill_with_zero > 0) { writer->write_zero((u4) fill_with_zero * type_size); } diff --git a/test/hotspot/jtreg/serviceability/HeapDump/PartialArrayContentTest.java b/test/hotspot/jtreg/serviceability/HeapDump/PartialArrayContentTest.java index cd91355cf6d..ab296e1a1cf 100644 --- a/test/hotspot/jtreg/serviceability/HeapDump/PartialArrayContentTest.java +++ b/test/hotspot/jtreg/serviceability/HeapDump/PartialArrayContentTest.java @@ -97,7 +97,7 @@ public static void checkPartialContentWithJcmd() throws Exception { File dumpFile = new File("partialarrays_with_jcmd.hprof"); createDump(dumpFile, true, "-Xmx500M", - "-XX:+LimitPrimArrayContentInHeapDump", + "-XX:+LimitPrimitiveArrayContentInHeapDump", "-XX:StringLikeContentSizeLimitInHeapDump=" + charLikeLimit, "-XX:ArrayContentSizeLimitInHeapDump=" + nonCharLikeLimit); verifyDump(dumpFile, true); @@ -126,7 +126,7 @@ public static void checkPartialContentWithOOM() throws Exception { "-XX:+HeapDumpOnOutOfMemoryError", "-XX:HeapDumpPath=" + dumpFile, "-XX:+HeapDumpOverwrite", - "-XX:+LimitPrimArrayContentInHeapDump"); + "-XX:+LimitPrimitiveArrayContentInHeapDump"); int largestArraySize = verifyDump(dumpFile, true); Asserts.assertEquals(largestArraySize, ArrayAllocOOMApp.largestArraySize); }