changeset 12628:2c44cff993b8

8171084: heapdump/JMapHeapCore fails with java.lang.RuntimeException: Heap segment size overflow Summary: Create a new heapdump segment and truncate huge arrays if required, to avoid overflow of the 32 bit value representing the size. Reviewed-by: dholmes, dsamersoff Contributed-by: jini.george@oracle.com
author jgeorge
date Mon, 30 Jan 2017 13:48:14 +0530
parents 8c1d94c35134
children ab3d4589f4d8
files src/jdk.hotspot.agent/linux/native/libsaproc/libproc_impl.c src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/utilities/AbstractHeapGraphWriter.java src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/utilities/HeapHprofBinWriter.java test/serviceability/sa/LingeredAppWithLargeArray.java test/serviceability/sa/TestHeapDumpForLargeArray.java test/serviceability/sa/jmap-hprof/JMapHProfLargeHeapTest.java
diffstat 6 files changed, 265 insertions(+), 58 deletions(-) [+]
line wrap: on
line diff
--- a/src/jdk.hotspot.agent/linux/native/libsaproc/libproc_impl.c	Fri Jan 27 21:39:12 2017 +0100
+++ b/src/jdk.hotspot.agent/linux/native/libsaproc/libproc_impl.c	Mon Jan 30 13:48:14 2017 +0530
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2003, 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2003, 2017, 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
@@ -50,7 +50,7 @@
   }
 
 
-  if (strlen(alt_root) + strlen(name) < PATH_MAX) {
+  if (strlen(alt_root) + strlen(name) > PATH_MAX) {
     // Buffer too small.
     return -1;
   }
--- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/utilities/AbstractHeapGraphWriter.java	Fri Jan 27 21:39:12 2017 +0100
+++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/utilities/AbstractHeapGraphWriter.java	Mon Jan 30 13:48:14 2017 +0530
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2004, 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2004, 2017, 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
@@ -114,6 +114,8 @@
                     }
                 });
 
+                writeHeapRecordPrologue();
+
                 // write JavaThreads
                 writeJavaThreads();
 
--- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/utilities/HeapHprofBinWriter.java	Fri Jan 27 21:39:12 2017 +0100
+++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/utilities/HeapHprofBinWriter.java	Mon Jan 30 13:48:14 2017 +0530
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2004, 2016, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2004, 2017, 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
@@ -45,8 +45,8 @@
  * WARNING: This format is still under development, and is subject to
  * change without notice.
  *
- * header    "JAVA PROFILE 1.0.1" or "JAVA PROFILE 1.0.2" (0-terminated)
- * u4        size of identifiers. Identifiers are used to represent
+ * header     "JAVA PROFILE 1.0.2" (0-terminated)
+ * u4         size of identifiers. Identifiers are used to represent
  *            UTF8 strings, objects, stack traces, etc. They usually
  *            have the same size as host pointers. For example, on
  *            Solaris and Win32, the size is 4.
@@ -294,10 +294,9 @@
  *                u2        stack trace depth
  *
  *
- * When the header is "JAVA PROFILE 1.0.2" a heap dump can optionally
- * be generated as a sequence of heap dump segments. This sequence is
- * terminated by an end record. The additional tags allowed by format
- * "JAVA PROFILE 1.0.2" are:
+ * A heap dump can optionally be generated as a sequence of heap dump
+ * segments. This sequence is terminated by an end record. The additional
+ * tags allowed by format "JAVA PROFILE 1.0.2" are:
  *
  * HPROF_HEAP_DUMP_SEGMENT  denote a heap dump segment
  *
@@ -310,8 +309,6 @@
 
 public class HeapHprofBinWriter extends AbstractHeapGraphWriter {
 
-    // The heap size threshold used to determine if segmented format
-    // ("JAVA PROFILE 1.0.2") should be used.
     private static final long HPROF_SEGMENTED_HEAP_DUMP_THRESHOLD = 2L * 0x40000000;
 
     // The approximate size of a heap segment. Used to calculate when to create
@@ -319,7 +316,6 @@
     private static final long HPROF_SEGMENTED_HEAP_DUMP_SEGMENT_SIZE = 1L * 0x40000000;
 
     // hprof binary file header
-    private static final String HPROF_HEADER_1_0_1 = "JAVA PROFILE 1.0.1";
     private static final String HPROF_HEADER_1_0_2 = "JAVA PROFILE 1.0.2";
 
     // constants in enum HprofTag
@@ -380,6 +376,7 @@
     private static final int JVM_SIGNATURE_ARRAY   = '[';
     private static final int JVM_SIGNATURE_CLASS   = 'L';
 
+    private static final long MAX_U4_VALUE = 0xFFFFFFFFL;
     int serialNum = 1;
 
     public synchronized void write(String fileName) throws IOException {
@@ -469,7 +466,6 @@
             // length later - hprof format requires length.
             out.flush();
             currentSegmentStart = fos.getChannel().position();
-
             // write dummy length of 0 and we'll fix it later.
             out.writeInt(0);
         }
@@ -479,7 +475,7 @@
     protected void writeHeapRecordEpilogue() throws IOException {
         if (useSegmentedHeapDump) {
             out.flush();
-            if ((fos.getChannel().position() - currentSegmentStart - 4) >= HPROF_SEGMENTED_HEAP_DUMP_SEGMENT_SIZE) {
+            if ((fos.getChannel().position() - currentSegmentStart - 4L) >= HPROF_SEGMENTED_HEAP_DUMP_SEGMENT_SIZE) {
                 fillInHeapRecordLength();
                 currentSegmentStart = 0;
             }
@@ -488,14 +484,14 @@
 
     private void fillInHeapRecordLength() throws IOException {
 
-        // now get current position to calculate length
+        // now get the current position to calculate length
         long dumpEnd = fos.getChannel().position();
 
-        // calculate length of heap data
+        // calculate the length of heap data
         long dumpLenLong = (dumpEnd - currentSegmentStart - 4L);
 
         // Check length boundary, overflow could happen but is _very_ unlikely
-        if(dumpLenLong >= (4L * 0x40000000)){
+        if (dumpLenLong >= (4L * 0x40000000)) {
             throw new RuntimeException("Heap segment size overflow.");
         }
 
@@ -517,6 +513,71 @@
         fos.getChannel().position(currentPosition);
     }
 
+    // get the size in bytes for the requested type
+    private long getSizeForType(int type) throws IOException {
+        switch (type) {
+            case TypeArrayKlass.T_BOOLEAN:
+                return BOOLEAN_SIZE;
+            case TypeArrayKlass.T_INT:
+                return INT_SIZE;
+            case TypeArrayKlass.T_CHAR:
+                return CHAR_SIZE;
+            case TypeArrayKlass.T_SHORT:
+                return SHORT_SIZE;
+            case TypeArrayKlass.T_BYTE:
+                return BYTE_SIZE;
+            case TypeArrayKlass.T_LONG:
+                return LONG_SIZE;
+            case TypeArrayKlass.T_FLOAT:
+                return FLOAT_SIZE;
+            case TypeArrayKlass.T_DOUBLE:
+                return DOUBLE_SIZE;
+            default:
+                throw new RuntimeException(
+                    "Should not reach here: Unknown type: " + type);
+         }
+    }
+
+    private int getArrayHeaderSize(boolean isObjectAarray) {
+        return isObjectAarray?
+            ((int) BYTE_SIZE + 2 * (int) INT_SIZE + 2 * (int) OBJ_ID_SIZE):
+            (2 * (int) BYTE_SIZE + 2 * (int) INT_SIZE + (int) OBJ_ID_SIZE);
+    }
+
+    // Check if we need to truncate an array
+    private int calculateArrayMaxLength(long originalArrayLength,
+                                        int headerSize,
+                                        long typeSize,
+                                        String typeName) throws IOException {
+
+        long length = originalArrayLength;
+
+        // now get the current position to calculate length
+        long dumpEnd = fos.getChannel().position();
+        long originalLengthInBytes = originalArrayLength * typeSize;
+
+        // calculate the length of heap data
+        long currentRecordLength = (dumpEnd - currentSegmentStart - 4L);
+        if (currentRecordLength > 0 &&
+            (currentRecordLength + headerSize + originalLengthInBytes) > MAX_U4_VALUE) {
+            fillInHeapRecordLength();
+            currentSegmentStart = 0;
+            writeHeapRecordPrologue();
+            currentRecordLength = 0;
+        }
+
+        // Calculate the max bytes we can use.
+        long maxBytes = (MAX_U4_VALUE - (headerSize + currentRecordLength));
+
+        if (originalLengthInBytes > maxBytes) {
+            length = maxBytes/typeSize;
+            System.err.println("WARNING: Cannot dump array of type " + typeName
+                               + " with length " + originalArrayLength
+                               + "; truncating to length " + length);
+        }
+        return (int) length;
+    }
+
     private void writeClassDumpRecords() throws IOException {
         SystemDictionary sysDict = VM.getVM().getSystemDictionary();
         ClassLoaderDataGraph cldGraph = VM.getVM().getClassLoaderDataGraph();
@@ -694,12 +755,16 @@
     }
 
     protected void writeObjectArray(ObjArray array) throws IOException {
+        int headerSize = getArrayHeaderSize(true);
+        final int length = calculateArrayMaxLength(array.getLength(),
+                                                   headerSize,
+                                                   OBJ_ID_SIZE,
+                                                   "Object");
         out.writeByte((byte) HPROF_GC_OBJ_ARRAY_DUMP);
         writeObjectID(array);
         out.writeInt(DUMMY_STACK_TRACE_ID);
-        out.writeInt((int) array.getLength());
+        out.writeInt(length);
         writeObjectID(array.getKlass().getJavaMirror());
-        final int length = (int) array.getLength();
         for (int index = 0; index < length; index++) {
             OopHandle handle = array.getOopHandleAt(index);
             writeObjectID(getAddressValue(handle));
@@ -707,101 +772,101 @@
     }
 
     protected void writePrimitiveArray(TypeArray array) throws IOException {
+        int headerSize = getArrayHeaderSize(false);
+        TypeArrayKlass tak = (TypeArrayKlass) array.getKlass();
+        final int type = (int) tak.getElementType();
+        final String typeName = tak.getElementTypeName();
+        final long typeSize = getSizeForType(type);
+        final int length = calculateArrayMaxLength(array.getLength(),
+                                                   headerSize,
+                                                   typeSize,
+                                                   typeName);
         out.writeByte((byte) HPROF_GC_PRIM_ARRAY_DUMP);
         writeObjectID(array);
         out.writeInt(DUMMY_STACK_TRACE_ID);
-        out.writeInt((int) array.getLength());
-        TypeArrayKlass tak = (TypeArrayKlass) array.getKlass();
-        final int type = (int) tak.getElementType();
+        out.writeInt(length);
         out.writeByte((byte) type);
         switch (type) {
             case TypeArrayKlass.T_BOOLEAN:
-                writeBooleanArray(array);
+                writeBooleanArray(array, length);
                 break;
             case TypeArrayKlass.T_CHAR:
-                writeCharArray(array);
+                writeCharArray(array, length);
                 break;
             case TypeArrayKlass.T_FLOAT:
-                writeFloatArray(array);
+                writeFloatArray(array, length);
                 break;
             case TypeArrayKlass.T_DOUBLE:
-                writeDoubleArray(array);
+                writeDoubleArray(array, length);
                 break;
             case TypeArrayKlass.T_BYTE:
-                writeByteArray(array);
+                writeByteArray(array, length);
                 break;
             case TypeArrayKlass.T_SHORT:
-                writeShortArray(array);
+                writeShortArray(array, length);
                 break;
             case TypeArrayKlass.T_INT:
-                writeIntArray(array);
+                writeIntArray(array, length);
                 break;
             case TypeArrayKlass.T_LONG:
-                writeLongArray(array);
+                writeLongArray(array, length);
                 break;
             default:
-                throw new RuntimeException("should not reach here");
+                throw new RuntimeException(
+                    "Should not reach here: Unknown type: " + type);
         }
     }
 
-    private void writeBooleanArray(TypeArray array) throws IOException {
-        final int length = (int) array.getLength();
+    private void writeBooleanArray(TypeArray array, int length) throws IOException {
         for (int index = 0; index < length; index++) {
              long offset = BOOLEAN_BASE_OFFSET + index * BOOLEAN_SIZE;
              out.writeBoolean(array.getHandle().getJBooleanAt(offset));
         }
     }
 
-    private void writeByteArray(TypeArray array) throws IOException {
-        final int length = (int) array.getLength();
+    private void writeByteArray(TypeArray array, int length) throws IOException {
         for (int index = 0; index < length; index++) {
              long offset = BYTE_BASE_OFFSET + index * BYTE_SIZE;
              out.writeByte(array.getHandle().getJByteAt(offset));
         }
     }
 
-    private void writeShortArray(TypeArray array) throws IOException {
-        final int length = (int) array.getLength();
+    private void writeShortArray(TypeArray array, int length) throws IOException {
         for (int index = 0; index < length; index++) {
              long offset = SHORT_BASE_OFFSET + index * SHORT_SIZE;
              out.writeShort(array.getHandle().getJShortAt(offset));
         }
     }
 
-    private void writeIntArray(TypeArray array) throws IOException {
-        final int length = (int) array.getLength();
+    private void writeIntArray(TypeArray array, int length) throws IOException {
         for (int index = 0; index < length; index++) {
              long offset = INT_BASE_OFFSET + index * INT_SIZE;
              out.writeInt(array.getHandle().getJIntAt(offset));
         }
     }
 
-    private void writeLongArray(TypeArray array) throws IOException {
-        final int length = (int) array.getLength();
+    private void writeLongArray(TypeArray array, int length) throws IOException {
         for (int index = 0; index < length; index++) {
              long offset = LONG_BASE_OFFSET + index * LONG_SIZE;
              out.writeLong(array.getHandle().getJLongAt(offset));
         }
     }
 
-    private void writeCharArray(TypeArray array) throws IOException {
-        final int length = (int) array.getLength();
+    private void writeCharArray(TypeArray array, int length) throws IOException {
         for (int index = 0; index < length; index++) {
              long offset = CHAR_BASE_OFFSET + index * CHAR_SIZE;
              out.writeChar(array.getHandle().getJCharAt(offset));
         }
     }
 
-    private void writeFloatArray(TypeArray array) throws IOException {
-        final int length = (int) array.getLength();
+    private void writeFloatArray(TypeArray array, int length) throws IOException {
         for (int index = 0; index < length; index++) {
              long offset = FLOAT_BASE_OFFSET + index * FLOAT_SIZE;
              out.writeFloat(array.getHandle().getJFloatAt(offset));
         }
     }
 
-    private void writeDoubleArray(TypeArray array) throws IOException {
-        final int length = (int) array.getLength();
+    private void writeDoubleArray(TypeArray array, int length) throws IOException {
         for (int index = 0; index < length; index++) {
              long offset = DOUBLE_BASE_OFFSET + index * DOUBLE_SIZE;
              out.writeDouble(array.getHandle().getJDoubleAt(offset));
@@ -996,12 +1061,7 @@
     // writes hprof binary file header
     private void writeFileHeader() throws IOException {
         // version string
-        if(useSegmentedHeapDump) {
-            out.writeBytes(HPROF_HEADER_1_0_2);
-        }
-        else {
-            out.writeBytes(HPROF_HEADER_1_0_1);
-        }
+        out.writeBytes(HPROF_HEADER_1_0_2);
         out.writeByte((byte)'\0');
 
         // write identifier size. we use pointers as identifiers.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/serviceability/sa/LingeredAppWithLargeArray.java	Mon Jan 30 13:48:14 2017 +0530
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2017, 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.
+ */
+
+import jdk.test.lib.apps.LingeredApp;
+
+public class LingeredAppWithLargeArray extends LingeredApp {
+    public static void main(String args[]) {
+        int[] hugeArray = new int[Integer.MAX_VALUE/2];
+        LingeredApp.main(args);
+    }
+ }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/serviceability/sa/TestHeapDumpForLargeArray.java	Mon Jan 30 13:48:14 2017 +0530
@@ -0,0 +1,117 @@
+/*
+ * Copyright (c) 2017, 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.
+ */
+
+import java.util.ArrayList;
+import java.util.List;
+import java.io.File;
+import java.nio.file.Files;
+import java.io.IOException;
+import java.io.BufferedInputStream;
+import java.util.stream.Collectors;
+import java.io.FileInputStream;
+
+import sun.jvm.hotspot.HotSpotAgent;
+import sun.jvm.hotspot.debugger.*;
+
+import jdk.test.lib.apps.LingeredApp;
+import jdk.test.lib.JDKToolLauncher;
+import jdk.test.lib.JDKToolFinder;
+import jdk.test.lib.Platform;
+import jdk.test.lib.process.ProcessTools;
+import jdk.test.lib.process.OutputAnalyzer;
+import jdk.test.lib.Utils;
+import jdk.test.lib.Asserts;
+
+/*
+ * @test
+ * @library /test/lib
+ * @bug 8171084
+ * @requires (vm.bits == "64" & os.maxMemory > 8g)
+ * @modules java.base/jdk.internal.misc
+ *          jdk.hotspot.agent/sun.jvm.hotspot
+ *          jdk.hotspot.agent/sun.jvm.hotspot.utilities
+ *          jdk.hotspot.agent/sun.jvm.hotspot.oops
+ *          jdk.hotspot.agent/sun.jvm.hotspot.debugger
+ * @run main/timeout=1800/othervm -Xmx8g TestHeapDumpForLargeArray
+ */
+
+public class TestHeapDumpForLargeArray {
+
+    private static LingeredAppWithLargeArray theApp = null;
+
+    private static void attachAndDump(String heapDumpFileName,
+                                      long lingeredAppPid) throws Exception {
+
+        JDKToolLauncher launcher = JDKToolLauncher.createUsingTestJDK("jhsdb");
+        launcher.addToolArg("jmap");
+        launcher.addToolArg("--binaryheap");
+        launcher.addToolArg("--dumpfile");
+        launcher.addToolArg(heapDumpFileName);
+        launcher.addToolArg("--pid");
+        launcher.addToolArg(Long.toString(lingeredAppPid));
+
+        ProcessBuilder processBuilder = new ProcessBuilder();
+        processBuilder.command(launcher.getCommand());
+        System.out.println(
+            processBuilder.command().stream().collect(Collectors.joining(" ")));
+
+        OutputAnalyzer SAOutput = ProcessTools.executeProcess(processBuilder);
+        SAOutput.shouldHaveExitValue(0);
+        SAOutput.shouldNotContain("Heap segment size overflow");
+        SAOutput.shouldContain("truncating to");
+        SAOutput.shouldContain("heap written to");
+        SAOutput.shouldContain(heapDumpFileName);
+        System.out.println(SAOutput.getOutput());
+
+    }
+
+    public static void main (String... args) throws Exception {
+
+        String heapDumpFileName = "LargeArrayHeapDump.bin";
+
+        if (!Platform.shouldSAAttach()) {
+            System.out.println(
+               "SA attach not expected to work - test skipped.");
+            return;
+        }
+
+        File heapDumpFile = new File(heapDumpFileName);
+        if (heapDumpFile.exists()) {
+            heapDumpFile.delete();
+        }
+
+        try {
+            List<String> vmArgs = new ArrayList<String>();
+            vmArgs.add("-XX:+UsePerfData");
+            vmArgs.add("-Xmx8g");
+            vmArgs.addAll(Utils.getVmOptions());
+
+            theApp = new LingeredAppWithLargeArray();
+            LingeredApp.startApp(vmArgs, theApp);
+            attachAndDump(heapDumpFileName, theApp.getPid());
+        } finally {
+            LingeredApp.stopApp(theApp);
+            heapDumpFile.delete();
+        }
+    }
+}
--- a/test/serviceability/sa/jmap-hprof/JMapHProfLargeHeapTest.java	Fri Jan 27 21:39:12 2017 +0100
+++ b/test/serviceability/sa/jmap-hprof/JMapHProfLargeHeapTest.java	Mon Jan 30 13:48:14 2017 +0530
@@ -53,7 +53,6 @@
 
 public class JMapHProfLargeHeapTest {
     private static final String HEAP_DUMP_FILE_NAME = "heap.bin";
-    private static final String HPROF_HEADER_1_0_1 = "JAVA PROFILE 1.0.1";
     private static final String HPROF_HEADER_1_0_2 = "JAVA PROFILE 1.0.2";
     private static final long M = 1024L;
     private static final long G = 1024L * M;
@@ -65,9 +64,7 @@
         }
 
         // All heap dumps should create 1.0.2 file format
-        // Hotspot internal heapdumper always use HPROF_HEADER_1_0_2 format,
-        // but SA heapdumper still use HPROF_HEADER_1_0_1 for small heaps
-        testHProfFileFormat("-Xmx1g", 22 * M, HPROF_HEADER_1_0_1);
+        testHProfFileFormat("-Xmx1g", 22 * M, HPROF_HEADER_1_0_2);
 
         /**
          * This test was deliberately commented out since the test system lacks