From 4966bd11acaed4deece797d1c618fe037282aeff Mon Sep 17 00:00:00 2001 From: Christian Haeubl Date: Sat, 25 Jun 2022 18:02:51 +0200 Subject: [PATCH 1/5] Reimplemented heap dumping. --- .../graalvm/compiler/core/common/NumUtil.java | 5 + .../oracle/svm/core/genscavenge/GCImpl.java | 6 +- .../oracle/svm/core/genscavenge/HeapImpl.java | 1 + .../genscavenge/RuntimeCodeCacheWalker.java | 4 +- .../core/genscavenge/ThreadLocalMTWalker.java | 52 - .../posix/PosixRawFileOperationSupport.java | 51 +- .../com/oracle/svm/core/posix/PosixStat.java | 6 +- .../oracle/svm/core/posix/headers/Fcntl.java | 6 + .../AllocationFreeFileOutputStreamPosix.java | 149 -- .../svm/core/DumpHeapOnSignalFeature.java | 2 +- .../com/oracle/svm/core/JavaMainWrapper.java | 21 +- .../oracle/svm/core/VMInspectionOptions.java | 5 +- .../svm/core/code/RuntimeCodeCache.java | 6 +- .../svm/core/code/RuntimeCodeInfoMemory.java | 24 +- .../svm/core/collections/GrowableArray.java | 54 + .../core/collections/GrowableArrayAccess.java | 78 + .../svm/core/heap/AbstractMemoryMXBean.java | 6 +- .../core/heap/RuntimeCodeCacheCleaner.java | 9 +- .../svm/core/heapdump/HProfSubRecord.java | 54 + ...mpWriter.java => HProfTopLevelRecord.java} | 39 +- .../oracle/svm/core/heapdump/HProfType.java | 77 + .../svm/core/heapdump/HeapDumpFeature.java | 57 - .../svm/core/heapdump/HeapDumpMetadata.java | 368 ++++ .../core/heapdump/HeapDumpSupportImpl.java | 137 ++ .../svm/core/heapdump/HeapDumpUtils.java | 347 ---- .../svm/core/heapdump/HeapDumpWriter.java | 1151 ++++++++++- .../svm/core/heapdump/HeapDumpWriterImpl.java | 1759 ----------------- .../oracle/svm/core/heapdump/StorageKind.java | 51 + .../com/oracle/svm/core/hub/DynamicHub.java | 6 +- .../oracle/svm/core/jfr/JfrChunkWriter.java | 51 +- .../os/AbstractRawFileOperationSupport.java | 41 +- .../core/os/BufferedFileOperationSupport.java | 448 +++++ .../svm/core/os/RawFileOperationSupport.java | 67 +- .../svm/core/thread/NativeVMOperation.java | 2 +- .../svm/core/thread/VMOperationControl.java | 1 + .../threadlocal/VMThreadLocalMTSupport.java | 20 +- .../coder/ByteStream.java} | 33 +- .../svm/core/util/coder/ByteStreamAccess.java | 33 + .../svm/core/util/coder/NativeCoder.java | 43 + .../svm/core/util/coder/Pack200Coder.java | 67 + .../svm/hosted/heap/HeapDumpFeature.java | 226 +++ .../hosted/heap/HeapDumpFieldsMapFeature.java | 162 -- 42 files changed, 3034 insertions(+), 2691 deletions(-) delete mode 100644 substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/ThreadLocalMTWalker.java delete mode 100644 substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/heapdump/AllocationFreeFileOutputStreamPosix.java create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/collections/GrowableArray.java create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/collections/GrowableArrayAccess.java create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/HProfSubRecord.java rename substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/{UnimplementedHeapDumpWriter.java => HProfTopLevelRecord.java} (63%) create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/HProfType.java delete mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/HeapDumpFeature.java create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/HeapDumpMetadata.java create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/HeapDumpSupportImpl.java delete mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/HeapDumpUtils.java delete mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/HeapDumpWriterImpl.java create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/StorageKind.java create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/os/BufferedFileOperationSupport.java rename substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/{heapdump/AllocationFreeOutputStream.java => util/coder/ByteStream.java} (66%) create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/coder/ByteStreamAccess.java create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/coder/NativeCoder.java create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/coder/Pack200Coder.java create mode 100644 substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/HeapDumpFeature.java delete mode 100644 substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/HeapDumpFieldsMapFeature.java diff --git a/compiler/src/org.graalvm.compiler.core.common/src/org/graalvm/compiler/core/common/NumUtil.java b/compiler/src/org.graalvm.compiler.core.common/src/org/graalvm/compiler/core/common/NumUtil.java index 0384e195ff41..15b935770a25 100644 --- a/compiler/src/org.graalvm.compiler.core.common/src/org/graalvm/compiler/core/common/NumUtil.java +++ b/compiler/src/org.graalvm.compiler.core.common/src/org/graalvm/compiler/core/common/NumUtil.java @@ -126,6 +126,11 @@ public static short safeToShort(int v) { return (short) v; } + public static int safeToUInt(long v) { + assert isUInt(v); + return (int) v; + } + public static int safeToInt(long v) { assert isInt(v); return (int) v; diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/GCImpl.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/GCImpl.java index 653da0c3dddc..fc8419d5a49a 100644 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/GCImpl.java +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/GCImpl.java @@ -90,6 +90,7 @@ import com.oracle.svm.core.thread.PlatformThreads; import com.oracle.svm.core.thread.VMOperation; import com.oracle.svm.core.thread.VMThreads; +import com.oracle.svm.core.threadlocal.VMThreadLocalMTSupport; import com.oracle.svm.core.util.TimeUtils; import com.oracle.svm.core.util.VMError; @@ -179,7 +180,6 @@ boolean collectWithoutAllocating(GCCause cause, boolean forceFullGC) { int size = SizeOf.get(CollectionVMOperationData.class); CollectionVMOperationData data = StackValue.get(size); UnmanagedMemoryUtil.fill((Pointer) data, WordFactory.unsigned(size), (byte) 0); - data.setNativeVMOperation(collectOperation); data.setCauseId(cause.getId()); data.setRequestingEpoch(getCollectionEpoch()); data.setRequestingNanoTime(System.nanoTime()); @@ -947,7 +947,9 @@ private void walkThreadLocals() { if (SubstrateOptions.MultiThreaded.getValue()) { Timer walkThreadLocalsTimer = timers.walkThreadLocals.open(); try { - ThreadLocalMTWalker.walk(greyToBlackObjRefVisitor); + for (IsolateThread isolateThread = VMThreads.firstThread(); isolateThread.isNonNull(); isolateThread = VMThreads.nextThread(isolateThread)) { + VMThreadLocalMTSupport.singleton().walk(isolateThread, greyToBlackObjRefVisitor); + } } finally { walkThreadLocalsTimer.close(); } diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/HeapImpl.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/HeapImpl.java index 0fb8fecdbf65..c25fd61ea8ac 100644 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/HeapImpl.java +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/HeapImpl.java @@ -457,6 +457,7 @@ public boolean walkImageHeapObjects(ObjectVisitor visitor) { @Override public boolean walkCollectedHeapObjects(ObjectVisitor visitor) { VMOperation.guaranteeInProgressAtSafepoint("Must only be called at a safepoint"); + ThreadLocalAllocation.disableAndFlushForAllThreads(); return getYoungGeneration().walkObjects(visitor) && getOldGeneration().walkObjects(visitor); } diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/RuntimeCodeCacheWalker.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/RuntimeCodeCacheWalker.java index 5440ef78c39d..2ae2bbacc974 100644 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/RuntimeCodeCacheWalker.java +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/RuntimeCodeCacheWalker.java @@ -29,13 +29,13 @@ import org.graalvm.nativeimage.Platforms; import com.oracle.svm.core.SubstrateGCOptions; -import com.oracle.svm.core.util.DuplicatedInNativeCode; import com.oracle.svm.core.code.CodeInfo; import com.oracle.svm.core.code.CodeInfoAccess; import com.oracle.svm.core.code.RuntimeCodeCache.CodeInfoVisitor; import com.oracle.svm.core.code.RuntimeCodeInfoAccess; import com.oracle.svm.core.code.UntetheredCodeInfoAccess; import com.oracle.svm.core.heap.ObjectReferenceVisitor; +import com.oracle.svm.core.util.DuplicatedInNativeCode; /** * References from the runtime compiled code to the Java heap must be considered either strong or @@ -58,7 +58,7 @@ final class RuntimeCodeCacheWalker implements CodeInfoVisitor { @Override @DuplicatedInNativeCode - public boolean visitCode(T codeInfo) { + public boolean visitCode(CodeInfo codeInfo) { if (RuntimeCodeInfoAccess.areAllObjectsOnImageHeap(codeInfo)) { return true; } diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/ThreadLocalMTWalker.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/ThreadLocalMTWalker.java deleted file mode 100644 index 122336af1c18..000000000000 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/ThreadLocalMTWalker.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (c) 2014, 2019, 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. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * 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. - */ -package com.oracle.svm.core.genscavenge; - -import org.graalvm.nativeimage.ImageSingletons; -import org.graalvm.nativeimage.IsolateThread; -import org.graalvm.word.Pointer; - -import com.oracle.svm.core.c.NonmovableArray; -import com.oracle.svm.core.c.NonmovableArrays; -import com.oracle.svm.core.heap.GC; -import com.oracle.svm.core.heap.InstanceReferenceMapDecoder; -import com.oracle.svm.core.heap.ObjectReferenceVisitor; -import com.oracle.svm.core.thread.VMThreads; -import com.oracle.svm.core.threadlocal.VMThreadLocalMTSupport; - -/** - * The class is registered with the {@link GC} to process VM thread local variables of type - * {@link Object}. - */ -final class ThreadLocalMTWalker { - static void walk(ObjectReferenceVisitor referenceVisitor) { - VMThreadLocalMTSupport threadLocals = ImageSingletons.lookup(VMThreadLocalMTSupport.class); - NonmovableArray threadRefMapEncoding = NonmovableArrays.fromImageHeap(threadLocals.vmThreadReferenceMapEncoding); - for (IsolateThread vmThread = VMThreads.firstThread(); vmThread.isNonNull(); vmThread = VMThreads.nextThread(vmThread)) { - InstanceReferenceMapDecoder.walkOffsetsFromPointer((Pointer) vmThread, threadRefMapEncoding, - threadLocals.vmThreadReferenceMapIndex, referenceVisitor, null); - } - } -} diff --git a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixRawFileOperationSupport.java b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixRawFileOperationSupport.java index fe39bc674048..7fe1c3aae1f1 100644 --- a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixRawFileOperationSupport.java +++ b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixRawFileOperationSupport.java @@ -55,6 +55,16 @@ public PosixRawFileOperationSupport(boolean useNativeByteOrder) { super(useNativeByteOrder); } + @Override + public RawFileDescriptor create(File file, FileCreationMode creationMode, FileAccessMode accessMode) { + String path = file.getPath(); + int flags = parseMode(creationMode) | parseMode(accessMode); + + try (CTypeConversion.CCharPointerHolder cPath = CTypeConversion.toCString(path)) { + return WordFactory.signed(Fcntl.NoTransitions.open(cPath.get(), flags, DEFAULT_PERMISSIONS)); + } + } + @Override public RawFileDescriptor open(File file, FileAccessMode mode) { String path = file.getPath(); @@ -83,24 +93,24 @@ public boolean close(RawFileDescriptor fd) { @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) @Override - public SignedWord size(RawFileDescriptor fd) { + public long size(RawFileDescriptor fd) { int posixFd = getPosixFileDescriptor(fd); return PosixStat.getSize(posixFd); } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) @Override - public SignedWord position(RawFileDescriptor fd) { + public long position(RawFileDescriptor fd) { int posixFd = getPosixFileDescriptor(fd); - return Unistd.NoTransitions.lseek(posixFd, WordFactory.signed(0), Unistd.SEEK_CUR()); + return Unistd.NoTransitions.lseek(posixFd, WordFactory.signed(0), Unistd.SEEK_CUR()).rawValue(); } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) @Override - public boolean seek(RawFileDescriptor fd, SignedWord position) { + public boolean seek(RawFileDescriptor fd, long position) { int posixFd = getPosixFileDescriptor(fd); - SignedWord newPos = Unistd.NoTransitions.lseek(posixFd, position, Unistd.SEEK_SET()); - return position.equal(newPos); + SignedWord newPos = Unistd.NoTransitions.lseek(posixFd, WordFactory.signed(position), Unistd.SEEK_SET()); + return position == newPos.rawValue(); } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) @@ -127,7 +137,7 @@ public boolean write(RawFileDescriptor fd, Pointer data, UnsignedWord size) { @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) @Override - public SignedWord read(RawFileDescriptor fd, Pointer buffer, UnsignedWord bufferSize) { + public long read(RawFileDescriptor fd, Pointer buffer, UnsignedWord bufferSize) { int posixFd = getPosixFileDescriptor(fd); SignedWord readBytes; @@ -135,7 +145,7 @@ public SignedWord read(RawFileDescriptor fd, Pointer buffer, UnsignedWord buffer readBytes = Unistd.NoTransitions.read(posixFd, buffer, bufferSize); } while (readBytes.equal(-1) && LibC.errno() == Errno.EINTR()); - return readBytes; + return readBytes.rawValue(); } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) @@ -145,14 +155,25 @@ private static int getPosixFileDescriptor(RawFileDescriptor fd) { return result; } + private static int parseMode(FileCreationMode mode) { + switch (mode) { + case CREATE: + return Fcntl.O_CREAT() | Fcntl.O_EXCL(); + case CREATE_OR_REPLACE: + return Fcntl.O_CREAT() | Fcntl.O_TRUNC(); + default: + throw VMError.shouldNotReachHere(); + } + } + private static int parseMode(FileAccessMode mode) { switch (mode) { case READ: return Fcntl.O_RDONLY(); case READ_WRITE: - return Fcntl.O_RDWR() | Fcntl.O_CREAT(); + return Fcntl.O_RDWR(); case WRITE: - return Fcntl.O_WRONLY() | Fcntl.O_CREAT(); + return Fcntl.O_WRONLY(); default: throw VMError.shouldNotReachHere(); } @@ -162,14 +183,14 @@ private static int parseMode(FileAccessMode mode) { @AutomaticallyRegisteredFeature class PosixRawFileOperationFeature implements InternalFeature { @Override - public void beforeAnalysis(BeforeAnalysisAccess access) { + public void afterRegistration(AfterRegistrationAccess access) { ByteOrder nativeByteOrder = ByteOrder.nativeOrder(); assert nativeByteOrder == ByteOrder.LITTLE_ENDIAN || nativeByteOrder == ByteOrder.BIG_ENDIAN; - PosixRawFileOperationSupport littleEndianSupport = new PosixRawFileOperationSupport(ByteOrder.LITTLE_ENDIAN == nativeByteOrder); - PosixRawFileOperationSupport bigEndianSupport = new PosixRawFileOperationSupport(ByteOrder.BIG_ENDIAN == nativeByteOrder); - PosixRawFileOperationSupport nativeByteOrderSupport = nativeByteOrder == ByteOrder.LITTLE_ENDIAN ? littleEndianSupport : bigEndianSupport; + PosixRawFileOperationSupport littleEndian = new PosixRawFileOperationSupport(ByteOrder.LITTLE_ENDIAN == nativeByteOrder); + PosixRawFileOperationSupport bigEndian = new PosixRawFileOperationSupport(ByteOrder.BIG_ENDIAN == nativeByteOrder); + PosixRawFileOperationSupport nativeOrder = nativeByteOrder == ByteOrder.LITTLE_ENDIAN ? littleEndian : bigEndian; - ImageSingletons.add(RawFileOperationSupportHolder.class, new RawFileOperationSupportHolder(littleEndianSupport, bigEndianSupport, nativeByteOrderSupport)); + ImageSingletons.add(RawFileOperationSupportHolder.class, new RawFileOperationSupportHolder(littleEndian, bigEndian, nativeOrder)); } } diff --git a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixStat.java b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixStat.java index 8ad151c02cbc..5120ba660ef4 100644 --- a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixStat.java +++ b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixStat.java @@ -26,8 +26,6 @@ import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; -import org.graalvm.word.SignedWord; -import org.graalvm.word.WordFactory; import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.graal.stackvalue.UnsafeStackValue; @@ -54,7 +52,7 @@ public static boolean isOpen(int fd) { } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - public static SignedWord getSize(int fd) { + public static long getSize(int fd) { long size = -1; if (Platform.includedIn(Platform.LINUX.class)) { LinuxStat.stat64 stat = UnsafeStackValue.get(LinuxStat.stat64.class); @@ -67,7 +65,7 @@ public static SignedWord getSize(int fd) { size = stat.st_size(); } } - return WordFactory.signed(size); + return size; } @Platforms(Platform.HOSTED_ONLY.class) diff --git a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/headers/Fcntl.java b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/headers/Fcntl.java index b140697794ab..10ce569238f5 100644 --- a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/headers/Fcntl.java +++ b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/headers/Fcntl.java @@ -50,6 +50,12 @@ public class Fcntl { @CConstant public static native int O_CREAT(); + @CConstant + public static native int O_TRUNC(); + + @CConstant + public static native int O_EXCL(); + public static class NoTransitions { @CFunction(value = "openSII", transition = Transition.NO_TRANSITION) public static native int open(CCharPointer pathname, int flags, int mode); diff --git a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/heapdump/AllocationFreeFileOutputStreamPosix.java b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/heapdump/AllocationFreeFileOutputStreamPosix.java deleted file mode 100644 index a21e6ece449d..000000000000 --- a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/heapdump/AllocationFreeFileOutputStreamPosix.java +++ /dev/null @@ -1,149 +0,0 @@ -/* - * Copyright (c) 2022, 2022, 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. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * 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. - */ -package com.oracle.svm.core.posix.heapdump; - -import java.io.FileDescriptor; -import java.io.FileOutputStream; -import java.io.IOException; - -import org.graalvm.nativeimage.Platform; -import org.graalvm.nativeimage.Platforms; -import org.graalvm.nativeimage.c.type.CCharPointer; -import org.graalvm.word.WordFactory; - -import com.oracle.svm.core.feature.AutomaticallyRegisteredImageSingleton; -import com.oracle.svm.core.graal.stackvalue.UnsafeStackValue; -import com.oracle.svm.core.heapdump.HeapDumpWriterImpl.AllocationFreeFileOutputStream; -import com.oracle.svm.core.posix.PosixUtils; -import com.oracle.svm.core.posix.headers.Unistd; -import com.oracle.svm.core.util.VMError; - -/** - * Posix implementation of allocation-free output stream created from FileOutputStream. - * - * The limitation to Linux and Darwin is necessary because the implementation currently uses - * posix-dependent low-level code. See GR-9725. - */ -@AutomaticallyRegisteredImageSingleton(AllocationFreeFileOutputStream.class) -@Platforms({Platform.LINUX.class, Platform.DARWIN.class}) -final class AllocationFreeFileOutputStreamPosix extends AllocationFreeFileOutputStream { - - /** - * Pre-allocated exceptions, for throwing from code that must not allocate. - */ - private static final IOException preallocatedIOException = new IOException("Write failed."); - private static final ArrayIndexOutOfBoundsException preallocatedArrayIndexOutOfBoundsException = new ArrayIndexOutOfBoundsException(); - - private FileOutputStream fos; - private FileDescriptor fileDescriptor; - - AllocationFreeFileOutputStreamPosix() { - - } - - private AllocationFreeFileOutputStreamPosix(FileOutputStream fileOutputStream) throws IOException { - fos = fileOutputStream; - fileDescriptor = fos.getFD(); - - if (!(Platform.includedIn(Platform.LINUX.class) || Platform.includedIn(Platform.DARWIN.class))) { - /* See GR-9725 */ - throw VMError.unsupportedFeature("Heap dump writing currently contains Posix specific code"); - } - } - - @Override - public AllocationFreeFileOutputStream newStreamFor(FileOutputStream fileOutputStream) throws IOException { - return new AllocationFreeFileOutputStreamPosix(fileOutputStream); - } - - @Override - public void write(int b) throws IOException { - final CCharPointer buffer = UnsafeStackValue.get(CCharPointer.class); - buffer.write((byte) b); - final boolean writeResult = PosixUtils.writeBytes(fileDescriptor, buffer, WordFactory.unsigned(1)); - if (!writeResult) { - throw preallocatedIOException; - } - } - - @Override - public void write(byte[] b, int off, int len) throws IOException { - /* Sanity check the arguments. */ - if ((b == null) || ((off < 0) || (len < 0) || ((b.length - off) < len))) { - throw preallocatedArrayIndexOutOfBoundsException; - } - - /* - * Stack allocation needs an allocation size that is a compile time constant, so we split - * the byte array up in multiple chunks and write them separately. - */ - final int chunkSize = 256; - final CCharPointer bytes = UnsafeStackValue.get(chunkSize); - - int chunkOffset = off; - int inputLength = len; - while (inputLength > 0) { - int chunkLength = Math.min(inputLength, chunkSize); - - for (int i = 0; i < chunkLength; i++) { - bytes.write(i, b[chunkOffset + i]); - } - - if (!PosixUtils.writeBytes(fileDescriptor, bytes, WordFactory.unsigned(chunkLength))) { - throw preallocatedIOException; - } - - chunkOffset += chunkLength; - inputLength -= chunkLength; - } - } - - @Override - public void close() throws IOException { - fos.close(); - } - - @Override - public void flush() throws IOException { - } - - /** - * Read the current position in a file descriptor. - */ - @Override - protected long position() { - int fd = PosixUtils.getFD(fileDescriptor); - return Unistd.lseek(fd, WordFactory.zero(), Unistd.SEEK_CUR()).rawValue(); - } - - /** - * Set the current position in a file descriptor. - */ - @Override - protected long position(long offset) { - int fd = PosixUtils.getFD(fileDescriptor); - return Unistd.lseek(fd, WordFactory.signed(offset), Unistd.SEEK_SET()).rawValue(); - } -} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/DumpHeapOnSignalFeature.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/DumpHeapOnSignalFeature.java index 9125f8f32f45..0eb2753f1445 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/DumpHeapOnSignalFeature.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/DumpHeapOnSignalFeature.java @@ -33,10 +33,10 @@ import org.graalvm.nativeimage.ProcessProperties; import org.graalvm.nativeimage.VMRuntime; +import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature; import com.oracle.svm.core.feature.InternalFeature; import com.oracle.svm.core.jdk.RuntimeSupport; import com.oracle.svm.core.log.Log; -import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature; import sun.misc.Signal; import sun.misc.SignalHandler; diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/JavaMainWrapper.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/JavaMainWrapper.java index b02fb7c24b59..e05ac0ac79c1 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/JavaMainWrapper.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/JavaMainWrapper.java @@ -142,6 +142,15 @@ private static int runCore() { */ private static int runCore0() { try { + if (SubstrateOptions.ParseRuntimeOptions.getValue()) { + /* + * When options are not parsed yet, it is also too early to run the startup hooks + * because they often depend on option values. The user is expected to manually run + * the startup hooks after setting all option values. + */ + VMRuntime.initialize(); + } + if (SubstrateOptions.DumpHeapAndExit.getValue()) { if (VMInspectionOptions.hasHeapDumpSupport()) { String absoluteHeapDumpPath = SubstrateOptions.getHeapDumpPath(SubstrateOptions.Name.getValue() + ".hprof"); @@ -149,20 +158,12 @@ private static int runCore0() { System.out.println("Heap dump created at '" + absoluteHeapDumpPath + "'."); return 0; } else { - System.err.println("Unable to dump heap. Heap dumping is only supported for native executables built with `" + VMInspectionOptions.getHeapdumpsCommandArgument() + "`."); + System.err.println("Unable to dump heap. Heap dumping is only supported on Linux and MacOS for native executables built with `" + + VMInspectionOptions.getHeapdumpsCommandArgument() + "`."); return 1; } } - if (SubstrateOptions.ParseRuntimeOptions.getValue()) { - /* - * When options are not parsed yet, it is also too early to run the startup hooks - * because they often depend on option values. The user is expected to manually run - * the startup hooks after setting all option values. - */ - VMRuntime.initialize(); - } - ThreadListenerSupport.get().beforeThreadRun(); // Ensure that native code using JNI_GetCreatedJavaVMs finds this isolate. diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/VMInspectionOptions.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/VMInspectionOptions.java index c6dd49651f4d..a249ed5fcbf6 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/VMInspectionOptions.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/VMInspectionOptions.java @@ -35,6 +35,7 @@ import org.graalvm.compiler.options.OptionType; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platform.WINDOWS; +import org.graalvm.nativeimage.Platforms; import com.oracle.svm.core.option.APIOption; import com.oracle.svm.core.option.HostedOptionKey; @@ -61,6 +62,7 @@ public final class VMInspectionOptions { public static final HostedOptionKey EnableMonitoringFeatures = new HostedOptionKey<>(LocatableMultiOptionValue.Strings.buildWithCommaDelimiter(), VMInspectionOptions::validateEnableMonitoringFeatures); + @Platforms(Platform.HOSTED_ONLY.class) public static void validateEnableMonitoringFeatures(@SuppressWarnings("unused") OptionKey optionKey) { Set enabledFeatures = getEnabledMonitoringFeatures(); if (enabledFeatures.contains(MONITORING_DEFAULT_NAME)) { @@ -77,6 +79,7 @@ public static void validateEnableMonitoringFeatures(@SuppressWarnings("unused") } } + @Platforms(Platform.HOSTED_ONLY.class) private static String getDefaultMonitoringCommandArgument() { return SubstrateOptionsParser.commandArgument(EnableMonitoringFeatures, MONITORING_DEFAULT_NAME); } @@ -86,7 +89,7 @@ public static String getHeapdumpsCommandArgument() { return SubstrateOptionsParser.commandArgument(EnableMonitoringFeatures, MONITORING_HEAPDUMP_NAME); } - public static Set getEnabledMonitoringFeatures() { + private static Set getEnabledMonitoringFeatures() { return new HashSet<>(EnableMonitoringFeatures.getValue().values()); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/RuntimeCodeCache.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/RuntimeCodeCache.java index 761b404a6c7d..6b3ca74d0e8c 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/RuntimeCodeCache.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/RuntimeCodeCache.java @@ -40,16 +40,16 @@ import org.graalvm.word.UnsignedWord; import org.graalvm.word.WordFactory; +import com.oracle.svm.core.NeverInline; import com.oracle.svm.core.SubstrateOptions; import com.oracle.svm.core.SubstrateUtil; -import com.oracle.svm.core.NeverInline; -import com.oracle.svm.core.heap.RestrictHeapAccess; import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.c.NonmovableArray; import com.oracle.svm.core.c.NonmovableArrays; import com.oracle.svm.core.deopt.DeoptimizedFrame; import com.oracle.svm.core.deopt.Deoptimizer; import com.oracle.svm.core.deopt.SubstrateInstalledCode; +import com.oracle.svm.core.heap.RestrictHeapAccess; import com.oracle.svm.core.option.RuntimeOptionKey; import com.oracle.svm.core.stack.JavaStackWalker; import com.oracle.svm.core.stack.StackFrameVisitor; @@ -322,6 +322,6 @@ public interface CodeInfoVisitor { * continue, else false. */ @RestrictHeapAccess(access = RestrictHeapAccess.Access.NO_ALLOCATION, reason = "Must not allocate while visiting code.") - boolean visitCode(T codeInfo); + boolean visitCode(CodeInfo codeInfo); } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/RuntimeCodeInfoMemory.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/RuntimeCodeInfoMemory.java index 3529dd3e1665..f05c8600ffcc 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/RuntimeCodeInfoMemory.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/RuntimeCodeInfoMemory.java @@ -201,7 +201,7 @@ private void rehashAfterUnregisterAt(int index) { // from IdentityHashMap: Knuth } } - public boolean walkRuntimeMethodsDuringGC(CodeInfoVisitor visitor) { + public void walkRuntimeMethodsDuringGC(CodeInfoVisitor visitor) { assert VMOperation.isGCInProgress() : "otherwise, we would need to make sure that the CodeInfo is not freeded by the GC"; if (table.isNonNull()) { int length = NonmovableArrays.lengthOf(table); @@ -221,22 +221,30 @@ public boolean walkRuntimeMethodsDuringGC(CodeInfoVisitor visitor) { } } } - return true; } - @Uninterruptible(reason = "Must prevent the GC from freeing the CodeInfo object.") - public boolean walkRuntimeMethodsUninterruptibly(CodeInfoVisitor visitor) { + @Uninterruptible(reason = "Prevent the GC from freeing the CodeInfo object.") + public void walkRuntimeMethods(CodeInfoVisitor visitor) { if (table.isNonNull()) { int length = NonmovableArrays.lengthOf(table); - for (int i = 0; i < length;) { + for (int i = 0; i < length; i++) { UntetheredCodeInfo info = NonmovableArrays.getWord(table, i); if (info.isNonNull()) { - visitor.visitCode(CodeInfoAccess.convert(info)); + Object tether = CodeInfoAccess.acquireTether(info); + try { + callVisitor(visitor, info, tether); + } finally { + CodeInfoAccess.releaseTether(info, tether); + } + assert info == NonmovableArrays.getWord(table, i); } - assert info == NonmovableArrays.getWord(table, i); } } - return true; + } + + @Uninterruptible(reason = "Call the visitor, which may execute interruptible code.", calleeMustBe = false) + private static void callVisitor(CodeInfoVisitor visitor, UntetheredCodeInfo info, Object tether) { + visitor.visitCode(CodeInfoAccess.convert(info, tether)); } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/collections/GrowableArray.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/collections/GrowableArray.java new file mode 100644 index 000000000000..33280b604f77 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/collections/GrowableArray.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2023, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package com.oracle.svm.core.collections; + +import org.graalvm.nativeimage.c.struct.RawField; +import org.graalvm.nativeimage.c.struct.RawStructure; +import org.graalvm.nativeimage.c.type.WordPointer; +import org.graalvm.word.PointerBase; + +/** + * Growable array with word-sized elements. The Word[] is allocated on the C heap. + */ +@RawStructure +public interface GrowableArray extends PointerBase { + @RawField + int getSize(); + + @RawField + void setSize(int value); + + @RawField + int getCapacity(); + + @RawField + void setCapacity(int value); + + @RawField + WordPointer getData(); + + @RawField + void setData(WordPointer value); +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/collections/GrowableArrayAccess.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/collections/GrowableArrayAccess.java new file mode 100644 index 000000000000..77014f006880 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/collections/GrowableArrayAccess.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2023, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package com.oracle.svm.core.collections; + +import org.graalvm.compiler.word.Word; +import org.graalvm.nativeimage.ImageSingletons; +import org.graalvm.nativeimage.c.struct.SizeOf; +import org.graalvm.nativeimage.c.type.WordPointer; +import org.graalvm.nativeimage.impl.UnmanagedMemorySupport; +import org.graalvm.word.UnsignedWord; +import org.graalvm.word.WordFactory; + +import com.oracle.svm.core.headers.LibC; + +public class GrowableArrayAccess { + public static void initialize(GrowableArray array) { + array.setSize(0); + array.setCapacity(0); + array.setData(WordFactory.nullPointer()); + } + + public static boolean add(GrowableArray array, Word element) { + if (array.getSize() == array.getCapacity() && !grow(array)) { + return false; + } + + array.getData().addressOf(array.getSize()).write(element); + array.setSize(array.getSize() + 1); + return true; + } + + public static void freeData(GrowableArray array) { + ImageSingletons.lookup(UnmanagedMemorySupport.class).free(array.getData()); + array.setData(WordFactory.nullPointer()); + array.setSize(0); + array.setCapacity(0); + } + + private static boolean grow(GrowableArray array) { + int newCapacity = array.getCapacity() * 2; + WordPointer oldData = array.getData(); + + UnsignedWord wordSize = SizeOf.unsigned(WordPointer.class); + WordPointer newData = ImageSingletons.lookup(UnmanagedMemorySupport.class).malloc(wordSize.multiply(newCapacity)); + if (newData.isNull()) { + return false; + } + + LibC.memcpy(newData, oldData, wordSize.multiply(array.getSize())); + ImageSingletons.lookup(UnmanagedMemorySupport.class).free(oldData); + + array.setData(newData); + array.setCapacity(newCapacity); + return true; + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/AbstractMemoryMXBean.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/AbstractMemoryMXBean.java index f067c5c3ecc7..0e2d5e97fd33 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/AbstractMemoryMXBean.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/AbstractMemoryMXBean.java @@ -68,7 +68,7 @@ public int getObjectPendingFinalizationCount() { @Override public MemoryUsage getNonHeapMemoryUsage() { codeInfoVisitor.reset(); - RuntimeCodeInfoMemory.singleton().walkRuntimeMethodsUninterruptibly(codeInfoVisitor); + RuntimeCodeInfoMemory.singleton().walkRuntimeMethods(codeInfoVisitor); long used = codeInfoVisitor.getRuntimeCodeInfoSize().rawValue(); return new MemoryUsage(UNDEFINED_MEMORY_USAGE, used, used, UNDEFINED_MEMORY_USAGE); } @@ -104,9 +104,9 @@ public void reset() { runtimeCodeInfoSize = WordFactory.zero(); } - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) @Override - public boolean visitCode(T codeInfo) { + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public boolean visitCode(CodeInfo codeInfo) { runtimeCodeInfoSize = runtimeCodeInfoSize.add(CodeInfoAccess.getCodeAndDataMemorySize(codeInfo)).add(CodeInfoAccess.getNativeMetadataSize(codeInfo)); return true; } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/RuntimeCodeCacheCleaner.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/RuntimeCodeCacheCleaner.java index 00b782993543..af6ca78c2898 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/RuntimeCodeCacheCleaner.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/RuntimeCodeCacheCleaner.java @@ -31,13 +31,12 @@ import com.oracle.svm.core.code.CodeInfoAccess; import com.oracle.svm.core.code.CodeInfoTable; import com.oracle.svm.core.code.RuntimeCodeCache.CodeInfoVisitor; -import com.oracle.svm.core.graal.meta.SharedRuntimeMethod; - -import jdk.vm.ci.meta.SpeculationLog.SpeculationReason; - import com.oracle.svm.core.code.RuntimeCodeInfoAccess; import com.oracle.svm.core.code.RuntimeCodeInfoHistory; import com.oracle.svm.core.code.RuntimeCodeInfoMemory; +import com.oracle.svm.core.graal.meta.SharedRuntimeMethod; + +import jdk.vm.ci.meta.SpeculationLog.SpeculationReason; /** * Cleans the code cache and frees the unmanaged memory that is used by {@link CodeInfo} objects. @@ -62,7 +61,7 @@ public RuntimeCodeCacheCleaner() { } @Override - public boolean visitCode(T codeInfo) { + public boolean visitCode(CodeInfo codeInfo) { if (RuntimeCodeInfoAccess.areAllObjectsOnImageHeap(codeInfo)) { return true; } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/HProfSubRecord.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/HProfSubRecord.java new file mode 100644 index 000000000000..5de899cca585 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/HProfSubRecord.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2023, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package com.oracle.svm.core.heapdump; + +import org.graalvm.compiler.core.common.NumUtil; + +/** See hprofTag in HotSpot. */ +public enum HProfSubRecord { + GC_ROOT_UNKNOWN(0xFF), + GC_ROOT_JNI_GLOBAL(0x01), + GC_ROOT_JNI_LOCAL(0x02), + GC_ROOT_JAVA_FRAME(0x03), + GC_ROOT_NATIVE_STACK(0x04), + GC_ROOT_STICKY_CLASS(0x05), + GC_ROOT_THREAD_BLOCK(0x06), + GC_ROOT_MONITOR_USED(0x07), + GC_ROOT_THREAD_OBJ(0x08), + GC_CLASS_DUMP(0x20), + GC_INSTANCE_DUMP(0x21), + GC_OBJ_ARRAY_DUMP(0x22), + GC_PRIM_ARRAY_DUMP(0x23); + + private final byte value; + + HProfSubRecord(int value) { + this.value = NumUtil.safeToUByte(value); + } + + public byte getValue() { + return value; + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/UnimplementedHeapDumpWriter.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/HProfTopLevelRecord.java similarity index 63% rename from substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/UnimplementedHeapDumpWriter.java rename to substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/HProfTopLevelRecord.java index 9356d05ddb8c..2e5208ca5901 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/UnimplementedHeapDumpWriter.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/HProfTopLevelRecord.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 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 @@ -24,25 +24,32 @@ */ package com.oracle.svm.core.heapdump; -import java.io.FileOutputStream; -import java.io.IOException; +import org.graalvm.compiler.core.common.NumUtil; -import com.oracle.svm.core.util.VMError; +/** See hprofTag in HotSpot. */ +public enum HProfTopLevelRecord { + UTF8(0x01), + LOAD_CLASS(0x02), + UNLOAD_CLASS(0x03), + FRAME(0x04), + TRACE(0x05), + ALLOC_SITES(0x06), + HEAP_SUMMARY(0x07), + START_THREAD(0x0A), + END_THREAD(0x0B), + HEAP_DUMP(0x0C), + CPU_SAMPLES(0x0D), + CONTROL_SETTINGS(0x0E), + HEAP_DUMP_SEGMENT(0x1C), + HEAP_DUMP_END(0x2C); -public final class UnimplementedHeapDumpWriter extends HeapDumpWriter { - private final String message; + private final byte value; - public UnimplementedHeapDumpWriter(String message) { - this.message = message; + HProfTopLevelRecord(int value) { + this.value = NumUtil.safeToUByte(value); } - @Override - public void writeHeapTo(FileOutputStream fileOutputStream, boolean gcBefore) throws IOException { - throw VMError.unimplemented(message); - } - - @Override - public void writeHeapTo(AllocationFreeOutputStream fileOutputStream, boolean gcBefore) throws IOException { - throw VMError.unimplemented(message); + public byte getValue() { + return value; } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/HProfType.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/HProfType.java new file mode 100644 index 000000000000..7c3984b35aef --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/HProfType.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2023, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package com.oracle.svm.core.heapdump; + +import org.graalvm.compiler.core.common.NumUtil; + +import com.oracle.svm.core.config.ConfigurationValues; +import com.oracle.svm.core.util.VMError; + +/** See hprofTag in HotSpot. */ +public enum HProfType { + NORMAL_OBJECT(0x2, 0), + BOOLEAN(0x4, 1), + CHAR(0x5, 2), + FLOAT(0x6, 4), + DOUBLE(0x7, 8), + BYTE(0x8, 1), + SHORT(0x9, 2), + INT(0xA, 4), + LONG(0xB, 8); + + private final byte value; + private final int size; + + HProfType(int value, int size) { + this.value = NumUtil.safeToUByte(value); + this.size = size; + } + + public byte getValue() { + return value; + } + + public int getSize() { + if (size == 0) { + return ConfigurationValues.getTarget().wordSize; + } + return size; + } + + public static HProfType fromStorageKind(byte storageKind) { + return switch (storageKind) { + case StorageKind.OBJECT -> HProfType.NORMAL_OBJECT; + case StorageKind.BOOLEAN -> HProfType.BOOLEAN; + case StorageKind.CHAR -> HProfType.CHAR; + case StorageKind.FLOAT -> HProfType.FLOAT; + case StorageKind.DOUBLE -> HProfType.DOUBLE; + case StorageKind.BYTE -> HProfType.BYTE; + case StorageKind.SHORT -> HProfType.SHORT; + case StorageKind.INT -> HProfType.INT; + case StorageKind.LONG -> HProfType.LONG; + default -> throw VMError.shouldNotReachHere("Unexpected storage kind."); + }; + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/HeapDumpFeature.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/HeapDumpFeature.java deleted file mode 100644 index 0b0336c6014d..000000000000 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/HeapDumpFeature.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (c) 2022, 2022, 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. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * 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. - */ -package com.oracle.svm.core.heapdump; - -import java.io.FileOutputStream; - -import org.graalvm.nativeimage.ImageSingletons; -import org.graalvm.nativeimage.Platform; -import org.graalvm.nativeimage.impl.HeapDumpSupport; - -import com.oracle.svm.core.feature.InternalFeature; -import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature; - -@AutomaticallyRegisteredFeature -public final class HeapDumpFeature implements InternalFeature { - @Override - public void afterRegistration(AfterRegistrationAccess access) { - ImageSingletons.add(HeapDumpSupport.class, new HeapDumpSupportImpl()); - ImageSingletons.add(HeapDumpUtils.class, new HeapDumpUtils()); - if (Platform.includedIn(Platform.WINDOWS.class)) { - ImageSingletons.add(HeapDumpWriter.class, new UnimplementedHeapDumpWriter("Currently not supported for " + ImageSingletons.lookup(Platform.class))); - } else { - ImageSingletons.add(HeapDumpWriter.class, new HeapDumpWriterImpl()); - } - } -} - -final class HeapDumpSupportImpl implements HeapDumpSupport { - @Override - public void dumpHeap(String outputFile, boolean live) throws java.io.IOException { - try (FileOutputStream fileOutputStream = new FileOutputStream(outputFile)) { - HeapDumpWriter.singleton().writeHeapTo(fileOutputStream, live); - } - } -} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/HeapDumpMetadata.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/HeapDumpMetadata.java new file mode 100644 index 000000000000..6ad163be3dbe --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/HeapDumpMetadata.java @@ -0,0 +1,368 @@ +/* + * Copyright (c) 2023, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package com.oracle.svm.core.heapdump; + +import org.graalvm.nativeimage.ImageSingletons; +import org.graalvm.nativeimage.StackValue; +import org.graalvm.nativeimage.c.struct.RawField; +import org.graalvm.nativeimage.c.struct.RawPointerTo; +import org.graalvm.nativeimage.c.struct.RawStructure; +import org.graalvm.nativeimage.c.struct.SizeOf; +import org.graalvm.nativeimage.c.type.CCharPointer; +import org.graalvm.nativeimage.impl.UnmanagedMemorySupport; +import org.graalvm.word.Pointer; +import org.graalvm.word.PointerBase; +import org.graalvm.word.UnsignedWord; +import org.graalvm.word.WordFactory; + +import com.oracle.svm.core.c.NonmovableArrays; +import com.oracle.svm.core.c.struct.PinnedObjectField; +import com.oracle.svm.core.heap.Heap; +import com.oracle.svm.core.heap.ObjectVisitor; +import com.oracle.svm.core.hub.DynamicHub; +import com.oracle.svm.core.util.coder.ByteStream; +import com.oracle.svm.core.util.coder.ByteStreamAccess; +import com.oracle.svm.core.util.coder.NativeCoder; +import com.oracle.svm.core.util.coder.Pack200Coder; + +class HeapDumpMetadata { + private static final ComputeHubDataVisitor COMPUTE_HUB_DATA_VISITOR = new ComputeHubDataVisitor(); + + private static int fieldNameCount; + private static int classInfoCount; + private static ClassInfo classInfos; + private static FieldInfoPointer fieldInfoTable; + private static FieldNamePointer fieldNameTable; + + public static boolean allocate(byte[] metadata) { + assert classInfos.isNull() && fieldInfoTable.isNull() && fieldNameTable.isNull(); + + Pointer start = NonmovableArrays.getArrayBase(NonmovableArrays.fromImageHeap(metadata)); + Pointer end = start.add(metadata.length); + + ByteStream stream = StackValue.get(ByteStream.class); + ByteStreamAccess.initialize(stream, start); + + /* Read the header. */ + int totalFieldCount = NativeCoder.readInt(stream); + int classCount = NativeCoder.readInt(stream); + fieldNameCount = NativeCoder.readInt(stream); + int maxTypeId = Pack200Coder.readUVAsInt(stream); + classInfoCount = maxTypeId + 1; + + /* + * Precompute some data structures so that the heap dumping can access the encoded data more + * efficiently. + */ + UnsignedWord classInfosSize = WordFactory.unsigned(classInfoCount).multiply(SizeOf.get(ClassInfo.class)); + classInfos = ImageSingletons.lookup(UnmanagedMemorySupport.class).calloc(classInfosSize); + if (classInfos.isNull()) { + return false; + } + + UnsignedWord fieldStartsSize = WordFactory.unsigned(totalFieldCount).multiply(SizeOf.get(FieldInfoPointer.class)); + fieldInfoTable = ImageSingletons.lookup(UnmanagedMemorySupport.class).calloc(fieldStartsSize); + if (fieldInfoTable.isNull()) { + return false; + } + + UnsignedWord fieldNameTableSize = WordFactory.unsigned(fieldNameCount).multiply(SizeOf.get(FieldNamePointer.class)); + fieldNameTable = ImageSingletons.lookup(UnmanagedMemorySupport.class).calloc(fieldNameTableSize); + if (fieldNameTable.isNull()) { + return false; + } + + /* Read the classes and fields. */ + int fieldIndex = 0; + for (int i = 0; i < classCount; i++) { + int typeId = Pack200Coder.readUVAsInt(stream); + + ClassInfo classInfo = getClassInfo(typeId); + + int numInstanceFields = Pack200Coder.readUVAsInt(stream); + classInfo.setInstanceFieldCount(numInstanceFields); + + int numStaticFields = Pack200Coder.readUVAsInt(stream); + classInfo.setStaticFieldCount(numStaticFields); + + classInfo.setInstanceFields(fieldInfoTable.addressOf(fieldIndex)); + for (int j = 0; j < numInstanceFields; j++) { + Pointer fieldInfo = (Pointer) fieldInfoTable.addressOf(fieldIndex); + fieldInfo.writeWord(0, stream.getPosition()); + FieldInfoAccess.skipFieldInfo(stream); + fieldIndex++; + } + + classInfo.setStaticFields(fieldInfoTable.addressOf(fieldIndex)); + for (int j = 0; j < numStaticFields; j++) { + Pointer fieldInfo = (Pointer) fieldInfoTable.addressOf(fieldIndex); + fieldInfo.writeWord(0, stream.getPosition()); + FieldInfoAccess.skipFieldInfo(stream); + fieldIndex++; + } + } + + /* Fill the symbol table. */ + for (int i = 0; i < fieldNameCount; i++) { + Pointer fieldName = (Pointer) fieldNameTable.addressOf(i); + fieldName.writeWord(0, stream.getPosition()); + int length = Pack200Coder.readUVAsInt(stream); + stream.setPosition(stream.getPosition().add(length)); + } + assert stream.getPosition().equal(end); + + /* Store the DynamicHubs in their corresponding ClassInfo structs. */ + COMPUTE_HUB_DATA_VISITOR.initialize(); + Heap.getHeap().walkImageHeapObjects(COMPUTE_HUB_DATA_VISITOR); + + /* Compute the size that the instance fields of the classes. */ + for (int i = 0; i < classInfoCount; i++) { + ClassInfo classInfo = HeapDumpMetadata.getClassInfo(i); + if (ClassInfoAccess.isValid(classInfo)) { + computeInstanceFieldsDumpSize(classInfo); + } + } + return true; + } + + public static void free() { + ImageSingletons.lookup(UnmanagedMemorySupport.class).free(classInfos); + classInfos = WordFactory.nullPointer(); + + ImageSingletons.lookup(UnmanagedMemorySupport.class).free(fieldInfoTable); + fieldInfoTable = WordFactory.nullPointer(); + + ImageSingletons.lookup(UnmanagedMemorySupport.class).free(fieldNameTable); + fieldNameTable = WordFactory.nullPointer(); + } + + public static int getClassInfoCount() { + return classInfoCount; + } + + public static ClassInfo getClassInfo(Class clazz) { + return getClassInfo(DynamicHub.fromClass(clazz)); + } + + public static ClassInfo getClassInfo(DynamicHub hub) { + if (hub == null) { + return WordFactory.nullPointer(); + } + return getClassInfo(hub.getTypeID()); + } + + public static ClassInfo getClassInfo(int typeId) { + return classInfos.addressOf(typeId); + } + + public static int getFieldNameCount() { + return fieldNameCount; + } + + public static FieldNamePointer getFieldNameTable() { + return fieldNameTable; + } + + /** + * Get the size in bytes that is required for the fields of the given class when an object of + * that class is written to the heap dump file. Note that this is not the same as the object + * size in the heap. The size in the heap will include the object header, padding/alignment, and + * the size of object references may be different. + */ + private static int computeInstanceFieldsDumpSize(ClassInfo classInfo) { + /* Check if this class was already processed earlier. */ + if (classInfo.getInstanceFieldsDumpSize() != -1) { + return classInfo.getInstanceFieldsDumpSize(); + } + + /* Compute the size of the immediate fields. */ + int result = computeFieldsDumpSize(classInfo.getInstanceFields(), classInfo.getInstanceFieldCount()); + + /* Add the size of all inherited fields. */ + DynamicHub superHub = classInfo.getHub().getSuperHub(); + if (superHub != null) { + result += computeInstanceFieldsDumpSize(HeapDumpMetadata.getClassInfo(superHub)); + } + classInfo.setInstanceFieldsDumpSize(result); + return result; + } + + static int computeFieldsDumpSize(FieldInfoPointer fields, int fieldCount) { + int result = 0; + for (int i = 0; i < fieldCount; i++) { + FieldInfo field = fields.addressOf(i).read(); + byte storageKind = FieldInfoAccess.getStorageKind(field); + result += StorageKind.getSize(storageKind); + } + return result; + } + + @RawStructure + public interface ClassInfo extends PointerBase { + @RawField + @PinnedObjectField + DynamicHub getHub(); + + @RawField + @PinnedObjectField + void setHub(DynamicHub value); + + @RawField + int getSerialNum(); + + @RawField + void setSerialNum(int value); + + @RawField + int getInstanceFieldsDumpSize(); + + @RawField + void setInstanceFieldsDumpSize(int value); + + @RawField + int getInstanceFieldCount(); + + @RawField + void setInstanceFieldCount(int value); + + @RawField + FieldInfoPointer getInstanceFields(); + + @RawField + void setInstanceFields(FieldInfoPointer value); + + @RawField + int getStaticFieldCount(); + + @RawField + void setStaticFieldCount(int value); + + @RawField + FieldInfoPointer getStaticFields(); + + @RawField + void setStaticFields(FieldInfoPointer value); + + ClassInfo addressOf(int index); + } + + public static class ClassInfoAccess { + static boolean isValid(ClassInfo classInfo) { + return classInfo.getHub() != null; + } + } + + @RawStructure + public interface FieldInfo extends PointerBase { + // u1 storageKind + // uv fieldNameIndex + // uv location + } + + public static class FieldInfoAccess { + static byte getStorageKind(FieldInfo field) { + return ((Pointer) field).readByte(0); + } + + static FieldName getFieldName(FieldInfo field) { + int fieldNameIndex = Pack200Coder.readUVAsInt(getFieldNameIndexAddress(field)); + return HeapDumpMetadata.getFieldNameTable().addressOf(fieldNameIndex).read(); + } + + static int getLocation(FieldInfo field) { + Pointer fieldNameIndex = getFieldNameIndexAddress(field); // skip storageKind + ByteStream stream = StackValue.get(ByteStream.class); + ByteStreamAccess.initialize(stream, fieldNameIndex); + Pack200Coder.readUVAsInt(stream); // skip fieldNameIndex + return Pack200Coder.readUVAsInt(stream); + } + + static void skipFieldInfo(ByteStream stream) { + NativeCoder.readByte(stream); // storage kind + Pack200Coder.readUVAsInt(stream); // field name index + Pack200Coder.readUVAsInt(stream); // location + } + + private static Pointer getFieldNameIndexAddress(FieldInfo field) { + return ((Pointer) field).add(1); + } + } + + @RawPointerTo(FieldInfo.class) + public interface FieldInfoPointer extends PointerBase { + FieldInfoPointer addressOf(int index); + + FieldInfo read(); + } + + /** Data structure has a variable size. */ + @RawStructure + public interface FieldName extends PointerBase { + // uv lengthInBytes + // (s1 utf8 character)* + } + + public static class FieldNameAccess { + static int getLength(FieldName fieldName) { + return Pack200Coder.readUVAsInt((Pointer) fieldName); + } + + static CCharPointer getChars(FieldName fieldName) { + ByteStream data = StackValue.get(ByteStream.class); + ByteStreamAccess.initialize(data, (Pointer) fieldName); + Pack200Coder.readUV(data); // skip length + return (CCharPointer) data.getPosition(); + } + } + + @RawPointerTo(FieldName.class) + public interface FieldNamePointer extends PointerBase { + FieldNamePointer addressOf(int index); + + FieldName read(); + } + + private static class ComputeHubDataVisitor implements ObjectVisitor { + private int classSerialNum; + + public void initialize() { + this.classSerialNum = 0; + } + + @Override + public boolean visitObject(Object o) { + if (o instanceof DynamicHub hub) { + ClassInfo classInfo = HeapDumpMetadata.getClassInfo(hub.getTypeID()); + if (classInfo.getHub() == null) { + /* Initialize the relevant data for all classes that don't declare any fields. */ + classInfo.setHub(hub); + classInfo.setSerialNum(++classSerialNum); + classInfo.setInstanceFieldsDumpSize(-1); + } + } + return true; + } + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/HeapDumpSupportImpl.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/HeapDumpSupportImpl.java new file mode 100644 index 000000000000..b3dbbb5bd3bd --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/HeapDumpSupportImpl.java @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2023, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package com.oracle.svm.core.heapdump; + +import static com.oracle.svm.core.heap.RestrictHeapAccess.Access.NO_ALLOCATION; + +import java.io.IOException; + +import org.graalvm.compiler.api.replacements.Fold; +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; +import org.graalvm.nativeimage.StackValue; +import org.graalvm.nativeimage.c.struct.RawField; +import org.graalvm.nativeimage.c.struct.RawStructure; +import org.graalvm.nativeimage.c.struct.SizeOf; +import org.graalvm.nativeimage.impl.HeapDumpSupport; +import org.graalvm.word.Pointer; +import org.graalvm.word.WordFactory; + +import com.oracle.svm.core.UnmanagedMemoryUtil; +import com.oracle.svm.core.heap.RestrictHeapAccess; +import com.oracle.svm.core.heap.VMOperationInfos; +import com.oracle.svm.core.os.RawFileOperationSupport; +import com.oracle.svm.core.os.RawFileOperationSupport.FileCreationMode; +import com.oracle.svm.core.thread.NativeVMOperation; +import com.oracle.svm.core.thread.NativeVMOperationData; +import com.oracle.svm.core.thread.VMOperation; + +public class HeapDumpSupportImpl implements HeapDumpSupport { + private final HeapDumpWriter writer; + private final HeapDumpOperation heapDumpOperation; + + @Platforms(Platform.HOSTED_ONLY.class) + public HeapDumpSupportImpl() { + this.writer = new HeapDumpWriter(); + this.heapDumpOperation = new HeapDumpOperation(); + } + + @Platforms(Platform.HOSTED_ONLY.class) + public void setMetadata(byte[] value) { + this.writer.setMetadata(value); + } + + @Override + public void dumpHeap(String outputFile, boolean live) throws IOException { + writeHeapTo(outputFile, live); + } + + public void writeHeapTo(String filename, boolean gcBefore) throws IOException { + RawFileOperationSupport.RawFileDescriptor fd = getFileSupport().create(filename, FileCreationMode.CREATE_OR_REPLACE, RawFileOperationSupport.FileAccessMode.READ_WRITE); + if (!getFileSupport().isValid(fd)) { + throw new IOException("Could not create the heap dump file: " + filename); + } + + try { + int size = SizeOf.get(HeapDumpVMOperationData.class); + HeapDumpVMOperationData data = StackValue.get(size); + UnmanagedMemoryUtil.fill((Pointer) data, WordFactory.unsigned(size), (byte) 0); + + data.setGCBefore(gcBefore); + data.setRawFileDescriptor(fd); + heapDumpOperation.enqueue(data); + + if (!data.getSuccess()) { + throw new IOException("An error occurred while writing the heap dump."); + } + } finally { + getFileSupport().close(fd); + } + } + + @Fold + static RawFileOperationSupport getFileSupport() { + return RawFileOperationSupport.bigEndian(); + } + + @RawStructure + private interface HeapDumpVMOperationData extends NativeVMOperationData { + @RawField + boolean getGCBefore(); + + @RawField + void setGCBefore(boolean value); + + @RawField + RawFileOperationSupport.RawFileDescriptor getRawFileDescriptor(); + + @RawField + void setRawFileDescriptor(RawFileOperationSupport.RawFileDescriptor fd); + + @RawField + boolean getSuccess(); + + @RawField + void setSuccess(boolean value); + } + + private class HeapDumpOperation extends NativeVMOperation { + @Platforms(Platform.HOSTED_ONLY.class) + HeapDumpOperation() { + super(VMOperationInfos.get(HeapDumpOperation.class, "Write heap dump", VMOperation.SystemEffect.SAFEPOINT)); + } + + @Override + @RestrictHeapAccess(access = NO_ALLOCATION, reason = "Heap dumping must not allocate.") + protected void operate(NativeVMOperationData d) { + HeapDumpVMOperationData data = (HeapDumpVMOperationData) d; + if (data.getGCBefore()) { + System.gc(); + } + boolean success = writer.dumpHeap(data.getRawFileDescriptor()); + data.setSuccess(success); + } + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/HeapDumpUtils.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/HeapDumpUtils.java deleted file mode 100644 index 752c9f25e9eb..000000000000 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/HeapDumpUtils.java +++ /dev/null @@ -1,347 +0,0 @@ -/* - * Copyright (c) 2022, 2022, 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. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * 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. - */ -package com.oracle.svm.core.heapdump; - -import static com.oracle.svm.core.snippets.KnownIntrinsics.readCallerStackPointer; - -import org.graalvm.compiler.word.Word; -import org.graalvm.nativeimage.CurrentIsolate; -import org.graalvm.nativeimage.ImageSingletons; -import org.graalvm.nativeimage.IsolateThread; -import org.graalvm.nativeimage.Platform; -import org.graalvm.nativeimage.Platforms; -import org.graalvm.nativeimage.c.function.CodePointer; -import org.graalvm.word.Pointer; -import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordFactory; - -import com.oracle.svm.core.NeverInline; -import com.oracle.svm.core.SubstrateOptions; -import com.oracle.svm.core.code.CodeInfo; -import com.oracle.svm.core.code.CodeInfoTable; -import com.oracle.svm.core.deopt.DeoptimizedFrame; -import com.oracle.svm.core.heap.Heap; -import com.oracle.svm.core.heap.ObjectReferenceVisitor; -import com.oracle.svm.core.heap.ObjectVisitor; -import com.oracle.svm.core.heap.RestrictHeapAccess; -import com.oracle.svm.core.heap.UnknownObjectField; -import com.oracle.svm.core.heap.VMOperationInfos; -import com.oracle.svm.core.hub.DynamicHub; -import com.oracle.svm.core.hub.InteriorObjRefWalker; -import com.oracle.svm.core.hub.LayoutEncoding; -import com.oracle.svm.core.snippets.KnownIntrinsics; -import com.oracle.svm.core.stack.JavaStackWalker; -import com.oracle.svm.core.stack.StackFrameVisitor; -import com.oracle.svm.core.thread.JavaVMOperation; -import com.oracle.svm.core.thread.VMThreads; - -/** A collection of utilities that might assist heap dumps. */ -public class HeapDumpUtils { - - @UnknownObjectField(types = {byte[].class}) private byte[] fieldsMap; - - /** Extra methods for testing. */ - private final TestingBackDoor testingBackDoor; - - /** Constructor. */ - HeapDumpUtils() { - this.testingBackDoor = new TestingBackDoor(this); - } - - /** Accessor for the singleton. */ - public static HeapDumpUtils getHeapDumpUtils() { - return ImageSingletons.lookup(HeapDumpUtils.class); - } - - @Platforms(Platform.HOSTED_ONLY.class) - public void setFieldsMap(byte[] map) { - fieldsMap = map; - } - - /** - * Walk all the objects in the heap, both the image heap and the garbage collected heap applying - * a visitor to each. - */ - public boolean walkHeapObjects(ObjectVisitor imageHeapVisitor, ObjectVisitor collectedHeapVisitor) { - final WalkHeapObjectsOperation operation = new WalkHeapObjectsOperation(imageHeapVisitor, collectedHeapVisitor); - return walkHeapObjectsWithoutAllocating(operation); - } - - @RestrictHeapAccess(access = RestrictHeapAccess.Access.NO_ALLOCATION, reason = "Iterating the heap must not allocate in the heap.") - boolean walkHeapObjectsWithoutAllocating(WalkHeapObjectsOperation operation) { - operation.enqueue(); - return operation.getResult(); - } - - public int instanceSizeOf(Class cls) { - final int encoding = DynamicHub.fromClass(cls).getLayoutEncoding(); - if (LayoutEncoding.isPureInstance(encoding)) { - /* - * May underestimate the object size if the identity hashcode field is optional. This is - * the best that what can do because the HPROF format does not support that instances of - * one class have different object sizes. - */ - return (int) LayoutEncoding.getPureInstanceAllocationSize(encoding).rawValue(); - } else { - return 0; - } - } - - boolean isPrimitiveArray(Object obj) { - final int encoding = KnownIntrinsics.readHub(obj).getLayoutEncoding(); - return LayoutEncoding.isPrimitiveArray(encoding); - } - - public boolean isJavaPrimitiveArray(Object obj) { - return (isPrimitiveArray(obj) && - ((obj instanceof char[]) || - (obj instanceof byte[]) || - (obj instanceof int[]) || - (obj instanceof long[]) || - (obj instanceof boolean[]) || - (obj instanceof short[]) || - (obj instanceof double[]) || - (obj instanceof float[]))); - } - - /** - * Return a pointer to an object. The result is untracked by the collector: it is treated as an - * integer. - */ - public Pointer objectToPointer(Object obj) { - return Word.objectToUntrackedPointer(obj); - } - - public byte[] getFieldsMap() { - return fieldsMap; - } - - public boolean walkStacks(StacksSlotsVisitor stacksSlotsVisitor) { - final WalkStacksSlotsOperation walkStacksOperation = new WalkStacksSlotsOperation(stacksSlotsVisitor); - walkStacksOperation.enqueue(); - return walkStacksOperation.getResult(); - } - - private static final class WalkHeapObjectsOperation extends JavaVMOperation { - - /* Instance state. */ - private final ObjectVisitor imageHeapVisitor; - private final ObjectVisitor collectedHeapVisitor; - boolean result; - - /** Constructor. */ - WalkHeapObjectsOperation(ObjectVisitor imageHeapVisitor, ObjectVisitor collectedHeapVisitor) { - super(VMOperationInfos.get(WalkHeapObjectsOperation.class, "Walk Java heap for heap dump", SystemEffect.SAFEPOINT)); - this.imageHeapVisitor = imageHeapVisitor; - this.collectedHeapVisitor = collectedHeapVisitor; - } - - @Override - public void operate() { - operateWithoutAllocation(); - } - - @RestrictHeapAccess(access = RestrictHeapAccess.Access.NO_ALLOCATION, reason = "Do not allocate while walking the heap.") - private void operateWithoutAllocation() { - result = Heap.getHeap().walkImageHeapObjects(imageHeapVisitor) && Heap.getHeap().walkCollectedHeapObjects(collectedHeapVisitor); - } - - private boolean getResult() { - return result; - } - } - - private static final class WalkStacksSlotsOperation extends JavaVMOperation { - - private final StacksSlotsVisitor stacksSlotsVisitor; - private boolean result; - - WalkStacksSlotsOperation(StacksSlotsVisitor stacksSlotsVisitor) { - super(VMOperationInfos.get(WalkStacksSlotsOperation.class, "Walk stack for heap dump", SystemEffect.SAFEPOINT)); - this.stacksSlotsVisitor = stacksSlotsVisitor; - } - - @Override - public void operate() { - operateWithoutAllocation(); - } - - @RestrictHeapAccess(access = RestrictHeapAccess.Access.NO_ALLOCATION, reason = "Do not allocate while walking thread stacks.") - private void operateWithoutAllocation() { - result = stacksSlotsVisitor.visitStacksSlots(); - } - - private boolean getResult() { - return result; - } - } - - public abstract static class StacksSlotsVisitor extends StackFrameVisitor implements ObjectReferenceVisitor { - - private IsolateThread currentVMThread; - private Pointer currentStackSP; - private Pointer currentFrameSP; - private CodePointer currentFrameIP; - private DeoptimizedFrame currentDeoptimizedFrame; - - /** Constructor for subclasses. */ - public StacksSlotsVisitor() { - /* Nothing to do. */ - } - - /* - * Access methods for subclasses. - */ - - /** The current VMThread. */ - protected IsolateThread getVMThread() { - return currentVMThread; - } - - /** The stack pointer for the current VMThread. */ - protected Pointer getStackSP() { - return currentStackSP; - } - - /** The stack pointer for the current frame. */ - protected Pointer getFrameSP() { - return currentFrameSP; - } - - /** The instruction pointer for the current frame. */ - protected CodePointer getFrameIP() { - return currentFrameIP; - } - - /** The DeoptimizedFrame for the current frame. */ - protected DeoptimizedFrame getDeoptimizedFrame() { - return currentDeoptimizedFrame; - } - - @NeverInline("Starting a stack walk in the caller frame") - protected boolean visitStacksSlots() { - /* Visit the current thread, because it does not have a JavaFrameAnchor. */ - currentVMThread = CurrentIsolate.getCurrentThread(); - currentStackSP = readCallerStackPointer(); - JavaStackWalker.walkCurrentThread(currentStackSP, this); - if (SubstrateOptions.MultiThreaded.getValue()) { - /* - * Scan the stacks of all the threads. Other threads will be blocked at a safepoint - * (or in native code) so they will each have a JavaFrameAnchor in their VMThread. - */ - for (IsolateThread vmThread = VMThreads.firstThread(); vmThread.isNonNull(); vmThread = VMThreads.nextThread(vmThread)) { - if (vmThread == CurrentIsolate.getCurrentThread()) { - /* - * The current thread is already scanned by code above, so we do not have to - * do anything for it here. It might have a JavaFrameAnchor from earlier - * Java-to-C transitions, but certainly not at the top of the stack since it - * is running this code, so just this scan would be incomplete. - */ - continue; - } - currentVMThread = vmThread; - currentStackSP = WordFactory.nullPointer(); - currentFrameSP = WordFactory.nullPointer(); - currentFrameIP = WordFactory.nullPointer(); - currentDeoptimizedFrame = null; - JavaStackWalker.walkThread(vmThread, this); - } - } - return true; - } - - @Override - public boolean visitFrame(Pointer sp, CodePointer ip, CodeInfo codeInfo, DeoptimizedFrame deoptimizedFrame) { - /* Notice a change in thread. */ - if (currentStackSP.isNull()) { - currentStackSP = sp; - } - currentFrameSP = sp; - currentFrameIP = ip; - currentDeoptimizedFrame = deoptimizedFrame; - return CodeInfoTable.visitObjectReferences(sp, ip, codeInfo, deoptimizedFrame, this); - } - } - - public TestingBackDoor getTestingBackDoor() { - return testingBackDoor; - } - - /** Expose some {@link HeapDumpUtils} methods for testing. */ - public static class TestingBackDoor { - - /** The HeapDumpUtils instance to use. */ - private final HeapDumpUtils heapDumpUtils; - - /** Constructor. */ - TestingBackDoor(HeapDumpUtils utils) { - this.heapDumpUtils = utils; - } - - public byte[] getFieldsMap() { - return heapDumpUtils.getFieldsMap(); - } - - public int instanceSizeOf(Class cls) { - return heapDumpUtils.instanceSizeOf(cls); - } - - public boolean isJavaPrimitiveArray(Object obj) { - return heapDumpUtils.isJavaPrimitiveArray(obj); - } - - public boolean isPrimitiveArray(Object obj) { - return heapDumpUtils.isPrimitiveArray(obj); - } - - public Pointer objectToPointer(Object obj) { - return heapDumpUtils.objectToPointer(obj); - } - - public long sizeOf(Object obj) { - final long result; - if (obj == null) { - result = 0; - } else { - final UnsignedWord objectSize = LayoutEncoding.getMomentarySizeFromObject(obj); - result = objectSize.rawValue(); - } - return result; - } - - public boolean walkHeapObjects(ObjectVisitor imageHeapVisitor, ObjectVisitor collectedHeapVisitor) { - return heapDumpUtils.walkHeapObjects(imageHeapVisitor, collectedHeapVisitor); - } - - @RestrictHeapAccess(access = RestrictHeapAccess.Access.NO_ALLOCATION, reason = "Iterating the heap must not allocate in the heap.") - public boolean walkInteriorReferences(Object obj, ObjectReferenceVisitor visitor) { - return InteriorObjRefWalker.walkObject(obj, visitor); - } - - public boolean walkStacks(StacksSlotsVisitor stacksSlotsVisitor) { - return heapDumpUtils.walkStacks(stacksSlotsVisitor); - } - } -} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/HeapDumpWriter.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/HeapDumpWriter.java index 84449a1c073a..05a056959fc5 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/HeapDumpWriter.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/HeapDumpWriter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 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 @@ -24,32 +24,1147 @@ */ package com.oracle.svm.core.heapdump; -import java.io.FileOutputStream; -import java.io.IOException; +import static com.oracle.svm.core.heap.RestrictHeapAccess.Access.NO_ALLOCATION; -import org.graalvm.nativeimage.ImageSingletons; +import org.graalvm.compiler.api.replacements.Fold; +import org.graalvm.compiler.core.common.NumUtil; +import org.graalvm.compiler.nodes.java.ArrayLengthNode; +import org.graalvm.compiler.word.ObjectAccess; +import org.graalvm.compiler.word.Word; +import org.graalvm.nativeimage.CurrentIsolate; +import org.graalvm.nativeimage.IsolateThread; +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; +import org.graalvm.nativeimage.StackValue; +import org.graalvm.nativeimage.c.function.CodePointer; +import org.graalvm.nativeimage.c.type.WordPointer; +import org.graalvm.word.Pointer; +import org.graalvm.word.UnsignedWord; +import org.graalvm.word.WordBase; +import org.graalvm.word.WordFactory; + +import com.oracle.svm.core.NeverInline; +import com.oracle.svm.core.StaticFieldsSupport; +import com.oracle.svm.core.SubstrateOptions; +import com.oracle.svm.core.c.NonmovableArray; +import com.oracle.svm.core.code.CodeInfo; +import com.oracle.svm.core.code.CodeInfoAccess; +import com.oracle.svm.core.code.CodeInfoDecoder; +import com.oracle.svm.core.code.CodeInfoTable; +import com.oracle.svm.core.code.FrameInfoQueryResult; +import com.oracle.svm.core.code.RuntimeCodeCache; +import com.oracle.svm.core.code.RuntimeCodeInfoAccess; +import com.oracle.svm.core.code.RuntimeCodeInfoMemory; +import com.oracle.svm.core.code.SimpleCodeInfoQueryResult; +import com.oracle.svm.core.collections.GrowableArray; +import com.oracle.svm.core.collections.GrowableArrayAccess; +import com.oracle.svm.core.config.ConfigurationValues; +import com.oracle.svm.core.deopt.DeoptimizedFrame; +import com.oracle.svm.core.heap.CodeReferenceMapDecoder; +import com.oracle.svm.core.heap.Heap; +import com.oracle.svm.core.heap.NoAllocationVerifier; +import com.oracle.svm.core.heap.ObjectReferenceVisitor; +import com.oracle.svm.core.heap.ObjectVisitor; +import com.oracle.svm.core.heap.ReferenceAccess; +import com.oracle.svm.core.heap.ReferenceMapIndex; +import com.oracle.svm.core.heap.RestrictHeapAccess; +import com.oracle.svm.core.heap.UnknownObjectField; +import com.oracle.svm.core.heapdump.HeapDumpMetadata.ClassInfo; +import com.oracle.svm.core.heapdump.HeapDumpMetadata.ClassInfoAccess; +import com.oracle.svm.core.heapdump.HeapDumpMetadata.FieldInfo; +import com.oracle.svm.core.heapdump.HeapDumpMetadata.FieldInfoAccess; +import com.oracle.svm.core.heapdump.HeapDumpMetadata.FieldInfoPointer; +import com.oracle.svm.core.heapdump.HeapDumpMetadata.FieldName; +import com.oracle.svm.core.heapdump.HeapDumpMetadata.FieldNameAccess; +import com.oracle.svm.core.heapdump.HeapDumpMetadata.FieldNamePointer; +import com.oracle.svm.core.hub.DynamicHub; +import com.oracle.svm.core.hub.LayoutEncoding; +import com.oracle.svm.core.log.Log; +import com.oracle.svm.core.os.BufferedFileOperationSupport; +import com.oracle.svm.core.os.BufferedFileOperationSupport.BufferedFile; +import com.oracle.svm.core.os.RawFileOperationSupport.RawFileDescriptor; +import com.oracle.svm.core.snippets.KnownIntrinsics; +import com.oracle.svm.core.stack.JavaStackWalker; +import com.oracle.svm.core.stack.StackFrameVisitor; +import com.oracle.svm.core.thread.PlatformThreads; +import com.oracle.svm.core.thread.ThreadingSupportImpl; +import com.oracle.svm.core.thread.VMOperation; +import com.oracle.svm.core.thread.VMThreads; +import com.oracle.svm.core.threadlocal.VMThreadLocalMTSupport; +import com.oracle.svm.core.util.VMError; + +/** + * This class dumps the image heap and the Java heap into a file (HPROF binary format), similar to + * the HotSpot class {@code heapDumper}. The heap dumping needs additional metadata that is encoded + * during the image build and that is stored in the image heap in a compact binary format (see + * {@code HeapDumpFeature}). + * + * The actual heap dumping logic is executed in a VM operation, so it is safe to iterate over all + * live threads and to, for example, access their thread local values. The heap dumper is + * implemented as a singleton and only a single heap dumping operation can be in progress at a given + * time. + * + * The heap dumping code must not modify the Java heap in any way as this would pollute or corrupt + * the heap dump. Therefore, no Java datastructures can be used and all data structures must be + * allocated using native memory instead. This also guarantees that the heap dumping works in case + * that Native Image is completely out of Java heap memory (see option + * {@code -XX:HeapDumpOnOutOfMemoryError}). + * + * The heap dump is stored in the HPROF binary format (big endian byte order). The high-level + * structure of the file is roughly as follows: + * + *
+ *   file header
+ *   ([top level record] [sub record]*)+
+ *   end marker
+ * 
+ * + * Other relevant file format aspects: + *
    + *
  • References to objects or symbols are encoded using their word-sized address. This is done + * regardless if compressed references are enabled or not.
  • + *
  • Symbols such as class or method names are encoded as UTF8.
  • + *
  • The size of individual records is limited to {@code MAX_UINT}. So, very large arrays that + * have a larger size than {@code MAX_UINT} need to be truncated.
  • + *
+ */ +public class HeapDumpWriter { + private static final int DUMMY_STACK_TRACE_ID = 1; + private static final int LARGE_OBJECT_THRESHOLD = 1 * 1024 * 1024; + private static final int HEAP_DUMP_SEGMENT_TARGET_SIZE = 1 * 1024 * 1024; + + private final NoAllocationVerifier noAllocationVerifier = NoAllocationVerifier.factory("HeapDumpWriter", false); + private final DumpStackFrameVisitor dumpStackFrameVisitor = new DumpStackFrameVisitor(); + private final DumpObjectsVisitor dumpObjectsVisitor = new DumpObjectsVisitor(); + private final CodeMetadataVisitor codeMetadataVisitor = new CodeMetadataVisitor(); + private final ThreadLocalVisitor threadLocalVisitor = new ThreadLocalVisitor(); + @UnknownObjectField(types = {byte[].class}) private byte[] metadata; + + private BufferedFile f; + private long topLevelRecordBegin = -1; + private long subRecordBegin = -1; + private boolean error; + + @Platforms(Platform.HOSTED_ONLY.class) + public HeapDumpWriter() { + } + + @Platforms(Platform.HOSTED_ONLY.class) + public void setMetadata(byte[] value) { + this.metadata = value; + } + + public boolean dumpHeap(RawFileDescriptor fd) { + assert VMOperation.isInProgressAtSafepoint(); + assert ThreadingSupportImpl.isRecurringCallbackPaused(); + noAllocationVerifier.open(); + try { + Heap.getHeap().suspendAllocation(); + return dumpHeap0(fd); + } finally { + noAllocationVerifier.close(); + } + } + + private boolean dumpHeap0(RawFileDescriptor fd) { + boolean initialized = initialize(fd); + try { + if (initialized) { + return writeHeapDump(); + } else { + Log.log().string("An error occurred while initializing the heap dump infrastructure. No heap data will be dumped.").newline(); + return false; + } + } finally { + reset(); + } + } + + private boolean initialize(RawFileDescriptor fd) { + assert topLevelRecordBegin == -1 && subRecordBegin == -1 && !error; + this.f = getFileSupport().allocate(fd); + if (f.isNull()) { + return false; + } + return HeapDumpMetadata.allocate(metadata); + } + + private void reset() { + getFileSupport().free(f); + this.f = WordFactory.nullPointer(); + this.topLevelRecordBegin = -1; + this.subRecordBegin = -1; + this.error = false; + HeapDumpMetadata.free(); + } + + @NeverInline("Starting a stack walk in the caller frame.") + private boolean writeHeapDump() { + Pointer currentThreadSp = KnownIntrinsics.readCallerStackPointer(); + + writeHeader(); + writeClassNames(); // UTF-8 symbols + writeFieldNames(); // UTF-8 symbols + writeLoadClassRecords(); // LOAD_CLASS + writeStackTraces(currentThreadSp); // FRAME and TRACE + + /* 1..n HEAP_DUMP_SEGMENT records */ + startTopLevelRecord(HProfTopLevelRecord.HEAP_DUMP_SEGMENT); + writeClasses(); // GC_CLASS_DUMP + writeThreads(currentThreadSp); // GC_ROOT_THREAD_OBJ, GC_ROOT_JAVA_FRAME, GC_ROOT_JNI_LOCAL + writeJNIGlobals(); // GC_ROOT_JNI_GLOBAL + writeStickyClasses(); // GC_ROOT_STICKY_CLASS + writeObjects(); // GC_INSTANCE_DUMP, GC_OBJ_ARRAY_DUMP, GC_PRIM_ARRAY_DUMP + endTopLevelRecord(); + + startTopLevelRecord(HProfTopLevelRecord.HEAP_DUMP_END); + endTopLevelRecord(); + + flush(); + + if (error) { + Log.log().string("An error occurred while writing the heap dump data. The data in the heap dump file may be corrupt.").newline(); + return false; + } + return true; + } + + private void writeHeader() { + writeUTF8("JAVA PROFILE 1.0.2"); + writeByte((byte) 0); + writeInt(getWordSize()); + writeLong(System.currentTimeMillis()); + } + + private void startTopLevelRecord(HProfTopLevelRecord tag) { + assert topLevelRecordBegin == -1; + writeByte(tag.getValue()); + writeInt(0); // timestamp + writeInt(0); // length (patched later on) + topLevelRecordBegin = getPosition(); + } + + private void endTopLevelRecord() { + assert topLevelRecordBegin >= 0; + long currentPosition = getPosition(); + setPosition(topLevelRecordBegin - Integer.BYTES); + writeInt(NumUtil.safeToUInt(currentPosition - topLevelRecordBegin)); + setPosition(currentPosition); + topLevelRecordBegin = -1; + } + + private void startSubRecord(HProfSubRecord tag, long size) { + assert topLevelRecordBegin >= 0 : "must be within a HEAP_DUMP_SEGMENT"; + long heapDumpSegmentSize = getPosition() - topLevelRecordBegin; + if (heapDumpSegmentSize > 0 && heapDumpSegmentSize + size > HEAP_DUMP_SEGMENT_TARGET_SIZE) { + endTopLevelRecord(); + startTopLevelRecord(HProfTopLevelRecord.HEAP_DUMP_SEGMENT); + } + + subRecordBegin = getPosition(); + writeByte(tag.getValue()); + } + + private void endSubRecord(long recordSize) { + assert subRecordBegin >= 0; + assert subRecordBegin + recordSize == getPosition(); + subRecordBegin = -1; + } + + private void writeClassNames() { + for (int i = 0; i < HeapDumpMetadata.getClassInfoCount(); i++) { + ClassInfo classInfo = HeapDumpMetadata.getClassInfo(i); + if (ClassInfoAccess.isValid(classInfo)) { + writeSymbol(classInfo.getHub().getName()); + } + } + } + + private void writeSymbol(String value) { + startTopLevelRecord(HProfTopLevelRecord.UTF8); + writeObjectId(value); + writeUTF8(value); + endTopLevelRecord(); + } + + private void writeSymbol(FieldName fieldName) { + startTopLevelRecord(HProfTopLevelRecord.UTF8); + writeFieldNameId(fieldName); + write((Pointer) FieldNameAccess.getChars(fieldName), WordFactory.unsigned(FieldNameAccess.getLength(fieldName))); + endTopLevelRecord(); + } + + private void writeFieldNames() { + FieldNamePointer fieldNameTable = HeapDumpMetadata.getFieldNameTable(); + for (int i = 0; i < HeapDumpMetadata.getFieldNameCount(); i++) { + FieldName fieldName = fieldNameTable.addressOf(i).read(); + writeSymbol(fieldName); + } + } + + private void writeLoadClassRecords() { + for (int i = 0; i < HeapDumpMetadata.getClassInfoCount(); i++) { + ClassInfo classInfo = HeapDumpMetadata.getClassInfo(i); + if (ClassInfoAccess.isValid(classInfo)) { + DynamicHub hub = classInfo.getHub(); + if (hub.isLoaded()) { + startTopLevelRecord(HProfTopLevelRecord.LOAD_CLASS); + writeInt(classInfo.getSerialNum()); + writeClassId(hub); + writeInt(DUMMY_STACK_TRACE_ID); + writeObjectId(hub.getName()); + endTopLevelRecord(); + } + } + } + } + + private void writeStackTraces(Pointer currentThreadSp) { + writeDummyStackTrace(); + + /* Write the stack traces of all threads. */ + long nextFrameId = 1; + int threadSerialNum = 1; + for (IsolateThread isolateThread = VMThreads.firstThread(); isolateThread.isNonNull(); isolateThread = VMThreads.nextThread(isolateThread)) { + /* Write FRAME records. */ + int writtenFrames = dumpStackData(isolateThread, currentThreadSp, threadSerialNum, nextFrameId, false); + + /* Write TRACE record that references the FRAME records. */ + startTopLevelRecord(HProfTopLevelRecord.TRACE); + int stackSerialNum = threadSerialNum + DUMMY_STACK_TRACE_ID; + writeInt(stackSerialNum); + writeInt(threadSerialNum); + writeInt(writtenFrames); + for (int i = 0; i < writtenFrames; i++) { + writeFrameId(nextFrameId++); + } + endTopLevelRecord(); + + threadSerialNum++; + } + } + + private int dumpStackData(IsolateThread isolateThread, Pointer currentThreadSp, int threadSerialNum, long nextFrameId, boolean markGCRoots) { + dumpStackFrameVisitor.initialize(threadSerialNum, nextFrameId, markGCRoots); + if (isolateThread == CurrentIsolate.getCurrentThread()) { + JavaStackWalker.walkCurrentThread(currentThreadSp, dumpStackFrameVisitor); + } else { + JavaStackWalker.walkThread(isolateThread, dumpStackFrameVisitor); + } + return dumpStackFrameVisitor.getWrittenFrames(); + } + + /** Writes an empty TRACE record that can be used in other records. */ + private void writeDummyStackTrace() { + startTopLevelRecord(HProfTopLevelRecord.TRACE); + writeInt(DUMMY_STACK_TRACE_ID); + writeInt(0); // thread serial number + writeInt(0); // number of frames + endTopLevelRecord(); + } + + private void writeClasses() { + for (int i = 0; i < HeapDumpMetadata.getClassInfoCount(); i++) { + ClassInfo classInfo = HeapDumpMetadata.getClassInfo(i); + if (ClassInfoAccess.isValid(classInfo)) { + if (classInfo.getHub().isLoaded()) { + writeClassDumpRecord(classInfo); + } + } + } + } + + private void writeClassDumpRecord(ClassInfo classInfo) { + int wordSize = getWordSize(); + int staticFieldsCount = classInfo.getStaticFieldCount(); + int staticFieldsSize = staticFieldsCount * (wordSize + 1) + HeapDumpMetadata.computeFieldsDumpSize(classInfo.getStaticFields(), classInfo.getStaticFieldCount()); + int instanceFieldsCount = classInfo.getInstanceFieldCount(); + int instanceFieldsSize = instanceFieldsCount * (wordSize + 1); + int recordSize = 1 + wordSize + 4 + 6 * wordSize + 4 + 2 + 2 + staticFieldsSize + 2 + instanceFieldsSize; + + Class clazz = DynamicHub.toClass(classInfo.getHub()); + startSubRecord(HProfSubRecord.GC_CLASS_DUMP, recordSize); + writeClassId(clazz); + writeInt(DUMMY_STACK_TRACE_ID); + writeClassId(clazz.getSuperclass()); + writeObjectId(getClassLoader(clazz)); + writeObjectId(clazz.getSigners()); + writeObjectId(null); // protection domain + writeObjectId(null); // reserved field + writeObjectId(null); // reserved field + writeInt(getObjectSizeInHeap(clazz)); + writeShort((short) 0); // size of constant pool + writeFieldDescriptors(staticFieldsCount, classInfo.getStaticFields(), true); + writeFieldDescriptors(instanceFieldsCount, classInfo.getInstanceFields(), false); + endSubRecord(recordSize); + } + + private void writeThreads(Pointer currentThreadSp) { + long nextFrameId = 1; + int threadSerialNum = 1; + for (IsolateThread isolateThread = VMThreads.firstThread(); isolateThread.isNonNull(); isolateThread = VMThreads.nextThread(isolateThread)) { + int stackTraceSerialNum = threadSerialNum + DUMMY_STACK_TRACE_ID; + /* + * If a thread is not fully initialized yet, then the java.lang.Thread object may still + * be null. In this case, a thread object id of 0 is written, which is fine according to + * the HPROF specification. + */ + Thread thread = PlatformThreads.fromVMThread(isolateThread); + writeThread(thread, threadSerialNum, stackTraceSerialNum); + nextFrameId += dumpStackData(isolateThread, currentThreadSp, threadSerialNum, nextFrameId, true); + + writeThreadLocals(isolateThread, threadSerialNum); + threadSerialNum++; + } + } + + private void writeThread(Thread threadObj, int threadSerialNum, int stackTraceSerialNum) { + int recordSize = 1 + getWordSize() + 4 + 4; + startSubRecord(HProfSubRecord.GC_ROOT_THREAD_OBJ, recordSize); + writeObjectId(threadObj); + writeInt(threadSerialNum); + writeInt(stackTraceSerialNum); + endSubRecord(recordSize); + } + + private void writeThreadLocals(IsolateThread isolateThread, int threadSerialNum) { + if (SubstrateOptions.MultiThreaded.getValue()) { + threadLocalVisitor.initialize(threadSerialNum); + VMThreadLocalMTSupport.singleton().walk(isolateThread, threadLocalVisitor); + } + } + + private void writeJNIGlobals() { + /* All objects that are referenced by the runtime code cache are GC roots. */ + RuntimeCodeInfoMemory.singleton().walkRuntimeMethods(codeMetadataVisitor); + } + + private void writeStickyClasses() { + for (int i = 0; i < HeapDumpMetadata.getClassInfoCount(); i++) { + ClassInfo classInfo = HeapDumpMetadata.getClassInfo(i); + if (ClassInfoAccess.isValid(classInfo)) { + int recordSize = 1 + getWordSize(); + startSubRecord(HProfSubRecord.GC_ROOT_STICKY_CLASS, recordSize); + writeClassId(classInfo.getHub()); + endSubRecord(recordSize); + } + } + } + + private void writeObjects() { + GrowableArray largeImageHeapObjects = StackValue.get(GrowableArray.class); + GrowableArrayAccess.initialize(largeImageHeapObjects); + + dumpObjectsVisitor.initialize(true, largeImageHeapObjects); + Heap.getHeap().walkImageHeapObjects(dumpObjectsVisitor); + + GrowableArray largeCollectedHeapObjects = StackValue.get(GrowableArray.class); + GrowableArrayAccess.initialize(largeCollectedHeapObjects); + + dumpObjectsVisitor.initialize(false, largeCollectedHeapObjects); + Heap.getHeap().walkCollectedHeapObjects(dumpObjectsVisitor); + + /* Large objects are collected and written separately. */ + writeLargeObjects(largeImageHeapObjects, true); + GrowableArrayAccess.freeData(largeImageHeapObjects); + largeImageHeapObjects = WordFactory.nullPointer(); + + writeLargeObjects(largeCollectedHeapObjects, false); + GrowableArrayAccess.freeData(largeCollectedHeapObjects); + largeCollectedHeapObjects = WordFactory.nullPointer(); + } + + private void writeLargeObjects(GrowableArray largeObjects, boolean inImageHeap) { + int count = largeObjects.getSize(); + WordPointer objects = largeObjects.getData(); + + for (int i = 0; i < count; i++) { + Word rawObj = objects.addressOf(i).read(); + writeObject(rawObj.toObject(), inImageHeap); + } + } + + private static ClassLoader getClassLoader(Class clazz) { + Class c = clazz; + while (c.isArray()) { + c = c.getComponentType(); + } + return c.getClassLoader(); + } + + private static int getObjectSizeInHeap(Class cls) { + DynamicHub hub = DynamicHub.fromClass(cls); + int encoding = hub.getLayoutEncoding(); + if (LayoutEncoding.isPureInstance(encoding)) { + /* + * May underestimate the object size if the identity hashcode field is optional. This is + * the best that what can do because the HPROF format does not support that instances of + * one class have different object sizes. + */ + return (int) LayoutEncoding.getPureInstanceAllocationSize(encoding).rawValue(); + } else if (LayoutEncoding.isHybrid(encoding)) { + /* For hybrid objects, return the size of the fields. */ + return LayoutEncoding.getArrayBaseOffsetAsInt(encoding); + } else { + /* Variable size. */ + return 0; + } + } + + private void writeFieldDescriptors(int fieldCount, FieldInfoPointer fieldInfos, boolean staticFields) { + writeShort(NumUtil.safeToUShort(fieldCount)); + for (int i = 0; i < fieldCount; i++) { + FieldInfo field = fieldInfos.addressOf(i).read(); + writeFieldNameId(FieldInfoAccess.getFieldName(field)); + byte typeCode = FieldInfoAccess.getStorageKind(field); + writeType(HProfType.fromStorageKind(typeCode)); + + if (staticFields) { + /* For static fields, write the field value to the heap dump as well. */ + boolean isObjectField = typeCode == StorageKind.OBJECT; + writeField(getStaticFieldData(isObjectField), field); + } + } + } + + private static Object getStaticFieldData(boolean isObjectField) { + if (isObjectField) { + return StaticFieldsSupport.getStaticObjectFields(); + } else { + return StaticFieldsSupport.getStaticPrimitiveFields(); + } + } + + private void writeField(Object obj, FieldInfo field) { + Pointer p = Word.objectToUntrackedPointer(obj); + int location = FieldInfoAccess.getLocation(field); + byte storageKind = FieldInfoAccess.getStorageKind(field); + switch (storageKind) { + case StorageKind.BOOLEAN, StorageKind.BYTE -> writeByte(p.readByte(location)); + case StorageKind.CHAR -> writeChar(p.readChar(location)); + case StorageKind.SHORT -> writeShort(p.readShort(location)); + case StorageKind.INT -> writeInt(p.readInt(location)); + case StorageKind.LONG -> writeLong(p.readLong(location)); + case StorageKind.FLOAT -> writeFloat(p.readFloat(location)); + case StorageKind.DOUBLE -> writeDouble(p.readDouble(location)); + case StorageKind.OBJECT -> writeObjectId(ReferenceAccess.singleton().readObjectAt(p.add(location), true)); + default -> throw VMError.shouldNotReachHere("Unexpected storage kind."); + } + } + + private void writeObject(Object obj, boolean inImageHeap) { + DynamicHub hub = KnownIntrinsics.readHub(obj); + int layoutEncoding = hub.getLayoutEncoding(); + if (LayoutEncoding.isArray(layoutEncoding)) { + if (LayoutEncoding.isPrimitiveArray(layoutEncoding)) { + writePrimitiveArray(obj, layoutEncoding); + } else { + writeObjectArray(obj); + } + } else { + /* + * Hybrid objects are handled here as well. This means that the array part of hybrid + * objects is currently skipped. It would be better to dump the array part as a separate + * array object. + */ + writeInstance(obj); + } + + if (inImageHeap) { + markImageHeapObjectAsGCRoot(obj); + } + + /* + * Ideally, we would model Java monitors as instance fields (they are only reachable if the + * object that owns the monitor is reachable), however that is not possible because the + * synthetic monitor field may overlap or collide with a normal field of a subclass. + * Therefore, we simply mark all monitors as GC roots. + */ + int monitorOffset = hub.getMonitorOffset(); + if (monitorOffset != 0) { + Object monitor = ObjectAccess.readObject(obj, monitorOffset); + if (monitor != null) { + markMonitorAsGCRoot(monitor); + } + } + } + + private void markImageHeapObjectAsGCRoot(Object obj) { + assert Heap.getHeap().isInImageHeap(obj); + markAsGCRoot0(obj); + } + + private void markCodeMetadataAsGCRoot(Object obj) { + markAsGCRoot0(obj); + } -public abstract class HeapDumpWriter { /** - * Writes heap in hprof format to ordinary file. - * - * @param fileOutputStream Underlying file stream to write the bytes to - * @param gcBefore Run GC before dumping the heap. - * @throws IOException + * We use GC_ROOT_JNI_GLOBAL for all concepts that don't exist on HotSpot, e.g., the image heap + * or {@link DeoptimizedFrame}s. */ - public abstract void writeHeapTo(FileOutputStream fileOutputStream, boolean gcBefore) throws IOException; + private void markAsGCRoot0(Object obj) { + int recordSize = 1 + 2 * getWordSize(); + startSubRecord(HProfSubRecord.GC_ROOT_JNI_GLOBAL, recordSize); + writeObjectId(obj); + writeObjectId(null); // global ref ID + endSubRecord(recordSize); + } + + private void markMonitorAsGCRoot(Object monitor) { + int recordSize = 1 + getWordSize(); + startSubRecord(HProfSubRecord.GC_ROOT_MONITOR_USED, recordSize); + writeObjectId(monitor); + endSubRecord(recordSize); + } + + private void markThreadLocalAsGCRoot(Object obj, int threadSerialNum) { + int recordSize = 1 + getWordSize() + 4 + 4; + startSubRecord(HProfSubRecord.GC_ROOT_JNI_LOCAL, recordSize); + writeObjectId(obj); + writeInt(threadSerialNum); + writeInt(-1); // empty stack + endSubRecord(recordSize); + } + + private void writeInstance(Object obj) { + ClassInfo classInfo = HeapDumpMetadata.getClassInfo(obj.getClass()); + int wordSize = getWordSize(); + int instanceFieldsSize = classInfo.getInstanceFieldsDumpSize(); + int recordSize = 1 + wordSize + 4 + wordSize + 4 + instanceFieldsSize; + + startSubRecord(HProfSubRecord.GC_INSTANCE_DUMP, recordSize); + writeObjectId(obj); + writeInt(DUMMY_STACK_TRACE_ID); + writeClassId(obj.getClass()); + writeInt(instanceFieldsSize); + + /* Write the field data. */ + do { + int instanceFieldCount = classInfo.getInstanceFieldCount(); + FieldInfoPointer instanceFields = classInfo.getInstanceFields(); + for (int i = 0; i < instanceFieldCount; i++) { + FieldInfo field = instanceFields.addressOf(i).read(); + writeField(obj, field); + } + classInfo = HeapDumpMetadata.getClassInfo(classInfo.getHub().getSuperHub()); + } while (classInfo.isNonNull()); + + endSubRecord(recordSize); + } + + private void writePrimitiveArray(Object array, int layoutEncoding) { + int arrayBaseOffset = LayoutEncoding.getArrayBaseOffsetAsInt(layoutEncoding); + int elementSize = LayoutEncoding.getArrayIndexScale(layoutEncoding); + + int recordHeaderSize = 1 + getWordSize() + 2 * 4 + 1; + int length = calculateMaxArrayLength(array, elementSize, recordHeaderSize); + long recordSize = recordHeaderSize + ((long) length) * elementSize; + + startSubRecord(HProfSubRecord.GC_PRIM_ARRAY_DUMP, recordSize); + writeObjectId(array); + writeInt(DUMMY_STACK_TRACE_ID); + writeInt(length); + + /* The file is big endian, so we need to read & write the data element-wise. */ + if (array instanceof boolean[]) { + writeType(HProfType.BOOLEAN); + writeU1ArrayData(array, length, arrayBaseOffset); + } else if (array instanceof byte[]) { + writeType(HProfType.BYTE); + writeU1ArrayData(array, length, arrayBaseOffset); + } else if (array instanceof short[]) { + writeType(HProfType.SHORT); + writeU2ArrayData(array, length, arrayBaseOffset); + } else if (array instanceof char[]) { + writeType(HProfType.CHAR); + writeU2ArrayData(array, length, arrayBaseOffset); + } else if (array instanceof int[]) { + writeType(HProfType.INT); + writeU4ArrayData(array, length, arrayBaseOffset); + } else if (array instanceof float[]) { + writeType(HProfType.FLOAT); + writeU4ArrayData(array, length, arrayBaseOffset); + } else if (array instanceof long[]) { + writeType(HProfType.LONG); + writeU8ArrayData(array, length, arrayBaseOffset); + } else if (array instanceof double[]) { + writeType(HProfType.DOUBLE); + writeU8ArrayData(array, length, arrayBaseOffset); + } else { + /* Word arrays are primitive arrays as well */ + assert WordBase.class.isAssignableFrom(array.getClass().getComponentType()); + assert elementSize == getWordSize(); + writeWordArray(array, length, arrayBaseOffset); + } + endSubRecord(recordSize); + } + + private void writeObjectArray(Object array) { + int wordSize = getWordSize(); + int recordHeaderSize = 1 + 2 * 4 + 2 * wordSize; + /* In the heap dump, object array elements are always uncompressed. */ + int length = calculateMaxArrayLength(array, wordSize, recordHeaderSize); + long recordSize = recordHeaderSize + ((long) length) * wordSize; + + startSubRecord(HProfSubRecord.GC_OBJ_ARRAY_DUMP, recordSize); + writeObjectId(array); + writeInt(DUMMY_STACK_TRACE_ID); + writeInt(length); + writeClassId(array.getClass()); + + Object[] data = (Object[]) array; + for (int i = 0; i < length; i++) { + writeObjectId(data[i]); + } + endSubRecord(recordSize); + } + + /* + * Hprof uses an u4 as the record length field, which means we need to truncate arrays that are + * too long. + */ + private static int calculateMaxArrayLength(Object array, int elementSize, int recordHeaderSize) { + int length = ArrayLengthNode.arrayLength(array); + UnsignedWord lengthInBytes = WordFactory.unsigned(length).multiply(elementSize); + UnsignedWord maxBytes = WordFactory.unsigned((1L << 32) - 1).subtract(recordHeaderSize); + + if (lengthInBytes.belowOrEqual(maxBytes)) { + return length; + } + + UnsignedWord newLength = maxBytes.unsignedDivide(elementSize); + Log.log().string("Cannot dump very large arrays. Array is truncated to ").unsigned(newLength).string(" elements.").newline(); + return NumUtil.safeToInt(newLength.rawValue()); + } + + private void writeWordArray(Object array, int length, int arrayBaseOffset) { + if (getWordSize() == 8) { + writeType(HProfType.LONG); + writeU8ArrayData(array, length, arrayBaseOffset); + } else { + assert getWordSize() == 4; + writeType(HProfType.INT); + writeU4ArrayData(array, length, arrayBaseOffset); + } + } + + private void writeU1ArrayData(Object array, int length, int arrayBaseOffset) { + Pointer data = getArrayData(array, arrayBaseOffset); + write(data, WordFactory.unsigned(length)); + } + + private void writeU2ArrayData(Object array, int length, int arrayBaseOffset) { + Pointer cur = getArrayData(array, arrayBaseOffset); + for (int i = 0; i < length; i++) { + writeChar(cur.readChar(0)); + cur = cur.add(2); + } + } + + private void writeU4ArrayData(Object array, int length, int arrayBaseOffset) { + Pointer cur = getArrayData(array, arrayBaseOffset); + for (int i = 0; i < length; i++) { + writeInt(cur.readInt(0)); + cur = cur.add(4); + } + } + + private void writeU8ArrayData(Object array, int length, int arrayBaseOffset) { + Pointer cur = getArrayData(array, arrayBaseOffset); + for (int i = 0; i < length; i++) { + writeLong(cur.readLong(0)); + cur = cur.add(8); + } + } + + private static Pointer getArrayData(Object array, int arrayBaseOffset) { + return Word.objectToUntrackedPointer(array).add(arrayBaseOffset); + } + + private void writeByte(byte value) { + boolean success = getFileSupport().writeByte(f, value); + handleError(success); + } + + private void writeType(HProfType type) { + writeByte(type.getValue()); + } + + private void writeShort(short value) { + boolean success = getFileSupport().writeShort(f, value); + handleError(success); + } + + private void writeChar(char value) { + boolean success = getFileSupport().writeChar(f, value); + handleError(success); + } + + private void writeInt(int value) { + boolean success = getFileSupport().writeInt(f, value); + handleError(success); + } + + private void writeLong(long value) { + boolean success = getFileSupport().writeLong(f, value); + handleError(success); + } + + private void writeFloat(float value) { + boolean success = getFileSupport().writeFloat(f, value); + handleError(success); + } + + private void writeDouble(double value) { + boolean success = getFileSupport().writeDouble(f, value); + handleError(success); + } + + private void writeObjectId(Object obj) { + writeId(Word.objectToUntrackedPointer(obj).rawValue()); + } + + private void writeClassId(Class clazz) { + writeClassId(DynamicHub.fromClass(clazz)); + } + + private void writeClassId(DynamicHub hub) { + /* + * HotSpot writes Class objects only as GC_CLASS_DUMP and never as GC_INSTANCE_DUMP records. + * It also always uses the address of the mirror class as the class id. This has the effect + * that the heap dump only contains a very limited set of information for class objects. + * + * It is handy to have detailed information about the DynamicHub in the heap dump. Ideally, + * we would just write both a GC_CLASS_DUMP and a GC_INSTANCE_DUMP record with the same id + * but that breaks VisualVM in a weird way. Therefore, we are using different ids for the + * GC_CLASS_DUMP and GC_INSTANCE_DUMP records. + */ + Word hubAddress = Word.objectToUntrackedPointer(hub); + if (hubAddress.isNonNull()) { + hubAddress = hubAddress.add(1); + } + writeId(hubAddress.rawValue()); + } + + private void writeId(long value) { + boolean success; + if (getWordSize() == 8) { + success = getFileSupport().writeLong(f, value); + } else { + assert getWordSize() == 4; + success = getFileSupport().writeInt(f, (int) value); + } + handleError(success); + } + + private void writeFieldNameId(FieldName fieldName) { + boolean success = getFileSupport().writeLong(f, fieldName.rawValue()); + handleError(success); + } + + private void writeFrameId(long frameId) { + boolean success = getFileSupport().writeLong(f, frameId); + handleError(success); + } + + private void writeUTF8(String value) { + boolean success = getFileSupport().writeUTF8(f, value); + handleError(success); + } + + private void write(Pointer data, UnsignedWord size) { + boolean success = getFileSupport().write(f, data, size); + handleError(success); + } + + private long getPosition() { + long result = getFileSupport().position(f); + handleError(result >= 0); + return result; + } + + private void setPosition(long newPos) { + boolean success = getFileSupport().seek(f, newPos); + handleError(success); + } + + private void flush() { + boolean success = getFileSupport().flush(f); + handleError(success); + } + + private void handleError(boolean success) { + if (!success) { + error = true; + } + } + + @Fold + static BufferedFileOperationSupport getFileSupport() { + return BufferedFileOperationSupport.bigEndian(); + } + + @Fold + static int getWordSize() { + return ConfigurationValues.getTarget().wordSize; + } /** - * Writes heap in hprof format to output stream, which does not support seeking. + * This class is used for different purposes: + *
    + *
  • Write a {@link HProfTopLevelRecord#FRAME} top-level record for every frame on the + * stack.
  • + *
  • Write a GC_ROOT sub-record for every deoptimized frame and every references that is on + * the stack.
  • + *
* - * @param outputStream Underlying stream to write the bytes to - * @param gcBefore Run GC before dumping the heap. - * @throws IOException + * Unfortunately, it is not possible to write all the information in a single pass because the + * data needs to end up in different HPROF records (top-level vs. sub-records). The data from + * the different passes needs to match though, therefore we use a {@link #markGCRoots field} to + * determine which data should be written. */ - public abstract void writeHeapTo(AllocationFreeOutputStream outputStream, boolean gcBefore) throws IOException; + private class DumpStackFrameVisitor extends StackFrameVisitor implements ObjectReferenceVisitor { + private static final int LINE_NUM_NATIVE_METHOD = -3; + + private final CodeInfoDecoder.FrameInfoCursor frameInfoCursor = new CodeInfoDecoder.FrameInfoCursor(); + + private int threadSerialNum; + private long initialNextFrameId; + private long nextFrameId; + private boolean markGCRoots; + + @Platforms(Platform.HOSTED_ONLY.class) + DumpStackFrameVisitor() { + } + + @SuppressWarnings("hiding") + public void initialize(int threadSerialNum, long nextFrameId, boolean markGCRoots) { + assert nextFrameId > 0; + assert threadSerialNum > 0; + assert nextFrameId > 0; + + this.threadSerialNum = threadSerialNum; + this.initialNextFrameId = nextFrameId; + this.nextFrameId = nextFrameId; + this.markGCRoots = markGCRoots; + } + + public int getWrittenFrames() { + return NumUtil.safeToInt(nextFrameId - initialNextFrameId); + } + + @Override + @RestrictHeapAccess(access = NO_ALLOCATION, reason = "Heap dumping must not allocate.") + protected boolean visitFrame(Pointer sp, CodePointer ip, CodeInfo codeInfo, DeoptimizedFrame deoptimizedFrame) { + if (deoptimizedFrame != null) { + markAsGCRoot(deoptimizedFrame); + + for (DeoptimizedFrame.VirtualFrame frame = deoptimizedFrame.getTopFrame(); frame != null; frame = frame.getCaller()) { + visitFrame(frame.getFrameInfo()); + nextFrameId++; + } + } else { + /* + * All references that are on the stack need to be marked as GC roots. Our + * information is not necessarily precise enough to identify to which Java-level + * stack frame a reference belongs. Therefore, we just dump the data in a way that + * it gets associated with the deepest inlined Java-level stack frame of each + * compilation unit. + */ + markStackValuesAsGCRoots(sp, ip, codeInfo); + + frameInfoCursor.initialize(codeInfo, ip); + while (frameInfoCursor.advance()) { + FrameInfoQueryResult frame = frameInfoCursor.get(); + visitFrame(frame); + nextFrameId++; + } + } + return true; + } - public static HeapDumpWriter singleton() { - return ImageSingletons.lookup(HeapDumpWriter.class); + private void markAsGCRoot(DeoptimizedFrame frame) { + if (markGCRoots) { + markAsGCRoot0(frame); + } + } + + private void markStackValuesAsGCRoots(Pointer sp, CodePointer ip, CodeInfo codeInfo) { + if (markGCRoots) { + SimpleCodeInfoQueryResult queryResult = StackValue.get(SimpleCodeInfoQueryResult.class); + CodeInfoAccess.lookupCodeInfo(codeInfo, CodeInfoAccess.relativeIP(codeInfo, ip), queryResult); + + NonmovableArray referenceMapEncoding = CodeInfoAccess.getStackReferenceMapEncoding(codeInfo); + long referenceMapIndex = queryResult.getReferenceMapIndex(); + if (referenceMapIndex == ReferenceMapIndex.NO_REFERENCE_MAP) { + throw CodeInfoTable.reportNoReferenceMap(sp, ip, codeInfo); + } + CodeReferenceMapDecoder.walkOffsetsFromPointer(sp, referenceMapEncoding, referenceMapIndex, this, null); + } + } + + @Override + public boolean visitObjectReference(Pointer objRef, boolean compressed, Object holderObject) { + assert markGCRoots; + + Object obj = ReferenceAccess.singleton().readObjectAt(objRef, compressed); + if (obj != null) { + int recordSize = 1 + getWordSize() + 4 + 4; + startSubRecord(HProfSubRecord.GC_ROOT_JAVA_FRAME, recordSize); + writeObjectId(obj); + writeInt(threadSerialNum); + /* Position of the stack frame in the stack trace. */ + writeInt(getWrittenFrames()); + } + return true; + } + + private void visitFrame(FrameInfoQueryResult frame) { + if (!markGCRoots) { + /* + * Write all UTF-8 symbols that are needed when writing the frame. Ideally, we would + * de-duplicate the symbols but it is not really crucial to do so. + */ + String methodName = getMethodName(frame); + String sourceFileName = getSourceFileName(frame); + writeSymbol(methodName); + writeSymbol(""); // method signature + writeSymbol(sourceFileName); + + /* Write the FRAME record. */ + ClassInfo classInfo = HeapDumpMetadata.getClassInfo(frame.getSourceClass()); + int lineNumber = getLineNumber(frame); + writeFrame(classInfo.getSerialNum(), lineNumber, methodName, sourceFileName); + } + } + + private void writeFrame(int classSerialNum, int lineNumber, String methodName, String sourceFileName) { + assert !markGCRoots; + + startTopLevelRecord(HProfTopLevelRecord.FRAME); + writeFrameId(nextFrameId); + writeObjectId(methodName); + writeObjectId(""); // method signature + writeObjectId(sourceFileName); + writeInt(classSerialNum); + writeInt(lineNumber); + endTopLevelRecord(); + } + + private String getMethodName(FrameInfoQueryResult frame) { + String methodName = frame.getSourceMethodName(); + if (methodName == null || methodName.isEmpty()) { + methodName = ""; + } + return methodName; + } + + private String getSourceFileName(FrameInfoQueryResult frame) { + String sourceFileName = frame.getSourceFileName(); + if (sourceFileName == null || sourceFileName.isEmpty()) { + sourceFileName = "Unknown Source"; + } + return sourceFileName; + } + + private int getLineNumber(FrameInfoQueryResult frame) { + if (frame.isNativeMethod()) { + return LINE_NUM_NATIVE_METHOD; + } + return frame.getSourceLineNumber(); + } } + private class DumpObjectsVisitor implements ObjectVisitor { + private boolean inImageHeap; + private GrowableArray largeObjects; + + @Platforms(Platform.HOSTED_ONLY.class) + DumpObjectsVisitor() { + } + + @SuppressWarnings("hiding") + public void initialize(boolean inImageHeap, GrowableArray largeObjects) { + this.inImageHeap = inImageHeap; + this.largeObjects = largeObjects; + } + + @Override + public boolean visitObject(Object obj) { + if (isLarge(obj)) { + boolean added = GrowableArrayAccess.add(largeObjects, Word.objectToUntrackedPointer(obj)); + if (!added) { + Log.log().string("Failed to add an element to the large object list. Heap dump will be incomplete.").newline(); + } + return true; + } + + writeObject(obj, inImageHeap); + return true; + } + + private boolean isLarge(Object obj) { + return getObjectSize(obj).aboveThan(LARGE_OBJECT_THRESHOLD); + } + + private UnsignedWord getObjectSize(Object obj) { + int layoutEncoding = KnownIntrinsics.readHub(obj).getLayoutEncoding(); + if (LayoutEncoding.isArray(layoutEncoding)) { + int elementSize; + if (LayoutEncoding.isPrimitiveArray(layoutEncoding)) { + elementSize = LayoutEncoding.getArrayIndexScale(layoutEncoding); + } else { + elementSize = getWordSize(); + } + int length = ArrayLengthNode.arrayLength(obj); + return WordFactory.unsigned(length).multiply(elementSize); + } else { + ClassInfo classInfo = HeapDumpMetadata.getClassInfo(obj.getClass()); + return WordFactory.unsigned(classInfo.getInstanceFieldsDumpSize()); + } + } + } + + private class CodeMetadataVisitor implements RuntimeCodeCache.CodeInfoVisitor, ObjectReferenceVisitor { + @Platforms(Platform.HOSTED_ONLY.class) + CodeMetadataVisitor() { + } + + @Override + public boolean visitCode(CodeInfo info) { + RuntimeCodeInfoAccess.walkObjectFields(info, this); + return true; + } + + @Override + public boolean visitObjectReference(Pointer objRef, boolean compressed, Object holderObject) { + Object obj = ReferenceAccess.singleton().readObjectAt(objRef, compressed); + if (obj != null) { + markCodeMetadataAsGCRoot(obj); + } + return true; + } + } + + private class ThreadLocalVisitor implements ObjectReferenceVisitor { + private int threadSerialNum; + + @Platforms(Platform.HOSTED_ONLY.class) + ThreadLocalVisitor() { + } + + @SuppressWarnings("hiding") + public void initialize(int threadSerialNum) { + this.threadSerialNum = threadSerialNum; + } + + @Override + public boolean visitObjectReference(Pointer objRef, boolean compressed, Object holderObject) { + Object obj = ReferenceAccess.singleton().readObjectAt(objRef, compressed); + if (obj != null) { + markThreadLocalAsGCRoot(obj, threadSerialNum); + } + return true; + } + } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/HeapDumpWriterImpl.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/HeapDumpWriterImpl.java deleted file mode 100644 index b9f239d614f9..000000000000 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/HeapDumpWriterImpl.java +++ /dev/null @@ -1,1759 +0,0 @@ -/* - * Copyright (c) 2022, 2022, 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. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * 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. - */ -package com.oracle.svm.core.heapdump; - -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.lang.management.ManagementFactory; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import org.graalvm.nativeimage.CurrentIsolate; -import org.graalvm.nativeimage.ImageSingletons; -import org.graalvm.nativeimage.IsolateThread; -import org.graalvm.word.Pointer; -import org.graalvm.word.WordBase; - -import com.oracle.svm.core.StaticFieldsSupport; -import com.oracle.svm.core.code.FrameInfoQueryResult; -import com.oracle.svm.core.config.ConfigurationValues; -import com.oracle.svm.core.heap.Heap; -import com.oracle.svm.core.heap.ObjectVisitor; -import com.oracle.svm.core.heap.ReferenceAccess; -import com.oracle.svm.core.heap.VMOperationInfos; -import com.oracle.svm.core.hub.DynamicHub; -import com.oracle.svm.core.stack.JavaStackFrameVisitor; -import com.oracle.svm.core.stack.JavaStackWalker; -import com.oracle.svm.core.thread.JavaVMOperation; -import com.oracle.svm.core.thread.PlatformThreads; -import com.oracle.svm.core.thread.VMOperation; -import com.oracle.svm.core.thread.VMThreads; -import com.oracle.svm.core.util.VMError; - -/* - * This class writes Java heap in hprof binary format. The class is heavily - * influenced by 'HeapHprofBinWriter' implementation. - */ - -/* hprof binary format originally published at: -* -* -* -* header "JAVA PROFILE 1.0.1" or "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. -* u4 high word -* u4 low word number of milliseconds since 0:00 GMT, 1/1/70 -* [record]* a sequence of records. -* -*/ - -/* -* -* Record format: -* -* u1 a TAG denoting the type of the record -* u4 number of *microseconds* since the time stamp in the -* header. (wraps around in a little more than an hour) -* u4 number of bytes *remaining* in the record. Note that -* this number excludes the tag and the length field itself. -* [u1]* BODY of the record (a sequence of bytes) -*/ - -/* -* The following TAGs are supported: -* -* TAG BODY notes -*---------------------------------------------------------- -* HPROF_UTF8 a UTF8-encoded name -* -* id name ID -* [u1]* UTF8 characters (no trailing zero) -* -* HPROF_LOAD_CLASS a newly loaded class -* -* u4 class serial number (> 0) -* id class object ID -* u4 stack trace serial number -* id class name ID -* -* HPROF_UNLOAD_CLASS an unloading class -* -* u4 class serial_number -* -* HPROF_FRAME a Java stack frame -* -* id stack frame ID -* id method name ID -* id method signature ID -* id source file name ID -* u4 class serial number -* i4 line number. >0: normal -* -1: unknown -* -2: compiled method -* -3: native method -* -* HPROF_TRACE a Java stack trace -* -* u4 stack trace serial number -* u4 thread serial number -* u4 number of frames -* [id]* stack frame IDs -* -* -* HPROF_ALLOC_SITES a set of heap allocation sites, obtained after GC -* -* u2 flags 0x0001: incremental vs. complete -* 0x0002: sorted by allocation vs. live -* 0x0004: whether to force a GC -* u4 cutoff ratio -* u4 total live bytes -* u4 total live instances -* u8 total bytes allocated -* u8 total instances allocated -* u4 number of sites that follow -* [u1 is_array: 0: normal object -* 2: object array -* 4: boolean array -* 5: char array -* 6: float array -* 7: double array -* 8: byte array -* 9: short array -* 10: int array -* 11: long array -* u4 class serial number (may be zero during startup) -* u4 stack trace serial number -* u4 number of bytes alive -* u4 number of instances alive -* u4 number of bytes allocated -* u4]* number of instance allocated -* -* HPROF_START_THREAD a newly started thread. -* -* u4 thread serial number (> 0) -* id thread object ID -* u4 stack trace serial number -* id thread name ID -* id thread group name ID -* id thread group parent name ID -* -* HPROF_END_THREAD a terminating thread. -* -* u4 thread serial number -* -* HPROF_HEAP_SUMMARY heap summary -* -* u4 total live bytes -* u4 total live instances -* u8 total bytes allocated -* u8 total instances allocated -* -* HPROF_HEAP_DUMP denote a heap dump -* -* [heap dump sub-records]* -* -* There are four kinds of heap dump sub-records: -* -* u1 sub-record type -* -* HPROF_GC_ROOT_UNKNOWN unknown root -* -* id object ID -* -* HPROF_GC_ROOT_THREAD_OBJ thread object -* -* id thread object ID (may be 0 for a -* thread newly attached through JNI) -* u4 thread sequence number -* u4 stack trace sequence number -* -* HPROF_GC_ROOT_JNI_GLOBAL JNI global ref root -* -* id object ID -* id JNI global ref ID -* -* HPROF_GC_ROOT_JNI_LOCAL JNI local ref -* -* id object ID -* u4 thread serial number -* u4 frame # in stack trace (-1 for empty) -* -* HPROF_GC_ROOT_JAVA_FRAME Java stack frame -* -* id object ID -* u4 thread serial number -* u4 frame # in stack trace (-1 for empty) -* -* HPROF_GC_ROOT_NATIVE_STACK Native stack -* -* id object ID -* u4 thread serial number -* -* HPROF_GC_ROOT_STICKY_CLASS System class -* -* id object ID -* -* HPROF_GC_ROOT_THREAD_BLOCK Reference from thread block -* -* id object ID -* u4 thread serial number -* -* HPROF_GC_ROOT_MONITOR_USED Busy monitor -* -* id object ID -* -* HPROF_GC_CLASS_DUMP dump of a class object -* -* id class object ID -* u4 stack trace serial number -* id super class object ID -* id class loader object ID -* id signers object ID -* id protection domain object ID -* id reserved -* id reserved -* -* u4 instance size (in bytes) -* -* u2 size of constant pool -* [u2, constant pool index, -* ty, type -* 2: object -* 4: boolean -* 5: char -* 6: float -* 7: double -* 8: byte -* 9: short -* 10: int -* 11: long -* vl]* and value -* -* u2 number of static fields -* [id, static field name, -* ty, type, -* vl]* and value -* -* u2 number of inst. fields (not inc. super) -* [id, instance field name, -* ty]* type -* -* HPROF_GC_INSTANCE_DUMP dump of a normal object -* -* id object ID -* u4 stack trace serial number -* id class object ID -* u4 number of bytes that follow -* [vl]* instance field values (class, followed -* by super, super's super ...) -* -* HPROF_GC_OBJ_ARRAY_DUMP dump of an object array -* -* id array object ID -* u4 stack trace serial number -* u4 number of elements -* id array class ID -* [id]* elements -* -* HPROF_GC_PRIM_ARRAY_DUMP dump of a primitive array -* -* id array object ID -* u4 stack trace serial number -* u4 number of elements -* u1 element type -* 4: boolean array -* 5: char array -* 6: float array -* 7: double array -* 8: byte array -* 9: short array -* 10: int array -* 11: long array -* [u1]* elements -* -* HPROF_CPU_SAMPLES a set of sample traces of running threads -* -* u4 total number of samples -* u4 # of traces -* [u4 # of samples -* u4]* stack trace serial number -* -* HPROF_CONTROL_SETTINGS the settings of on/off switches -* -* u4 0x00000001: alloc traces on/off -* 0x00000002: cpu sampling on/off -* 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: -* -* HPROF_HEAP_DUMP_SEGMENT denote a heap dump segment -* -* [heap dump sub-records]* -* The same sub-record types allowed by HPROF_HEAP_DUMP -* -* HPROF_HEAP_DUMP_END denotes the end of a heap dump -* -*/ -public class HeapDumpWriterImpl extends HeapDumpWriter { - - /** - * 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 * 1024 * 1024 * 1024; - - /** - * The approximate size of a heap segment. Used to calculate when to create a new segment. - */ - private static final long HPROF_SEGMENTED_HEAP_DUMP_SEGMENT_SIZE = 1L * 1024 * 1024 * 1024; - - /** - * The approximate size of a heap segment for no seek case. Used to calculate when to create a - * new segment. - */ - private static final int HPROF_NOSEEK_HEAP_DUMP_SEGMENT_SIZE = 1 * 1024 * 1024; - - /** 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. */ - private static final int HPROF_UTF8 = 0x01; - private static final int HPROF_LOAD_CLASS = 0x02; - /* private static final int HPROF_UNLOAD_CLASS = 0x03; */ - private static final int HPROF_FRAME = 0x04; - private static final int HPROF_TRACE = 0x05; - /* private static final int HPROF_ALLOC_SITES = 0x06; */ - /* private static final int HPROF_HEAP_SUMMARY = 0x07; */ - /* private static final int HPROF_START_THREAD = 0x0A; */ - /* private static final int HPROF_END_THREAD = 0x0B; */ - private static final int HPROF_HEAP_DUMP = 0x0C; - /* private static final int HPROF_CPU_SAMPLES = 0x0D; */ - /* private static final int HPROF_CONTROL_SETTINGS = 0x0E; */ - - /* 1.0.2 record types. */ - private static final int HPROF_HEAP_DUMP_SEGMENT = 0x1C; - private static final int HPROF_HEAP_DUMP_END = 0x2C; - - /* Heap dump constants */ - /* Constants in enum HprofGcTag. */ - private static final int HPROF_GC_ROOT_UNKNOWN = 0xFF; - private static final int HPROF_GC_ROOT_JNI_GLOBAL = 0x01; - /* private static final int HPROF_GC_ROOT_JNI_LOCAL = 0x02; */ - /* private static final int HPROF_GC_ROOT_JAVA_FRAME = 0x03; */ - /* private static final int HPROF_GC_ROOT_NATIVE_STACK = 0x04; */ - private static final int HPROF_GC_ROOT_STICKY_CLASS = 0x05; - /* private static final int HPROF_GC_ROOT_THREAD_BLOCK = 0x06; */ - /* private static final int HPROF_GC_ROOT_MONITOR_USED = 0x07; */ - private static final int HPROF_GC_ROOT_THREAD_OBJ = 0x08; - private static final int HPROF_GC_CLASS_DUMP = 0x20; - private static final int HPROF_GC_INSTANCE_DUMP = 0x21; - private static final int HPROF_GC_OBJ_ARRAY_DUMP = 0x22; - private static final int HPROF_GC_PRIM_ARRAY_DUMP = 0x23; - - /* Constants in enum HprofType. */ - private static final int HPROF_NORMAL_OBJECT = 2; - private static final int HPROF_BOOLEAN = 4; - private static final int HPROF_CHAR = 5; - private static final int HPROF_FLOAT = 6; - private static final int HPROF_DOUBLE = 7; - private static final int HPROF_BYTE = 8; - private static final int HPROF_SHORT = 9; - private static final int HPROF_INT = 10; - private static final int HPROF_LONG = 11; - - /* Java type codes. */ - private static final char JVM_SIGNATURE_BOOLEAN = 'Z'; - private static final char JVM_SIGNATURE_CHAR = 'C'; - private static final char JVM_SIGNATURE_BYTE = 'B'; - private static final char JVM_SIGNATURE_SHORT = 'S'; - private static final char JVM_SIGNATURE_INT = 'I'; - private static final char JVM_SIGNATURE_LONG = 'J'; - private static final char JVM_SIGNATURE_FLOAT = 'F'; - private static final char JVM_SIGNATURE_DOUBLE = 'D'; - private static final char JVM_SIGNATURE_ARRAY = '['; - private static final char JVM_SIGNATURE_CLASS = 'L'; - - /* - * We don't have allocation site info. We write a dummy stack trace with this id. - */ - private static final int DUMMY_STACK_TRACE_ID = 1; - /* private static final int EMPTY_FRAME_DEPTH = -1; */ - - private static final Field[] ZERO_FIELD_ARR = new Field[0]; - - /** Pre-allocated exceptions, for throwing from code that must not allocate. */ - private static final RuntimeException heapSegmentSizeOverflowException = new RuntimeException("Heap segment size overflow."); - - private AllocationFreeDataOutputStream out; - private HeapDumpUtils heapDumpUtils; - private Map> fieldsMap; - private ClassToClassDataMap classDataCache; - - /* Added for hprof file format 1.0.2 support. */ - private boolean useSegmentedHeapDump; - private long currentSegmentStart; - private long segmentSize; - - @Override - public void writeHeapTo(AllocationFreeOutputStream dataOutputStream, boolean gcBefore) throws IOException { - initialize(true, HPROF_NOSEEK_HEAP_DUMP_SEGMENT_SIZE); - - WriterOperation writerOperation = new WriterOperation(dataOutputStream, gcBefore, HPROF_NOSEEK_HEAP_DUMP_SEGMENT_SIZE); - writerOperation.enqueue(); - IOException operationException = writerOperation.getException(); - if (operationException != null) { - throw operationException; - } - - /* - * Close the data stream. Needs to be done outside of the VMOperation because it uses - * synchronization. - */ - dataOutputStream.close(); - } - - @Override - public void writeHeapTo(FileOutputStream fileOutputStream, boolean gcBefore) throws IOException { - initialize(ManagementFactory.getMemoryMXBean().getHeapMemoryUsage().getUsed() > HPROF_SEGMENTED_HEAP_DUMP_THRESHOLD, HPROF_SEGMENTED_HEAP_DUMP_SEGMENT_SIZE); - - WriterOperation writerOperation = new WriterOperation(fileOutputStream, gcBefore); - writerOperation.enqueue(); - IOException operationException = writerOperation.getException(); - if (operationException != null) { - throw operationException; - } - - /* - * Close the file stream. Needs to be done outside of the VMOperation because it uses - * synchronization. - */ - fileOutputStream.close(); - } - - @SuppressWarnings("hiding") - private void initialize(boolean useSegmentedHeapDump, long segmentSize) { - this.currentSegmentStart = 0L; - this.useSegmentedHeapDump = useSegmentedHeapDump; - this.segmentSize = segmentSize; - } - - /* This method runs as part of a VMOperation. */ - private void writeTo(AllocationFreeDataOutputStream outputStream, boolean gcBefore) throws IOException { - /* If requested, clean up the heap. */ - if (gcBefore) { - System.gc(); - } - - out = outputStream; - heapDumpUtils = HeapDumpUtils.getHeapDumpUtils(); - - /* hprof bin format header. */ - writeFileHeader(); - - /* Dummy stack trace. */ - writeDummyTrace(); - - List> classList = Heap.getHeap().getLoadedClasses(); - - /* hprof UTF-8 symbols section. */ - writeClassNames(classList); - - byte[] fieldsMapData = heapDumpUtils.getFieldsMap(); - if (fieldsMapData.length == 0) { - throw new IOException("Empty fieldsMap"); - } - fieldsMap = createFieldsMap(fieldsMapData); - - /* HPROF_LOAD_CLASS records for all classes. */ - writeClasses(classList); - - /* write HPROF_FRAME and HPROF_TRACE records */ - dumpStackTraces(); - - /* Write CLASS_DUMP records. */ - writeClassDumpRecords(classList); - - /* Write HEAP_DUMP record. */ - writeInstanceDumpRecords(classList); - - /* get current position to calculate length. */ - long dumpEnd = out.position(); - - /* Calculate length of heap data. */ - long dumpLenLong = (dumpEnd - currentSegmentStart - 4L); - /* Fill in final length. */ - fillInHeapRecordLength(dumpLenLong); - - if (useSegmentedHeapDump) { - /* Write heap segment-end record. */ - out.writeByte((byte) HPROF_HEAP_DUMP_END); - out.writeInt(0); - out.writeInt(0); - } - - /* Flush buffer stream and throw fields away. */ - out.flush(); - out = null; - heapDumpUtils = null; - fieldsMap = null; - classDataCache = null; - } - - private void writeHeapRecordPrologue() throws IOException { - if (currentSegmentStart == 0) { - out.flush(); - /* Write heap data header, depending on heap size use segmented heap format. */ - out.writeByte((byte) (useSegmentedHeapDump ? HPROF_HEAP_DUMP_SEGMENT - : HPROF_HEAP_DUMP)); - out.writeInt(0); - - /* - * Remember position of dump length, we will fixup length later: hprof format requires - * length. - */ - currentSegmentStart = out.position(); - - /* Write dummy length of 0 and we'll fix it later. */ - out.writeInt(0); - } - } - - private void writeHeapRecordEpilogue() throws IOException { - writeHeapRecordEpilogue(0); - } - - private void writeHeapRecordEpilogue(long dumpLenSize) throws IOException { - if (useSegmentedHeapDump) { - /* get current position (plus dumpLenLong) to calculate length. */ - long dumpEnd = out.position() + dumpLenSize; - - /* Calculate length of heap data. */ - long dumpLenLong = dumpEnd - currentSegmentStart - 4L; - if (dumpLenLong >= segmentSize) { - fillInHeapRecordLength(dumpLenLong); - out.flush(); - currentSegmentStart = 0; - } - } - } - - private void fillInHeapRecordLength(long dumpLenLong) throws IOException { - /* Check length boundary, overflow of 4GB could happen but is _very_ unlikely. */ - if (dumpLenLong >= (4L * 1024 * 1024 * 1024)) { - throw heapSegmentSizeOverflowException; - } - - /* Save the current position. */ - long currentPosition = out.position(); - - /* Seek the position to write length. */ - out.position(currentSegmentStart); - - int dumpLen = (int) dumpLenLong; - - /* Write length as integer. */ - out.writeInt(dumpLen); - - /* Reset to previous current position. */ - out.position(currentPosition); - } - - private void writeClassDumpRecords(List> classList) throws IOException { - for (Class cls : classList) { - writeHeapRecordPrologue(); - writeClassDumpRecord(cls); - writeHeapRecordEpilogue(); - } - } - - private void writeInstanceDumpRecords(List> classList) throws IOException { - final StacksSlotsVisitorImpl stackVisitor = new StacksSlotsVisitorImpl(); - final CollectedHeapVisitorImpl collectedVisitor = new CollectedHeapVisitorImpl(); - final ImageHeapVisitorImpl imageVisitor = new ImageHeapVisitorImpl(); - IOException visitorException; - - heapDumpUtils.walkStacks(stackVisitor); - visitorException = stackVisitor.getException(); - if (visitorException != null) { - throw visitorException; - } - - heapDumpUtils.walkHeapObjects(imageVisitor, collectedVisitor); - visitorException = collectedVisitor.getException(); - if (visitorException != null) { - throw visitorException; - } - visitorException = imageVisitor.getException(); - if (visitorException != null) { - throw visitorException; - } - /* Write root sticky class. */ - writeStickyClasses(classList); - - /* write JavaThreads */ - writeJavaThreads(); - } - - private void writeStickyClasses(List> classList) throws IOException { - for (Class cls : classList) { - writeHeapRecordPrologue(); - out.writeByte((byte) HPROF_GC_ROOT_STICKY_CLASS); - writeObjectID(cls); - writeHeapRecordEpilogue(); - } - } - - private void writeImageGCRoot(Object obj) throws IOException { - if (!(obj instanceof Class)) { - writeHeapRecordPrologue(); - out.writeByte((byte) HPROF_GC_ROOT_JNI_GLOBAL); - writeObjectID(obj); - writeObjectID(null); - writeHeapRecordEpilogue(); - } - } - - private void writeUnknownGCRoot(Object obj) throws IOException { - if (obj != null) { - writeHeapRecordPrologue(); - out.writeByte((byte) HPROF_GC_ROOT_UNKNOWN); - writeObjectID(obj); - writeHeapRecordEpilogue(); - } - } - - private void writeJavaThreads() throws IOException { - int threadSerialNum = 1; // Note that the thread serial number range is 1-to-N - - for (IsolateThread vmThread = VMThreads.firstThread(); vmThread.isNonNull(); vmThread = VMThreads.nextThread(vmThread)) { - if (vmThread == CurrentIsolate.getCurrentThread()) { - /* Skip itself */ - continue; - } - - Thread jt = PlatformThreads.fromVMThread(vmThread); - writeJavaThread(jt, threadSerialNum++); - } - } - - private void writeJavaThread(Thread jt, int threadSerialNum) throws IOException { - writeHeapRecordPrologue(); - out.writeByte((byte) HPROF_GC_ROOT_THREAD_OBJ); - writeObjectID(jt); - out.writeInt(threadSerialNum); // thread serial number - out.writeInt(threadSerialNum + DUMMY_STACK_TRACE_ID); // stack trace serial number - writeHeapRecordEpilogue(); - } - - private void writeClass(Class clazz) throws IOException { - /* - * All ordinary Class objects are covered by writeClassDumpRecords and they are in - * classDataCache. - */ - if (classDataCache.get(clazz) == null) { - /* Unknown class. */ - writeInstance(clazz); - } - } - - private void writeClassDumpRecord(Class cls) throws IOException { - out.writeByte((byte) HPROF_GC_CLASS_DUMP); - writeObjectID(cls); - out.writeInt(DUMMY_STACK_TRACE_ID); - writeObjectID(cls.getSuperclass()); - - if (!isArray(cls)) { - writeObjectID(cls.getClassLoader()); - writeObjectID(null); /* Signers. */ - writeObjectID(null); /* Protection domain. */ - writeObjectID(null); /* Reserved field 1. */ - writeObjectID(null); /* Reserved field 2. */ - out.writeInt(heapDumpUtils.instanceSizeOf(cls)); - - /* Ignore constant pool output number of cp entries as zero. */ - out.writeShort((short) 0); - - List declaredFields = getImmediateFields(cls); - int staticFields = 0; - int instanceFields = 0; - for (int i = 0; i < declaredFields.size(); i++) { - Field field = declaredFields.get(i); - if (field.isStatic()) { - staticFields++; - } else { - instanceFields++; - } - } - - /* Dump static field descriptors. */ - writeFieldDescriptors(true, staticFields, declaredFields); - - /* Dump instance field descriptors. */ - writeFieldDescriptors(false, instanceFields, declaredFields); - } else { - /* Array. */ - Class baseClass = getBaseClass(cls); - writeObjectID(baseClass.getClassLoader()); - writeObjectID(null); - writeObjectID(null); - /* Two reserved id fields. */ - writeObjectID(null); - writeObjectID(null); - /* Write zero instance size: instance size is variable for arrays. */ - out.writeInt(0); - /* No constant pool for array klasses. */ - out.writeShort((short) 0); - /* No static fields for array klasses. */ - out.writeShort((short) 0); - /* No instance fields for array klasses. */ - out.writeShort((short) 0); - } - } - - private void dumpStackTraces() throws IOException { - // write a HPROF_TRACE record without any frames to be referenced as object alloc sites - writeHeader(HPROF_TRACE, 3 * 4); - out.writeInt(DUMMY_STACK_TRACE_ID); - out.writeInt(0); // thread number - out.writeInt(0); // frame count - - int frameSerialNum = 0; - int numThreads = 0; - Set names = new HashSet<>(); - for (IsolateThread vmThread = VMThreads.firstThread(); vmThread.isNonNull(); vmThread = VMThreads.nextThread(vmThread)) { - if (vmThread == CurrentIsolate.getCurrentThread()) { - /* Skip itself */ - continue; - } - - final List stack = new ArrayList<>(); - - // dump thread stack trace - JavaStackFrameVisitor visitor = new JavaStackFrameVisitor() { - @Override - public boolean visitFrame(FrameInfoQueryResult frameInfo) { - if (frameInfo.getSourceClass() != null) { - stack.add(frameInfo); - } - return true; - } - }; - JavaStackWalker.walkThread(vmThread, visitor); - numThreads++; - - // write HPROF_FRAME records for this thread's stack trace - int depth = stack.size(); - int threadFrameStart = frameSerialNum; - for (int j = 0; j < depth; j++) { - FrameInfoQueryResult frame = stack.get(j); - ClassData cd = classDataCache.get(frame.getSourceClass()); - int classSerialNum = cd.serialNum; - - // the class serial number starts from 1 - assert classSerialNum > 0 : "class not found"; - dumpStackFrame(++frameSerialNum, classSerialNum, frame, names); - } - - // write HPROF_TRACE record for one thread - writeHeader(HPROF_TRACE, 3 * 4 + depth * getObjIDSize()); - int stackSerialNum = numThreads + DUMMY_STACK_TRACE_ID; - out.writeInt(stackSerialNum); // stack trace serial number - out.writeInt(numThreads); // thread serial number - out.writeInt(depth); // frame count - for (int j = 1; j <= depth; j++) { - writeObjectAddress(threadFrameStart + j); - } - } - names = null; - } - - private void dumpStackFrame(int frameSN, int classSN, FrameInfoQueryResult frame, Set names) throws IOException { - int lineNumber; - if (frame.isNativeMethod()) { - lineNumber = -3; // native frame - } else { - lineNumber = frame.getSourceLineNumber(); - } - // First dump UTF8 if needed - String method = frame.getSourceMethodName(); - String source = frame.getSourceFileName(); - if (method == null || method.isEmpty()) { - method = ""; - } - if (source == null || source.isEmpty()) { - source = "Unknown Source"; - } - writeName(method, names); // method's name - writeName("", names); // method's signature - writeName(source, names); // source file name - // Then write FRAME descriptor - writeHeader(HPROF_FRAME, 4 * getObjIDSize() + 2 * 4); - writeObjectAddress(frameSN); // frame serial number - writeSymbolID(method); // method's name - writeSymbolID(""); // method's signature - writeSymbolID(source); // source file name - out.writeInt(classSN); // class serial number - out.writeInt(lineNumber); // line number - } - - private void writeName(String name, Set names) throws IOException { - if (names.add(name)) { - writeSymbol(name); - } - } - - private void writeHeapInstance(Object obj) throws IOException { - writeHeapRecordPrologue(); - if (obj instanceof Class) { - writeClass((Class) obj); - writeHeapRecordEpilogue(); - } else if (heapDumpUtils.isJavaPrimitiveArray(obj)) { - writePrimitiveArray(obj); - } else if (isArray(obj.getClass())) { - writeObjectArray((Object[]) obj); - } else { - writeInstance(obj); - writeHeapRecordEpilogue(); - } - } - - private List getImmediateFields(Class cls) { - String clsName = cls.getName(); - List fields = fieldsMap.get(clsName); - if (fields == null) { - return Collections.emptyList(); - } - return fields; - } - - private void writeObjectArray(Object[] array) throws IOException { - out.writeByte((byte) HPROF_GC_OBJ_ARRAY_DUMP); - writeObjectID(array); - out.writeInt(DUMMY_STACK_TRACE_ID); - out.writeInt(array.length); - writeObjectID(array.getClass()); - writeHeapRecordEpilogue(array.length * getObjIDSize()); - for (Object o : array) { - writeObjectID(o); - } - } - - private void writePrimitiveArray(Object pArray) throws IOException { - out.writeByte((byte) HPROF_GC_PRIM_ARRAY_DUMP); - writeObjectID(pArray); - out.writeInt(DUMMY_STACK_TRACE_ID); - /* These are ordered by expected frequency. */ - if (pArray instanceof char[]) { - writeCharArray((char[]) pArray); - } else if (pArray instanceof byte[]) { - writeByteArray((byte[]) pArray); - } else if (pArray instanceof int[]) { - writeIntArray((int[]) pArray); - } else if (pArray instanceof long[]) { - writeLongArray((long[]) pArray); - } else if (pArray instanceof boolean[]) { - writeBooleanArray((boolean[]) pArray); - } else if (pArray instanceof short[]) { - writeShortArray((short[]) pArray); - } else if (pArray instanceof double[]) { - writeDoubleArray((double[]) pArray); - } else if (pArray instanceof float[]) { - writeFloatArray((float[]) pArray); - } else { - throw VMError.shouldNotReachHere(pArray.getClass().getName()); - } - } - - private void writeBooleanArray(boolean[] array) throws IOException { - out.writeInt(array.length); - out.writeByte((byte) HPROF_BOOLEAN); - writeHeapRecordEpilogue(array.length * 1); - for (boolean b : array) { - out.writeBoolean(b); - } - } - - private void writeByteArray(byte[] array) throws IOException { - out.writeInt(array.length); - out.writeByte((byte) HPROF_BYTE); - writeHeapRecordEpilogue(array.length * 1); - for (byte b : array) { - out.writeByte(b); - } - } - - private void writeShortArray(short[] array) throws IOException { - out.writeInt(array.length); - out.writeByte((byte) HPROF_SHORT); - writeHeapRecordEpilogue(array.length * 2); - for (short s : array) { - out.writeShort(s); - } - } - - private void writeIntArray(int[] array) throws IOException { - out.writeInt(array.length); - out.writeByte((byte) HPROF_INT); - writeHeapRecordEpilogue(array.length * 4); - for (int i : array) { - out.writeInt(i); - } - } - - private void writeLongArray(long[] array) throws IOException { - out.writeInt(array.length); - out.writeByte((byte) HPROF_LONG); - writeHeapRecordEpilogue(array.length * 8); - for (long l : array) { - out.writeLong(l); - } - } - - private void writeCharArray(char[] array) throws IOException { - out.writeInt(array.length); - out.writeByte((byte) HPROF_CHAR); - writeHeapRecordEpilogue(array.length * 2); - for (char c : array) { - out.writeChar(c); - } - } - - private void writeFloatArray(float[] array) throws IOException { - out.writeInt(array.length); - out.writeByte((byte) HPROF_FLOAT); - writeHeapRecordEpilogue(array.length * 4); - for (float f : array) { - out.writeFloat(f); - } - } - - private void writeDoubleArray(double[] array) throws IOException { - out.writeInt(array.length); - out.writeByte((byte) HPROF_DOUBLE); - writeHeapRecordEpilogue(array.length * 8); - for (double d : array) { - out.writeDouble(d); - } - } - - private void writeInstance(Object instance) throws IOException { - out.writeByte((byte) HPROF_GC_INSTANCE_DUMP); - writeObjectID(instance); - out.writeInt(DUMMY_STACK_TRACE_ID); - Class cls = instance.getClass(); - writeObjectID(cls); - - final ClassData cd = classDataCache.get(cls); - out.writeInt(cd.instSize); - final Pointer objRef = heapDumpUtils.objectToPointer(instance); - for (int i = 0; i < cd.fields.length; i++) { - writeField(cd.fields[i], objRef); - } - } - - private void writeFieldDescriptors(boolean isStatic, int size, List fields) throws IOException { - /* cls == null for instance fields. */ - out.writeShort((short) size); - for (int i = 0; i < fields.size(); i++) { - Field field = fields.get(i); - if (isStatic == field.isStatic()) { - writeSymbolIDFromField(field); - char typeCode = field.getStorageSignature(); - int kind = signatureToHprofKind(typeCode); - out.writeByte((byte) kind); - if (field.isStatic()) { - /* Static field. */ - Object staticData; - char javaSignature = field.getStorageSignature(); - if (javaSignature == JVM_SIGNATURE_CLASS || javaSignature == JVM_SIGNATURE_ARRAY) { - staticData = StaticFieldsSupport.getStaticObjectFields(); - } else { - staticData = StaticFieldsSupport.getStaticPrimitiveFields(); - } - writeField(field, heapDumpUtils.objectToPointer(staticData)); - } - } - } - } - - private static int signatureToHprofKind(char ch) { - switch (ch) { - case JVM_SIGNATURE_CLASS: - case JVM_SIGNATURE_ARRAY: - return HPROF_NORMAL_OBJECT; - case JVM_SIGNATURE_BOOLEAN: - return HPROF_BOOLEAN; - case JVM_SIGNATURE_CHAR: - return HPROF_CHAR; - case JVM_SIGNATURE_FLOAT: - return HPROF_FLOAT; - case JVM_SIGNATURE_DOUBLE: - return HPROF_DOUBLE; - case JVM_SIGNATURE_BYTE: - return HPROF_BYTE; - case JVM_SIGNATURE_SHORT: - return HPROF_SHORT; - case JVM_SIGNATURE_INT: - return HPROF_INT; - case JVM_SIGNATURE_LONG: - return HPROF_LONG; - default: - throw new RuntimeException("should not reach here"); - } - } - - private void writeField(Field field, Pointer p) throws IOException { - char storageSignature = field.getStorageSignature(); - int location = field.getLocation(); - - switch (storageSignature) { - case JVM_SIGNATURE_BOOLEAN: - out.writeByte(p.readByte(location)); - break; - case JVM_SIGNATURE_CHAR: - out.writeChar(p.readChar(location)); - break; - case JVM_SIGNATURE_BYTE: - out.writeByte(p.readByte(location)); - break; - case JVM_SIGNATURE_SHORT: - out.writeShort(p.readShort(location)); - break; - case JVM_SIGNATURE_INT: - out.writeInt(p.readInt(location)); - break; - case JVM_SIGNATURE_LONG: - out.writeLong(p.readLong(location)); - break; - case JVM_SIGNATURE_FLOAT: - out.writeFloat(p.readFloat(location)); - break; - case JVM_SIGNATURE_DOUBLE: - out.writeDouble(p.readDouble(location)); - break; - case JVM_SIGNATURE_CLASS: - case JVM_SIGNATURE_ARRAY: - writeObjectID(ReferenceAccess.singleton().readObjectAt(p.add(location), true)); - break; - default: - throw VMError.shouldNotReachHere("HeapDumpWriter.writeField: storageSignature"); - } - } - - private void writeHeader(int tag, int len) throws IOException { - out.writeByte((byte) tag); - out.writeInt(0); /* current ticks. */ - out.writeInt(len); - } - - private void writeDummyTrace() throws IOException { - writeHeader(HPROF_TRACE, 3 * 4); - out.writeInt(DUMMY_STACK_TRACE_ID); - out.writeInt(0); - out.writeInt(0); - } - - private void writeSymbolFromField(byte[] data, Field field) throws IOException { - writeHeader(HPROF_UTF8, field.getNameLength() + getObjIDSize()); - writeSymbolIDFromField(field); - out.write(data, field.getNameStartOffset(), field.getNameLength()); - } - - private void writeSymbol(String clsName) throws IOException { - byte[] buf = clsName.getBytes(StandardCharsets.UTF_8); - writeHeader(HPROF_UTF8, buf.length + getObjIDSize()); - writeSymbolID(clsName); - out.write(buf); - } - - private void writeClassNames(List> classList) throws IOException { - /* hprof UTF-8 symbols section. */ - for (Class cls : classList) { - writeSymbol(cls.getName()); - } - } - - private void writeClasses(List> classList) throws IOException { - int serialNum = 1; - /* - * Build a temporary map from Class to ClassData while I can allocate, but turn it into a - * map between arrays for later use. - */ - Map, ClassData> classDataMap = new HashMap<>(); - List fields = new ArrayList<>(); - for (Class cls : classList) { - writeHeader(HPROF_LOAD_CLASS, 2 * (getObjIDSize() + 4)); - out.writeInt(serialNum); - writeObjectID(cls); - out.writeInt(DUMMY_STACK_TRACE_ID); - writeSymbolID(cls.getName()); - assert fields.isEmpty(); - addInstanceFieldsTo(fields, cls); - int instSize = getSizeForFields(fields); - classDataMap.put(cls, new ClassData(serialNum, instSize, fields.toArray(ZERO_FIELD_ARR))); - fields.clear(); - serialNum++; - } - classDataCache = new ClassToClassDataMap(classDataMap); - } - - /** 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.writeByte((byte) '\0'); - - /* Write identifier size. we use pointers as identifiers. */ - out.writeInt(getObjIDSize()); - - /* Time stamp: file creation time. */ - out.writeLong(System.currentTimeMillis()); - } - - /** Writes unique ID for an object. */ - private void writeObjectID(Object obj) throws IOException { - if (obj != null) { - WordBase ptr = ReferenceAccess.singleton().getCompressedRepresentation(obj); - writeObjectAddress(ptr.rawValue()); - } else { - writeObjectAddress(0L); - } - } - - private void writeSymbolID(String clsName) throws IOException { - writeObjectID(clsName); - } - - private void writeSymbolIDFromField(Field field) throws IOException { - writeObjectID(field); - } - - private void writeObjectAddress(long address) throws IOException { - if (getObjIDSize() == 4) { - out.writeInt((int) address); - } else { - out.writeLong(address); - } - } - - /** Get all declared as well as inherited (directly/indirectly) fields. */ - private void addInstanceFieldsTo(List res, final Class cls) { - Class clazz = cls; - while (clazz != null) { - List curFields = getImmediateFields(clazz); - for (int i = 0; i < curFields.size(); i++) { - Field f = curFields.get(i); - - if (!f.isStatic()) { - res.add(f); - } - } - clazz = clazz.getSuperclass(); - } - } - - /** - * Get size in bytes (in stream) required for given fields. Note that this is not the same as - * object size in heap. The size in heap will include size of padding/alignment bytes as well. - */ - private static int getSizeForFields(List fields) { - int size = 0; - for (int i = 0; i < fields.size(); i++) { - Field field = fields.get(i); - char typeCode = field.getStorageSignature(); - switch (typeCode) { - case JVM_SIGNATURE_BOOLEAN: - case JVM_SIGNATURE_BYTE: - size++; - break; - case JVM_SIGNATURE_CHAR: - case JVM_SIGNATURE_SHORT: - size += 2; - break; - case JVM_SIGNATURE_INT: - case JVM_SIGNATURE_FLOAT: - size += 4; - break; - case JVM_SIGNATURE_CLASS: - case JVM_SIGNATURE_ARRAY: - size += getObjIDSize(); - break; - case JVM_SIGNATURE_LONG: - case JVM_SIGNATURE_DOUBLE: - size += 8; - break; - default: - throw new RuntimeException("should not reach here"); - } - } - return size; - } - - private static int getObjIDSize() { - return ConfigurationValues.getObjectLayout().getReferenceSize(); - } - - private static boolean isArray(Class cls) { - return cls.getName().startsWith("["); - } - - private static Class getBaseClass(final Class array) { - Class arr = array; - while (isArray(arr)) { - arr = arr.getComponentType(); - } - return arr; - } - - private Map> createFieldsMap(byte[] data) throws IOException { - int offset = 0; - Map> fldMap = new HashMap<>(); - while (offset < data.length) { - List fields; - String className = readString(data, offset); - offset += className.length() + 1; - - if (data[offset] == 0 && data[offset + 1] == 0) { - /* No fields. */ - fields = Collections.emptyList(); - offset += 2; - } else { - fields = new ArrayList<>(); - offset = readFields(false, data, offset, fields); - offset++; - offset = readFields(true, data, offset, fields); - offset++; - } - fldMap.put(className, fields); - } - return fldMap; - } - - private int readFields(boolean isStatic, byte[] data, int dataOffset, List fields) throws IOException { - int offset = dataOffset; - while (data[offset] != 0) { - /* Read field. */ - int stringStart = offset; - int stringLength = readStringLength(data, offset); - offset += stringLength + 1; - char javaSig = (char) data[offset++]; - char storageSig = (char) data[offset++]; - int location = readInt(data, offset); - offset += 4; - Field fieldDef = new Field(stringStart, stringLength, javaSig, storageSig, isStatic, location); - writeSymbolFromField(data, fieldDef); - fields.add(fieldDef); - } - return offset; - } - - private static int readInt(final byte[] data, final int st) { - int start = st; - int ch1 = data[start++] & 0xFF; - int ch2 = data[start++] & 0xFF; - int ch3 = data[start++] & 0xFF; - int ch4 = data[start++] & 0xFF; - return ((ch1 << 24) + (ch2 << 16) + (ch3 << 8) + (ch4 << 0)); - } - - private static String readString(byte[] data, int start) { - int len = readStringLength(data, start); - - return new String(data, start, len, StandardCharsets.UTF_8); - } - - /** - * Returns size of the null-terminated string. - * - * @param data byte[] array that is the source of string. - * @param start the initial offset to data array. - * @return the number of characters (bytes) in a null-terminated character sequence, without - * including the null-terminating character. - */ - private static int readStringLength(byte[] data, int start) { - int offset = start; - - while (data[offset] != 0) { - offset++; - } - return offset - start; - } - - private static class ClassData { - - int serialNum; - int instSize; - Field[] fields; - - ClassData(int serialNum, int instSize, Field[] fields) { - this.serialNum = serialNum; - this.instSize = instSize; - this.fields = fields; - } - } - - private static final class Field { - - private final int nameStart; - private final int nameLength; - private final char javaSig; - private final char storageSig; - private final boolean isStatic; - private final int location; - - private Field(final int ss, final int sl, final char jsig, final char ssig, - final boolean s, int loc) { - nameStart = ss; - nameLength = sl; - javaSig = jsig == 'A' ? JVM_SIGNATURE_CLASS : jsig; - storageSig = ssig == 'A' ? JVM_SIGNATURE_CLASS : ssig; - isStatic = s; - location = loc; - } - - private boolean isStatic() { - return isStatic; - } - - @SuppressWarnings({"unused"}) - private char getJavaSignature() { - return javaSig; - } - - private char getStorageSignature() { - return storageSig; - } - - private int getNameStartOffset() { - return nameStart; - } - - private int getNameLength() { - return nameLength; - } - - private int getLocation() { - return location; - } - } - - private class CollectedHeapVisitorImpl implements ObjectVisitor { - - private IOException exception; - - @Override - public boolean visitObject(Object obj) { - Object asObject = obj; - try { - writeHeapInstance(asObject); - } catch (IOException ex) { - /* Remember exception and abort VM operation. */ - exception = ex; - return false; - } - return true; - } - - private IOException getException() { - return exception; - } - } - - private class ImageHeapVisitorImpl implements ObjectVisitor { - - private IOException exception; - - @Override - public boolean visitObject(Object obj) { - Object asObject = obj; - try { - writeHeapInstance(asObject); - writeImageGCRoot(asObject); - } catch (IOException ex) { - /* Remember exception and abort VM operation. */ - exception = ex; - return false; - } - return true; - } - - private IOException getException() { - return exception; - } - } - - private class StacksSlotsVisitorImpl extends HeapDumpUtils.StacksSlotsVisitor { - - private IOException exception; - - @Override - public boolean visitObjectReference(Pointer objRef, boolean compressed, Object holderObject) { - try { - /* Get the referent of the reference. */ - final Object obj = ReferenceAccess.singleton().readObjectAt(objRef, compressed); - writeUnknownGCRoot(obj); - return true; - } catch (IOException ex) { - /* Remember exception. */ - exception = ex; - return false; - } - } - - private IOException getException() { - return exception; - } - } - - /** - * Abstract Allocation-free output stream created from FileOutputStream. This is the base class - * used by HeapDumpWriteImpl class. - */ - public abstract static class AllocationFreeFileOutputStream extends OutputStream { - - // constructor - public abstract AllocationFreeFileOutputStream newStreamFor(FileOutputStream fileOutputStream) throws IOException; - - @Override - public abstract void write(int b) throws IOException; - - @Override - public abstract void write(byte[] b, int offset, int length) throws IOException; - - @Override - public abstract void close() throws IOException; - - @Override - public void flush() throws IOException { - } - - /** Read the current position in a file descriptor. */ - protected abstract long position() throws IOException; - - /** Set the current position in a file descriptor. */ - protected abstract long position(long offset) throws IOException; - } - - /** - * Implementation of allocation-free output stream, which delegates to - * AllocationFreeOutputStream interface. - */ - private final class AllocationFreeFileOutputStreamWrapper extends AllocationFreeFileOutputStream { - - private final AllocationFreeOutputStream out; - private long position; - - private AllocationFreeFileOutputStreamWrapper(AllocationFreeOutputStream outputStream) { - out = outputStream; - position = 0; - } - - @Override - public AllocationFreeFileOutputStream newStreamFor(FileOutputStream fileOutputStream) throws IOException { - throw VMError.shouldNotReachHere(); - } - - @Override - public void write(int b) throws IOException { - out.write(b); - position++; - } - - @Override - public void write(byte[] b, int offset, int length) throws IOException { - out.write(b, offset, length); - position += length; - } - - @Override - public void close() throws IOException { - out.close(); - } - - @Override - public void flush() throws IOException { - out.flush(); - } - - @Override - protected long position() throws IOException { - return position; - } - - @Override - protected long position(long offset) throws IOException { - throw VMError.shouldNotReachHere(); - } - } - - private static final class AllocationFreeDataOutputStream { - - private final AllocationFreeBufferedOutputStream out; - - private AllocationFreeDataOutputStream(AllocationFreeBufferedOutputStream o) { - out = o; - } - - private void writeBoolean(boolean v) throws IOException { - out.write(v ? 1 : 0); - } - - private void writeByte(int v) throws IOException { - out.write(v); - } - - private void writeChar(int v) throws IOException { - out.write((v >>> 8) & 0xFF); - out.write((v >>> 0) & 0xFF); - } - - private void writeShort(int v) throws IOException { - out.write((v >>> 8) & 0xFF); - out.write((v >>> 0) & 0xFF); - } - - private void writeInt(int v) throws IOException { - out.write((v >>> 24) & 0xFF); - out.write((v >>> 16) & 0xFF); - out.write((v >>> 8) & 0xFF); - out.write((v >>> 0) & 0xFF); - } - - private void writeFloat(float v) throws IOException { - writeInt(Float.floatToIntBits(v)); - } - - private void writeLong(long v) throws IOException { - out.write((byte) (v >>> 56)); - out.write((byte) (v >>> 48)); - out.write((byte) (v >>> 40)); - out.write((byte) (v >>> 32)); - out.write((byte) (v >>> 24)); - out.write((byte) (v >>> 16)); - out.write((byte) (v >>> 8)); - out.write((byte) (v >>> 0)); - } - - private void writeDouble(double v) throws IOException { - writeLong(Double.doubleToLongBits(v)); - } - - private void flush() throws IOException { - out.flush(); - } - - private void write(byte[] buf) throws IOException { - out.write(buf, 0, buf.length); - } - - private void write(byte[] buf, int off, int len) throws IOException { - out.write(buf, off, len); - } - - private void writeBytes(String s) throws IOException { - int len = s.length(); - for (int i = 0; i < len; i++) { - out.write((byte) s.charAt(i)); - } - } - - private long position() throws IOException { - return out.position(); - } - - private void position(long pos) throws IOException { - out.position(pos); - } - } - - private static final class AllocationFreeBufferedOutputStream { - - private byte[] buf; - // current index in buf array - private int position; - // size of valid data in buf array - private int size; - private AllocationFreeFileOutputStream out; - - private AllocationFreeBufferedOutputStream(AllocationFreeFileOutputStream out) { - this(out, 8192); - } - - private AllocationFreeBufferedOutputStream(AllocationFreeFileOutputStream out, int size) { - this.out = out; - if (size <= 0) { - throw new IllegalArgumentException("Buffer size <= 0"); - } - buf = new byte[size]; - } - - private void flushBuffer() throws IOException { - if (size > 0) { - out.write(buf, 0, size); - position = 0; - size = 0; - } - } - - private void write(int b) throws IOException { - if (position >= buf.length) { - flushBuffer(); - } - buf[position++] = (byte) b; - setSize(); - } - - public void write(byte[] b, int off, int len) throws IOException { - if (len >= buf.length) { - flushBuffer(); - out.write(b, off, len); - return; - } - if (len > buf.length - position) { - flushBuffer(); - } - System.arraycopy(b, off, buf, position, len); - position += len; - setSize(); - } - - void flush() throws IOException { - flushBuffer(); - out.flush(); - } - - private long position() throws IOException { - return out.position() + position; - } - - private void position(long pos) throws IOException { - long currentFlushPos = out.position(); - long newCount = pos - currentFlushPos; - - if (newCount >= 0 && newCount <= size) { - position = (int) newCount; - } else { - flush(); - out.position(pos); - } - } - - private void setSize() { - if (position > size) { - size = position; - } - } - } - - private class WriterOperation extends JavaVMOperation { - - private final AllocationFreeDataOutputStream dataOutput; - private final boolean gcBefore; - private IOException exception; - - WriterOperation(FileOutputStream fileOutputStream, boolean gcBefore) throws IOException { - super(VMOperationInfos.get(WriterOperation.class, "Write heap dump", VMOperation.SystemEffect.SAFEPOINT)); - /* open file stream and create buffered data output stream. */ - AllocationFreeFileOutputStream fos = ImageSingletons.lookup(AllocationFreeFileOutputStream.class).newStreamFor(fileOutputStream); - dataOutput = new AllocationFreeDataOutputStream(new AllocationFreeBufferedOutputStream(fos)); - this.gcBefore = gcBefore; - } - - WriterOperation(AllocationFreeOutputStream outputStream, boolean gcBefore, int bufferSize) { - super(VMOperationInfos.get(WriterOperation.class, "Write heap dump", VMOperation.SystemEffect.SAFEPOINT)); - /* open file stream and create buffered data output stream. */ - AllocationFreeFileOutputStream fos = new AllocationFreeFileOutputStreamWrapper(outputStream); - dataOutput = new AllocationFreeDataOutputStream(new AllocationFreeBufferedOutputStream(fos, bufferSize + 32768)); - this.gcBefore = gcBefore; - } - - @Override - protected void operate() { - try { - writeTo(dataOutput, gcBefore); - } catch (IOException ex) { - exception = ex; - } - } - - private IOException getException() { - return exception; - } - - } - - /** A map from Class to ClassData. */ - private static class ClassToClassDataMap { - - private final ClassData[] classDataArray; - - ClassToClassDataMap(Map, ClassData> map) { - /* Find the maximum typeID among the classes. */ - int maxTypeID = 0; - for (Class key : map.keySet()) { - maxTypeID = Integer.max(maxTypeID, typeIDFromClass(key)); - } - /* Make up an array large enough to be indexed by typeID. */ - classDataArray = new ClassData[maxTypeID + 1]; - /* Fill in the array. */ - for (Class key : map.keySet()) { - classDataArray[typeIDFromClass(key)] = map.get(key); - } - } - - /** Use typeID to find the Class. */ - ClassData get(Class clazz) { - int id = typeIDFromClass(clazz); - if (id >= classDataArray.length) { - return null; // class not loaded, there can be no instances - } - return classDataArray[id]; - } - - /** Look up the typeID of a Class from the DynamicHub. */ - private static int typeIDFromClass(Class clazz) { - return DynamicHub.fromClass(clazz).getTypeID(); - } - } -} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/StorageKind.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/StorageKind.java new file mode 100644 index 000000000000..db946ad45673 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/StorageKind.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2023, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package com.oracle.svm.core.heapdump; + +import com.oracle.svm.core.config.ConfigurationValues; +import com.oracle.svm.core.util.VMError; + +public class StorageKind { + public static final byte BOOLEAN = 'Z'; + public static final byte BYTE = 'B'; + public static final byte SHORT = 'S'; + public static final byte CHAR = 'C'; + public static final byte INT = 'I'; + public static final byte FLOAT = 'F'; + public static final byte LONG = 'J'; + public static final byte DOUBLE = 'D'; + public static final byte OBJECT = 'A'; + + public static int getSize(byte storageKind) { + return switch (storageKind) { + case StorageKind.BOOLEAN, StorageKind.BYTE -> 1; + case StorageKind.CHAR, StorageKind.SHORT -> 2; + case StorageKind.INT, StorageKind.FLOAT -> 4; + case StorageKind.OBJECT -> ConfigurationValues.getTarget().wordSize; + case StorageKind.LONG, StorageKind.DOUBLE -> 8; + default -> throw VMError.shouldNotReachHere("Unexpected storage kind."); + }; + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHub.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHub.java index 11412eb47e76..a7727d7a16a1 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHub.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHub.java @@ -768,8 +768,10 @@ public InputStream getResourceAsStream(String resourceName) { @KeepOriginal private native boolean isOpenToCaller(String resourceName, Class caller); - @KeepOriginal - private native ClassLoader getClassLoader(); + @Substitute + public ClassLoader getClassLoader() { + return classLoader; + } @KeepOriginal private native ClassLoader getClassLoader0(); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrChunkWriter.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrChunkWriter.java index 2225aec0f4c1..2050038a41e8 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrChunkWriter.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrChunkWriter.java @@ -32,7 +32,6 @@ import org.graalvm.nativeimage.IsolateThread; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; -import org.graalvm.word.SignedWord; import org.graalvm.word.UnsignedWord; import org.graalvm.word.WordFactory; @@ -42,6 +41,8 @@ import com.oracle.svm.core.jfr.sampler.JfrRecurringCallbackExecutionSampler; import com.oracle.svm.core.jfr.traceid.JfrTraceIdEpoch; import com.oracle.svm.core.os.RawFileOperationSupport; +import com.oracle.svm.core.os.RawFileOperationSupport.FileAccessMode; +import com.oracle.svm.core.os.RawFileOperationSupport.FileCreationMode; import com.oracle.svm.core.sampler.SamplerBuffersAccess; import com.oracle.svm.core.thread.JavaVMOperation; import com.oracle.svm.core.thread.ThreadingSupportImpl; @@ -123,7 +124,7 @@ public boolean openFile(String outputFile) { chunkStartNanos = JfrTicks.currentTimeNanos(); chunkStartTicks = JfrTicks.elapsedTicks(); filename = outputFile; - fd = getFileSupport().open(filename, RawFileOperationSupport.FileAccessMode.READ_WRITE); + fd = getFileSupport().create(filename, FileCreationMode.CREATE_OR_REPLACE, FileAccessMode.READ_WRITE); writeFileHeader(); return true; } @@ -142,7 +143,7 @@ public boolean write(JfrBuffer buffer) { // We lost some data because the write failed. return false; } - return getFileSupport().position(fd).greaterThan(WordFactory.signed(notificationThreshold)); + return getFileSupport().position(fd) > notificationThreshold; } /** @@ -163,8 +164,8 @@ public void closeFile(byte[] metadataDescriptor, JfrConstantPool[] repositories) * data structures of the new epoch. This guarantees that the data in the old epoch can be * persisted to a file without a safepoint. */ - SignedWord constantPoolPosition = writeCheckpointEvent(repositories); - SignedWord metadataPosition = writeMetadataEvent(metadataDescriptor); + long constantPoolPosition = writeCheckpointEvent(repositories); + long metadataPosition = writeMetadataEvent(metadataDescriptor); patchFileHeader(constantPoolPosition, metadataPosition); getFileSupport().close(fd); @@ -177,7 +178,7 @@ private void writeFileHeader() { getFileSupport().write(fd, FILE_MAGIC); getFileSupport().writeShort(fd, JFR_VERSION_MAJOR); getFileSupport().writeShort(fd, JFR_VERSION_MINOR); - assert getFileSupport().position(fd).equal(CHUNK_SIZE_OFFSET); + assert getFileSupport().position(fd) == CHUNK_SIZE_OFFSET; getFileSupport().writeLong(fd, 0L); // chunk size getFileSupport().writeLong(fd, 0L); // last checkpoint offset getFileSupport().writeLong(fd, 0L); // metadata position @@ -188,30 +189,30 @@ private void writeFileHeader() { getFileSupport().writeInt(fd, compressedInts ? 1 : 0); } - public void patchFileHeader(SignedWord constantPoolPosition, SignedWord metadataPosition) { - long chunkSize = getFileSupport().position(fd).rawValue(); + public void patchFileHeader(long constantPoolPosition, long metadataPosition) { + long chunkSize = getFileSupport().position(fd); long durationNanos = JfrTicks.currentTimeNanos() - chunkStartNanos; - getFileSupport().seek(fd, WordFactory.signed(CHUNK_SIZE_OFFSET)); + getFileSupport().seek(fd, CHUNK_SIZE_OFFSET); getFileSupport().writeLong(fd, chunkSize); - getFileSupport().writeLong(fd, constantPoolPosition.rawValue()); - getFileSupport().writeLong(fd, metadataPosition.rawValue()); + getFileSupport().writeLong(fd, constantPoolPosition); + getFileSupport().writeLong(fd, metadataPosition); getFileSupport().writeLong(fd, chunkStartNanos); getFileSupport().writeLong(fd, durationNanos); } - private SignedWord writeCheckpointEvent(JfrConstantPool[] repositories) { - SignedWord start = beginEvent(); + private long writeCheckpointEvent(JfrConstantPool[] repositories) { + long start = beginEvent(); writeCompressedLong(CONSTANT_POOL_TYPE_ID); writeCompressedLong(JfrTicks.elapsedTicks()); writeCompressedLong(0); // duration writeCompressedLong(0); // deltaToNext writeBoolean(true); // flush - SignedWord poolCountPos = getFileSupport().position(fd); + long poolCountPos = getFileSupport().position(fd); getFileSupport().writeInt(fd, 0); // We'll patch this later. JfrConstantPool[] serializers = JfrSerializerSupport.get().getSerializers(); int poolCount = writeConstantPools(serializers) + writeConstantPools(repositories); - SignedWord currentPos = getFileSupport().position(fd); + long currentPos = getFileSupport().position(fd); getFileSupport().seek(fd, poolCountPos); getFileSupport().writeInt(fd, makePaddedInt(poolCount)); getFileSupport().seek(fd, currentPos); @@ -229,8 +230,8 @@ private int writeConstantPools(JfrConstantPool[] constantPools) { return count; } - private SignedWord writeMetadataEvent(byte[] metadataDescriptor) { - SignedWord start = beginEvent(); + private long writeMetadataEvent(byte[] metadataDescriptor) { + long start = beginEvent(); writeCompressedLong(METADATA_TYPE_ID); writeCompressedLong(JfrTicks.elapsedTicks()); writeCompressedLong(0); // duration @@ -242,21 +243,21 @@ private SignedWord writeMetadataEvent(byte[] metadataDescriptor) { public boolean shouldRotateDisk() { assert lock.isHeldByCurrentThread(); - return getFileSupport().isValid(fd) && getFileSupport().size(fd).greaterThan(WordFactory.signed(notificationThreshold)); + return getFileSupport().isValid(fd) && getFileSupport().size(fd) > notificationThreshold; } - public SignedWord beginEvent() { - SignedWord start = getFileSupport().position(fd); + public long beginEvent() { + long start = getFileSupport().position(fd); // Write a placeholder for the size. Will be patched by endEvent, getFileSupport().writeInt(fd, 0); return start; } - public void endEvent(SignedWord start) { - SignedWord end = getFileSupport().position(fd); - SignedWord writtenBytes = end.subtract(start); + public void endEvent(long start) { + long end = getFileSupport().position(fd); + long writtenBytes = end - start; getFileSupport().seek(fd, start); - getFileSupport().writeInt(fd, makePaddedInt(writtenBytes.rawValue())); + getFileSupport().writeInt(fd, makePaddedInt(writtenBytes)); getFileSupport().seek(fd, end); } @@ -362,7 +363,7 @@ public enum StringEncoding { CHAR_ARRAY(4), LATIN1_BYTE_ARRAY(5); - public byte byteValue; + public final byte byteValue; StringEncoding(int byteValue) { this.byteValue = (byte) byteValue; diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/os/AbstractRawFileOperationSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/os/AbstractRawFileOperationSupport.java index 9145879b9976..215f4add6a52 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/os/AbstractRawFileOperationSupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/os/AbstractRawFileOperationSupport.java @@ -50,8 +50,13 @@ protected AbstractRawFileOperationSupport(boolean useNativeByteOrder) { } @Override - public RawFileDescriptor open(String filename, FileAccessMode mode) { - return open(new File(filename), mode); + public RawFileDescriptor create(String filename, FileCreationMode creationMode, FileAccessMode accessMode) { + return create(new File(filename), creationMode, accessMode); + } + + @Override + public RawFileDescriptor open(String filename, FileAccessMode accessMode) { + return open(new File(filename), accessMode); } @Override @@ -114,34 +119,48 @@ public boolean writeLong(RawFileDescriptor fd, long data) { return write(fd, dataPtr, WordFactory.unsigned(sizeInBytes)); } + @Override + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public boolean writeFloat(RawFileDescriptor fd, float data) { + return writeInt(fd, Float.floatToIntBits(data)); + } + + @Override + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public boolean writeDouble(RawFileDescriptor fd, double data) { + return writeLong(fd, Double.doubleToLongBits(data)); + } + public static class RawFileOperationSupportHolder { private final RawFileOperationSupport littleEndian; private final RawFileOperationSupport bigEndian; - private final RawFileOperationSupport nativeByteOrder; + private final RawFileOperationSupport nativeOrder; @Platforms(Platform.HOSTED_ONLY.class) - public RawFileOperationSupportHolder(RawFileOperationSupport littleEndian, RawFileOperationSupport bigEndian, RawFileOperationSupport nativeByteOrder) { + public RawFileOperationSupportHolder(RawFileOperationSupport littleEndian, RawFileOperationSupport bigEndian, RawFileOperationSupport nativeOrder) { this.littleEndian = littleEndian; this.bigEndian = bigEndian; - this.nativeByteOrder = nativeByteOrder; + this.nativeOrder = nativeOrder; + } + + @Fold + static RawFileOperationSupportHolder singleton() { + return ImageSingletons.lookup(RawFileOperationSupportHolder.class); } @Fold public static RawFileOperationSupport getLittleEndian() { - RawFileOperationSupportHolder holder = ImageSingletons.lookup(RawFileOperationSupportHolder.class); - return holder.littleEndian; + return singleton().littleEndian; } @Fold public static RawFileOperationSupport getBigEndian() { - RawFileOperationSupportHolder holder = ImageSingletons.lookup(RawFileOperationSupportHolder.class); - return holder.bigEndian; + return singleton().bigEndian; } @Fold public static RawFileOperationSupport getNativeByteOrder() { - RawFileOperationSupportHolder holder = ImageSingletons.lookup(RawFileOperationSupportHolder.class); - return holder.nativeByteOrder; + return singleton().nativeOrder; } } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/os/BufferedFileOperationSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/os/BufferedFileOperationSupport.java new file mode 100644 index 000000000000..452399d0ba58 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/os/BufferedFileOperationSupport.java @@ -0,0 +1,448 @@ +/* + * Copyright (c) 2021, 2021, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package com.oracle.svm.core.os; + +import java.nio.ByteOrder; + +import org.graalvm.compiler.api.replacements.Fold; +import org.graalvm.compiler.word.Word; +import org.graalvm.nativeimage.ImageSingletons; +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; +import org.graalvm.nativeimage.c.struct.RawField; +import org.graalvm.nativeimage.c.struct.RawStructure; +import org.graalvm.nativeimage.c.struct.SizeOf; +import org.graalvm.nativeimage.impl.UnmanagedMemorySupport; +import org.graalvm.word.Pointer; +import org.graalvm.word.PointerBase; +import org.graalvm.word.UnsignedWord; +import org.graalvm.word.WordFactory; + +import com.oracle.svm.core.Uninterruptible; +import com.oracle.svm.core.UnmanagedMemoryUtil; +import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature; +import com.oracle.svm.core.feature.InternalFeature; +import com.oracle.svm.core.hub.DynamicHub; +import com.oracle.svm.core.hub.LayoutEncoding; +import com.oracle.svm.core.jdk.JavaLangSubstitutions; +import com.oracle.svm.core.os.AbstractRawFileOperationSupport.RawFileOperationSupportHolder; +import com.oracle.svm.core.os.BufferedFileOperationSupport.BufferedFileOperationSupportHolder; +import com.oracle.svm.core.os.RawFileOperationSupport.RawFileDescriptor; +import com.oracle.svm.core.snippets.KnownIntrinsics; + +/** + * Provides buffered, OS-independent operations on files. Most of the code is implemented in a way + * that it can be used from uninterruptible code. + */ +public class BufferedFileOperationSupport { + /** + * Returns a {@link BufferedFileOperationSupport} singleton that uses little endian byte + * ordering. + */ + @Fold + public static BufferedFileOperationSupport littleEndian() { + return BufferedFileOperationSupportHolder.singleton().littleEndian; + } + + /** + * Returns a {@link BufferedFileOperationSupport} singleton that uses big endian byte ordering. + */ + @Fold + public static BufferedFileOperationSupport bigEndian() { + return BufferedFileOperationSupportHolder.singleton().bigEndian; + } + + /** + * Returns a {@link BufferedFileOperationSupport} singleton that uses the native byte ordering + * of the underlying architecture. + */ + @Fold + public static BufferedFileOperationSupport nativeByteOrder() { + return BufferedFileOperationSupportHolder.singleton().nativeOrder; + } + + private static final int BUFFER_SIZE = 4 * 1024; + + private final boolean useNativeByteOrder; + + @Platforms(Platform.HOSTED_ONLY.class) + protected BufferedFileOperationSupport(boolean useNativeByteOrder) { + this.useNativeByteOrder = useNativeByteOrder; + } + + /** + * Allocate a {@link BufferedFile} for a {@link RawFileDescriptor}. + **/ + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public BufferedFile allocate(RawFileDescriptor fd) { + if (!rawFiles().isValid(fd)) { + return WordFactory.nullPointer(); + } + long filePosition = rawFiles().position(fd); + if (filePosition < 0) { + return WordFactory.nullPointer(); + } + + /* Use a single allocation for the struct and the corresponding buffer. */ + UnsignedWord totalSize = SizeOf.unsigned(BufferedFile.class).add(WordFactory.unsigned(BUFFER_SIZE)); + BufferedFile result = ImageSingletons.lookup(UnmanagedMemorySupport.class).malloc(totalSize); + if (result.isNull()) { + return WordFactory.nullPointer(); + } + + result.setFileDescriptor(fd); + result.setFilePosition(filePosition); + result.setBufferPos(getBufferStart(result)); + return result; + } + + /** + * Free the {@link BufferedFile} and its corresponding buffer. This operation does neither flush + * pending data nor close the underlying {@link RawFileDescriptor}. + */ + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public void free(BufferedFile f) { + ImageSingletons.lookup(UnmanagedMemorySupport.class).free(f); + } + + /** + * Flush the buffered data to the disk. + * + * @return true if the operation is successful or there was no pending data that needed + * flushing. + */ + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public boolean flush(BufferedFile f) { + int unflushed = getUnflushedDataSize(f); + if (unflushed == 0) { + return true; + } + + boolean success = rawFiles().write(f.getFileDescriptor(), getBufferStart(f), WordFactory.unsigned(unflushed)); + if (success) { + f.setBufferPos(getBufferStart(f)); + f.setFilePosition(f.getFilePosition() + unflushed); + } + return success; + } + + /** + * Gets the current file position within a file. + * + * @return If the operation is successful, it returns the current file position. Otherwise, it + * returns a value less than 0. + */ + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public long position(BufferedFile f) { + return f.getFilePosition() + getUnflushedDataSize(f); + } + + /** + * Sets the current file position within a file. + * + * @return true if the file position was updated to the given value, false otherwise. + */ + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public boolean seek(BufferedFile f, long position) { + if (position >= 0 && flush(f) && rawFiles().seek(f.getFileDescriptor(), position)) { + f.setFilePosition(position); + return true; + } + return false; + } + + /** + * Writes data to the current file position and advances the file position. + * + * @return true if the data was written, false otherwise. + */ + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public boolean write(BufferedFile f, Pointer data, UnsignedWord size) { + /* Large data may bypass the buffer and is written directly to the file. */ + if (size.aboveOrEqual(BUFFER_SIZE)) { + if (flush(f) && rawFiles().write(f.getFileDescriptor(), data, size)) { + f.setFilePosition(f.getFilePosition() + size.rawValue()); + assert f.getBufferPos() == getBufferStart(f); + return true; + } + return false; + } + + /* Try to write the data to the buffer. */ + assert (int) size.rawValue() == size.rawValue(); + if (!ensureBufferSpace(f, (int) size.rawValue())) { + return false; + } + + Pointer pos = f.getBufferPos(); + UnmanagedMemoryUtil.copy(data, pos, size); + f.setBufferPos(pos.add(size)); + return true; + } + + /** + * Writes data to the current file position and advances the file position. + * + * @return true if the data was written, false otherwise. + */ + @Uninterruptible(reason = "Array must not move.") + public boolean write(BufferedFile f, byte[] data) { + DynamicHub hub = KnownIntrinsics.readHub(data); + UnsignedWord baseOffset = LayoutEncoding.getArrayBaseOffset(hub.getLayoutEncoding()); + Pointer dataPtr = Word.objectToUntrackedPointer(data).add(baseOffset); + return write(f, dataPtr, WordFactory.unsigned(data.length)); + } + + /** + * Writes data to the current file position and advances the file position. + * + * @return true if the data was written, false otherwise. + */ + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public boolean writeBoolean(BufferedFile f, boolean data) { + return writeByte(f, (byte) (data ? 1 : 0)); + } + + /** + * Writes data to the current file position and advances the file position. + * + * @return true if the data was written, false otherwise. + */ + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public boolean writeByte(BufferedFile f, byte data) { + if (!ensureBufferSpace(f, Byte.BYTES)) { + return false; + } + + Pointer pos = f.getBufferPos(); + pos.writeByte(0, data); + f.setBufferPos(pos.add(Byte.BYTES)); + return true; + } + + /** + * Writes a short value in the specified byte ordering to the current file position and advances + * the file position. + * + * @return true if the data was written, false otherwise. + */ + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public boolean writeShort(BufferedFile f, short data) { + if (!ensureBufferSpace(f, Short.BYTES)) { + return false; + } + + Pointer pos = f.getBufferPos(); + pos.writeShort(0, useNativeByteOrder ? data : Short.reverseBytes(data)); + f.setBufferPos(pos.add(Short.BYTES)); + return true; + } + + /** + * Writes a char value in the specified byte ordering to the current file position and advances + * the file position. + * + * @return true if the data was written, false otherwise. + */ + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public boolean writeChar(BufferedFile f, char data) { + if (!ensureBufferSpace(f, Character.BYTES)) { + return false; + } + + Pointer pos = f.getBufferPos(); + pos.writeChar(0, useNativeByteOrder ? data : Character.reverseBytes(data)); + f.setBufferPos(pos.add(Character.BYTES)); + return true; + } + + /** + * Writes an integer value in the specified byte ordering to the current file position and + * advances the file position. + * + * @return true if the data was written, false otherwise. + */ + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public boolean writeInt(BufferedFile f, int data) { + if (!ensureBufferSpace(f, Integer.BYTES)) { + return false; + } + + Pointer pos = f.getBufferPos(); + pos.writeInt(0, useNativeByteOrder ? data : Integer.reverseBytes(data)); + f.setBufferPos(pos.add(Integer.BYTES)); + return true; + } + + /** + * Writes a long value in the specified byte ordering to the current file position and advances + * the file position. + * + * @return true if the v was written, false otherwise. + */ + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public boolean writeLong(BufferedFile f, long v) { + if (!ensureBufferSpace(f, Long.BYTES)) { + return false; + } + + Pointer pos = f.getBufferPos(); + pos.writeLong(0, useNativeByteOrder ? v : Long.reverseBytes(v)); + f.setBufferPos(pos.add(Long.BYTES)); + return true; + } + + /** + * Writes a float value in the specified byte ordering to the current file position and advances + * the file position. + * + * @return true if the data was written, false otherwise. + */ + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public boolean writeFloat(BufferedFile f, float v) { + return writeInt(f, Float.floatToIntBits(v)); + } + + /** + * Writes a double value in the specified byte ordering to the current file position and + * advances the file position. + * + * @return true if the data was written, false otherwise. + */ + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public boolean writeDouble(BufferedFile f, double v) { + return writeLong(f, Double.doubleToLongBits(v)); + } + + /** + * Writes the String characters UTF8 encoded to the current file position and advances the file + * position. + * + * @return true if the data was written, false otherwise. + */ + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public boolean writeUTF8(BufferedFile f, String string) { + boolean success = true; + for (int index = 0; index < string.length() && success; index++) { + success &= writeUTF8(f, JavaLangSubstitutions.StringUtil.charAt(string, index)); + } + return success; + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + private boolean writeUTF8(BufferedFile f, char c) { + boolean success; + if (c <= 0x007F) { + success = writeByte(f, (byte) c); + } else if (c <= 0x07FF) { + success = writeByte(f, (byte) (0xC0 | (c >> 6))); + success = success && writeByte(f, (byte) (0x80 | (c & 0x3F))); + } else { + success = writeByte(f, (byte) (0xE0 | (c >> 12))); + success = success && writeByte(f, (byte) (0x80 | ((c >> 6) & 0x3F))); + success = success && writeByte(f, (byte) (0x80 | (c & 0x3F))); + } + return success; + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + private static int getUnflushedDataSize(BufferedFile f) { + UnsignedWord result = f.getBufferPos().subtract(getBufferStart(f)); + assert result.belowOrEqual(BUFFER_SIZE); + return (int) result.rawValue(); + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + private static Pointer getBufferStart(BufferedFile f) { + return ((Pointer) f).add(SizeOf.unsigned(BufferedFile.class)); + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + private boolean ensureBufferSpace(BufferedFile f, int size) { + assert size < BUFFER_SIZE : "only called for small data"; + if (getUnflushedDataSize(f) + size >= BUFFER_SIZE) { + return flush(f); + } + return true; + } + + @Fold + static RawFileOperationSupport rawFiles() { + /* The byte order does not matter because we only use byte-order-independent methods. */ + return RawFileOperationSupport.nativeByteOrder(); + } + + @RawStructure + public interface BufferedFile extends PointerBase { + @RawField + RawFileDescriptor getFileDescriptor(); + + @RawField + void setFileDescriptor(RawFileDescriptor value); + + @RawField + Pointer getBufferPos(); + + @RawField + void setBufferPos(Pointer value); + + @RawField + long getFilePosition(); + + @RawField + void setFilePosition(long value); + } + + public static class BufferedFileOperationSupportHolder { + private final BufferedFileOperationSupport littleEndian; + private final BufferedFileOperationSupport bigEndian; + private final BufferedFileOperationSupport nativeOrder; + + @Platforms(Platform.HOSTED_ONLY.class) + public BufferedFileOperationSupportHolder() { + ByteOrder nativeByteOrder = ByteOrder.nativeOrder(); + assert nativeByteOrder == ByteOrder.LITTLE_ENDIAN || nativeByteOrder == ByteOrder.BIG_ENDIAN; + + this.littleEndian = new BufferedFileOperationSupport(ByteOrder.LITTLE_ENDIAN == nativeByteOrder); + this.bigEndian = new BufferedFileOperationSupport(ByteOrder.BIG_ENDIAN == nativeByteOrder); + this.nativeOrder = nativeByteOrder == ByteOrder.LITTLE_ENDIAN ? littleEndian : bigEndian; + } + + @Fold + static BufferedFileOperationSupportHolder singleton() { + return ImageSingletons.lookup(BufferedFileOperationSupportHolder.class); + } + } +} + +@AutomaticallyRegisteredFeature +class BufferedFileOperationFeature implements InternalFeature { + @Override + public void beforeAnalysis(BeforeAnalysisAccess access) { + if (ImageSingletons.contains(RawFileOperationSupportHolder.class)) { + ImageSingletons.add(BufferedFileOperationSupportHolder.class, new BufferedFileOperationSupportHolder()); + } + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/os/RawFileOperationSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/os/RawFileOperationSupport.java index 96e092db6906..45cb10c7d108 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/os/RawFileOperationSupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/os/RawFileOperationSupport.java @@ -28,7 +28,6 @@ import org.graalvm.compiler.api.replacements.Fold; import org.graalvm.word.Pointer; -import org.graalvm.word.SignedWord; import org.graalvm.word.UnsignedWord; import org.graalvm.word.WordBase; @@ -36,8 +35,8 @@ import com.oracle.svm.core.os.AbstractRawFileOperationSupport.RawFileOperationSupportHolder; /** - * Provides an OS-independent abstraction for operations on files. Most of the code is implemented - * in a way that it can be used from uninterruptible code. + * Provides unbuffered, OS-independent operations on files. Most of the code is implemented in a way + * that it can be used from uninterruptible code. */ public interface RawFileOperationSupport { /** @@ -66,20 +65,38 @@ static RawFileOperationSupport nativeByteOrder() { } /** - * Opens or creates a file with the specified {@link FileAccessMode access mode}. + * Creates a file with the specified {@link FileCreationMode creation} and {@link FileAccessMode + * access modes}. * * @return If the operation is successful, it returns the file descriptor. Otherwise, it returns * a value where {@link #isValid} will return false. */ - RawFileDescriptor open(String filename, FileAccessMode mode); + RawFileDescriptor create(String filename, FileCreationMode creationMode, FileAccessMode accessMode); /** - * Opens or creates a file with the specified {@link FileAccessMode access mode}. + * Creates a file with the specified {@link FileCreationMode creation} and {@link FileAccessMode + * access modes}. * * @return If the operation is successful, it returns the file descriptor. Otherwise, it returns * a value where {@link #isValid} will return false. */ - RawFileDescriptor open(File file, FileAccessMode mode); + RawFileDescriptor create(File file, FileCreationMode creationMode, FileAccessMode accessMode); + + /** + * Opens a file with the specified {@link FileAccessMode access mode}. + * + * @return If the operation is successful, it returns the file descriptor. Otherwise, it returns + * a value where {@link #isValid} will return false. + */ + RawFileDescriptor open(String filename, FileAccessMode accessMode); + + /** + * Opens a file with the specified {@link FileAccessMode access mode}. + * + * @return If the operation is successful, it returns the file descriptor. Otherwise, it returns + * a value where {@link #isValid} will return false. + */ + RawFileDescriptor open(File file, FileAccessMode accessMode); /** * Checks if a file descriptor is valid or if it represents an error value. @@ -104,7 +121,7 @@ static RawFileOperationSupport nativeByteOrder() { * returns a value less than 0. */ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - SignedWord size(RawFileDescriptor fd); + long size(RawFileDescriptor fd); /** * Gets the current file position within a file. @@ -113,7 +130,7 @@ static RawFileOperationSupport nativeByteOrder() { * returns a value less than 0. */ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - SignedWord position(RawFileDescriptor fd); + long position(RawFileDescriptor fd); /** * Sets the current file position within a file. @@ -121,7 +138,7 @@ static RawFileOperationSupport nativeByteOrder() { * @return true if the file position was updated to the given value, false otherwise. */ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - boolean seek(RawFileDescriptor fd, SignedWord position); + boolean seek(RawFileDescriptor fd, long position); /** * Writes data to the current file position and advances the file position. @@ -191,6 +208,24 @@ static RawFileOperationSupport nativeByteOrder() { @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) boolean writeLong(RawFileDescriptor fd, long data); + /** + * Writes a float value in the specified byte ordering to the current file position and advances + * the file position. + * + * @return true if the data was written, false otherwise. + */ + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + boolean writeFloat(RawFileDescriptor fd, float data); + + /** + * Writes a double value in the specified byte ordering to the current file position and + * advances the file position. + * + * @return true if the data was written, false otherwise. + */ + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + boolean writeDouble(RawFileDescriptor fd, double data); + /** * Reads up to bufferSize bytes of data from to the current file position and advances the file * position. @@ -199,7 +234,7 @@ static RawFileOperationSupport nativeByteOrder() { * returns a negative value. */ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - SignedWord read(RawFileDescriptor fd, Pointer buffer, UnsignedWord bufferSize); + long read(RawFileDescriptor fd, Pointer buffer, UnsignedWord bufferSize); /** * OS-specific signed value that represents a file descriptor. It is OS-specific which values @@ -209,12 +244,14 @@ static RawFileOperationSupport nativeByteOrder() { interface RawFileDescriptor extends WordBase { } - /** - * The file access modes that can be used when opening/creating a file. - */ + enum FileCreationMode { + CREATE, + CREATE_OR_REPLACE, + } + enum FileAccessMode { READ, READ_WRITE, - WRITE + WRITE, } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/NativeVMOperation.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/NativeVMOperation.java index 46f7cadc0953..0d0a07730d0c 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/NativeVMOperation.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/NativeVMOperation.java @@ -45,7 +45,7 @@ protected NativeVMOperation(VMOperationInfo info) { } public void enqueue(NativeVMOperationData data) { - assert data.getNativeVMOperation() == this; + data.setNativeVMOperation(this); VMOperationControl.get().enqueue(data); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/VMOperationControl.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/VMOperationControl.java index 637da5dd77e8..8c318480b048 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/VMOperationControl.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/VMOperationControl.java @@ -276,6 +276,7 @@ void enqueue(JavaVMOperation operation) { } void enqueue(NativeVMOperationData data) { + assert data.getNativeVMOperation() != null; enqueue(data.getNativeVMOperation(), data); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/threadlocal/VMThreadLocalMTSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/threadlocal/VMThreadLocalMTSupport.java index 93dcadaa2851..bb192cc5a5b2 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/threadlocal/VMThreadLocalMTSupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/threadlocal/VMThreadLocalMTSupport.java @@ -24,14 +24,20 @@ */ package com.oracle.svm.core.threadlocal; +import org.graalvm.compiler.api.replacements.Fold; +import org.graalvm.nativeimage.ImageSingletons; +import org.graalvm.nativeimage.IsolateThread; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; +import org.graalvm.word.Pointer; import com.oracle.svm.core.Uninterruptible; -import com.oracle.svm.core.heap.UnknownObjectField; -import com.oracle.svm.core.heap.UnknownPrimitiveField; import com.oracle.svm.core.c.NonmovableArray; import com.oracle.svm.core.c.NonmovableArrays; +import com.oracle.svm.core.heap.InstanceReferenceMapDecoder; +import com.oracle.svm.core.heap.ObjectReferenceVisitor; +import com.oracle.svm.core.heap.UnknownObjectField; +import com.oracle.svm.core.heap.UnknownPrimitiveField; public class VMThreadLocalMTSupport { @UnknownPrimitiveField public int vmThreadSize = -1; @@ -42,6 +48,11 @@ public class VMThreadLocalMTSupport { public VMThreadLocalMTSupport() { } + @Fold + public static VMThreadLocalMTSupport singleton() { + return ImageSingletons.lookup(VMThreadLocalMTSupport.class); + } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public NonmovableArray getThreadLocalsReferenceMap() { assert vmThreadReferenceMapEncoding != null; @@ -55,4 +66,9 @@ public int getThreadLocalsReferenceMapIndex() { assert (int) result == result; return (int) result; } + + public void walk(IsolateThread isolateThread, ObjectReferenceVisitor referenceVisitor) { + NonmovableArray threadRefMapEncoding = NonmovableArrays.fromImageHeap(vmThreadReferenceMapEncoding); + InstanceReferenceMapDecoder.walkOffsetsFromPointer((Pointer) isolateThread, threadRefMapEncoding, vmThreadReferenceMapIndex, referenceVisitor, null); + } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/AllocationFreeOutputStream.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/coder/ByteStream.java similarity index 66% rename from substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/AllocationFreeOutputStream.java rename to substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/coder/ByteStream.java index fe10df6adff1..61080d02319b 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/AllocationFreeOutputStream.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/coder/ByteStream.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 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 @@ -22,27 +22,18 @@ * or visit www.oracle.com if you need additional information or have any * questions. */ -package com.oracle.svm.core.heapdump; +package com.oracle.svm.core.util.coder; -import java.io.IOException; +import org.graalvm.nativeimage.c.struct.RawField; +import org.graalvm.nativeimage.c.struct.RawStructure; +import org.graalvm.word.Pointer; +import org.graalvm.word.PointerBase; -/** - * Simple interface for writing heap dump to arbitrary data source. - * - */ -public interface AllocationFreeOutputStream { - - void write(int b) throws IOException; - - void write(byte[] b, int offset, int length) throws IOException; - - /** - * close method is called outside of critical section and can allocate objects. - * - * @throws IOException - */ - void close() throws IOException; - - void flush() throws IOException; +@RawStructure +public interface ByteStream extends PointerBase { + @RawField + Pointer getPosition(); + @RawField + void setPosition(Pointer value); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/coder/ByteStreamAccess.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/coder/ByteStreamAccess.java new file mode 100644 index 000000000000..67c3ce021494 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/coder/ByteStreamAccess.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2023, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package com.oracle.svm.core.util.coder; + +import org.graalvm.word.Pointer; + +public class ByteStreamAccess { + public static void initialize(ByteStream stream, Pointer data) { + stream.setPosition(data); + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/coder/NativeCoder.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/coder/NativeCoder.java new file mode 100644 index 000000000000..4cd32e2dabe5 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/coder/NativeCoder.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2023, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package com.oracle.svm.core.util.coder; + +import org.graalvm.word.Pointer; + +public class NativeCoder { + public static byte readByte(ByteStream data) { + Pointer position = data.getPosition(); + byte result = position.readByte(0); + data.setPosition(position.add(Byte.BYTES)); + return result; + } + + public static int readInt(ByteStream data) { + Pointer position = data.getPosition(); + int result = position.readInt(0); + data.setPosition(position.add(Integer.BYTES)); + return result; + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/coder/Pack200Coder.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/coder/Pack200Coder.java new file mode 100644 index 000000000000..1fce2c2ecf4f --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/coder/Pack200Coder.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2023, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package com.oracle.svm.core.util.coder; + +import org.graalvm.compiler.core.common.NumUtil; +import org.graalvm.compiler.core.common.util.UnsafeArrayTypeWriter; +import org.graalvm.nativeimage.StackValue; +import org.graalvm.word.Pointer; + +public class Pack200Coder { + public static long readUV(Pointer data) { + ByteStream stream = StackValue.get(ByteStream.class); + ByteStreamAccess.initialize(stream, data); + return readUV(stream); + } + + /** This code is adapted from TypeReader.getUV(). */ + public static long readUV(ByteStream data) { + Pointer pos = data.getPosition(); + long result = pos.readByte(0) & 0xFF; + pos = pos.add(1); + if (result >= UnsafeArrayTypeWriter.NUM_LOW_CODES) { + long shift = UnsafeArrayTypeWriter.HIGH_WORD_SHIFT; + for (int i = 2;; i++) { + long b = pos.readByte(0) & 0xFF; + pos = pos.add(1); + result += b << shift; + if (b < UnsafeArrayTypeWriter.NUM_LOW_CODES || i == UnsafeArrayTypeWriter.MAX_BYTES) { + break; + } + shift += UnsafeArrayTypeWriter.HIGH_WORD_SHIFT; + } + } + data.setPosition(pos); + return result; + } + + public static int readUVAsInt(Pointer data) { + return NumUtil.safeToInt(readUV(data)); + } + + public static int readUVAsInt(ByteStream data) { + return NumUtil.safeToInt(readUV(data)); + } +} diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/HeapDumpFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/HeapDumpFeature.java new file mode 100644 index 000000000000..0fd5f8f68da1 --- /dev/null +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/HeapDumpFeature.java @@ -0,0 +1,226 @@ +/* + * Copyright (c) 2023, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package com.oracle.svm.hosted.heap; + +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Comparator; + +import org.graalvm.collections.EconomicMap; +import org.graalvm.collections.MapCursor; +import org.graalvm.compiler.core.common.util.TypeConversion; +import org.graalvm.compiler.core.common.util.UnsafeArrayTypeWriter; +import org.graalvm.nativeimage.ImageSingletons; +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.hosted.Feature; +import org.graalvm.nativeimage.impl.HeapDumpSupport; + +import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature; +import com.oracle.svm.core.feature.InternalFeature; +import com.oracle.svm.core.heapdump.HeapDumpSupportImpl; +import com.oracle.svm.core.meta.SharedField; +import com.oracle.svm.core.meta.SharedType; +import com.oracle.svm.core.util.ByteArrayReader; +import com.oracle.svm.hosted.FeatureImpl.AfterCompilationAccessImpl; + +import jdk.vm.ci.meta.ResolvedJavaField; + +@AutomaticallyRegisteredFeature +public class HeapDumpFeature implements InternalFeature { + @Override + public boolean isInConfiguration(IsInConfigurationAccess access) { + /* + * Include the feature unconditionally on Linux and macOS. The code and all its data are + * only present in the final image if the heap dumping infrastructure is called by some + * code. + */ + return Platform.includedIn(Platform.LINUX.class) || Platform.includedIn(Platform.DARWIN.class); + } + + @Override + public void afterRegistration(AfterRegistrationAccess access) { + ImageSingletons.add(HeapDumpSupport.class, new HeapDumpSupportImpl()); + } + + @Override + public void afterCompilation(Feature.AfterCompilationAccess access) { + AfterCompilationAccessImpl accessImpl = (AfterCompilationAccessImpl) access; + byte[] metadata = computeMetadata(accessImpl.getTypes()); + + HeapDumpSupportImpl support = (HeapDumpSupportImpl) ImageSingletons.lookup(HeapDumpSupport.class); + support.setMetadata(metadata); + access.registerAsImmutable(metadata); + } + + /** + * This method writes the metadata that is needed for heap dumping into one large byte[]. The + * format is as follows: + * + *
+     * |----------------------------|
+     * | data in the byte[]         |
+     * |----------------------------|
+     * | s4 totalFieldCount         |
+     * | s4 classCount              |
+     * | s4 fieldNameCount          |
+     * | uv maxTypeId               |
+     * | (class information)*       |
+     * | (field names)*             |
+     * |----------------------------|
+     *
+     * |----------------------------|
+     * | information per class      |
+     * |----------------------------|
+     * | uv typeId                  |
+     * | uv instanceFieldCount      |
+     * | uv staticFieldCount        |
+     * | (instance field)*          |
+     * | (static field)*            |
+     * |----------------------------|
+     *
+     * |----------------------------|
+     * | information per field      |
+     * |----------------------------|
+     * | u1 storageKind             |
+     * | uv fieldNameIndex          |
+     * | uv location                |
+     * |----------------------------|
+     *
+     * |----------------------------|
+     * | information per field name |
+     * |----------------------------|
+     * | uv lengthInBytes           |
+     * | (s1 utf8 character)*       |
+     * |----------------------------|
+     * 
+ */ + private static byte[] computeMetadata(Collection types) { + int maxTypeId = types.stream().mapToInt(t -> t.getHub().getTypeID()).max().orElse(0); + assert maxTypeId > 0; + + UnsafeArrayTypeWriter output = UnsafeArrayTypeWriter.create(ByteArrayReader.supportsUnalignedMemoryAccess()); + writeMetadata(output, types, maxTypeId); + + int length = TypeConversion.asS4(output.getBytesWritten()); + return output.toArray(new byte[length]); + } + + private static void writeMetadata(UnsafeArrayTypeWriter output, Collection types, int maxTypeId) { + /* Write header. */ + long totalFieldCountOffset = output.getBytesWritten(); + output.putS4(0); // total field count (patched later on) + long classCountOffset = output.getBytesWritten(); + output.putS4(0); // class count (patched later on) + long fieldNameCountOffset = output.getBytesWritten(); + output.putS4(0); // field name count (patched later on) + + output.putUV(maxTypeId); + + /* Write the class and field information. */ + int totalFieldCount = 0; + int classCount = 0; + EconomicMap fieldNames = EconomicMap.create(); + for (SharedType type : types) { + if (type.isInstanceClass()) { + ArrayList instanceFields = prepareFields(type.getInstanceFields(false)); + ArrayList staticFields = prepareFields(type.getStaticFields()); + if (instanceFields.size() == 0 && staticFields.size() == 0) { + continue; + } + + classCount++; + totalFieldCount += instanceFields.size() + staticFields.size(); + + output.putUV(type.getHub().getTypeID()); + output.putUV(instanceFields.size()); + output.putUV(staticFields.size()); + + /* Write direct instance fields. */ + for (SharedField field : instanceFields) { + writeField(field, output, fieldNames); + } + + /* Write static fields. */ + for (SharedField field : staticFields) { + writeField(field, output, fieldNames); + } + } + } + + /* Patch the header. */ + output.patchS4(totalFieldCount, totalFieldCountOffset); + output.patchS4(classCount, classCountOffset); + output.patchS4(fieldNames.size(), fieldNameCountOffset); + + /* Write the field names. */ + int index = 0; + MapCursor cursor = fieldNames.getEntries(); + while (cursor.advance()) { + assert cursor.getValue() == index; + byte[] utf8 = cursor.getKey().getBytes(StandardCharsets.UTF_8); + output.putUV(utf8.length); + for (byte b : utf8) { + output.putS1(b); + } + index++; + } + } + + private static ArrayList prepareFields(ResolvedJavaField[] input) { + /* Collect all fields that have a location. */ + ArrayList result = new ArrayList<>(); + for (ResolvedJavaField f : input) { + if (f instanceof SharedField field) { + if (field.getLocation() >= 0) { + result.add(field); + } + } + } + + /* Sort fields by their location. */ + result.sort(Comparator.comparingInt(SharedField::getLocation)); + return result; + } + + private static void writeField(SharedField field, UnsafeArrayTypeWriter output, EconomicMap fieldNames) { + int location = field.getLocation(); + assert location >= 0; + output.putU1(field.getStorageKind().getTypeChar()); + output.putUV(addFieldName(field.getName(), fieldNames)); + output.putUV(location); + } + + private static int addFieldName(String fieldName, EconomicMap fieldNames) { + Integer fieldNameIndex = fieldNames.get(fieldName); + if (fieldNameIndex != null) { + return fieldNameIndex; + } + + int result = fieldNames.size(); + fieldNames.put(fieldName, result); + return result; + } +} diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/HeapDumpFieldsMapFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/HeapDumpFieldsMapFeature.java deleted file mode 100644 index aaff568446ad..000000000000 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/HeapDumpFieldsMapFeature.java +++ /dev/null @@ -1,162 +0,0 @@ -/* - * Copyright (c) 2022, 2022, 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. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * 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. - */ -package com.oracle.svm.hosted.heap; - -import java.io.UnsupportedEncodingException; -import java.util.Collection; - -import org.graalvm.compiler.core.common.util.TypeConversion; -import org.graalvm.compiler.core.common.util.UnsafeArrayTypeWriter; -import org.graalvm.nativeimage.Platform; -import org.graalvm.nativeimage.Platforms; -import org.graalvm.nativeimage.hosted.Feature; - -import com.oracle.svm.core.feature.InternalFeature; -import com.oracle.svm.core.heapdump.HeapDumpUtils; -import com.oracle.svm.core.meta.SharedField; -import com.oracle.svm.core.meta.SharedType; -import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature; -import com.oracle.svm.core.util.ByteArrayReader; -import com.oracle.svm.core.util.VMError; -import com.oracle.svm.hosted.FeatureImpl.AfterCompilationAccessImpl; - -import jdk.vm.ci.meta.ResolvedJavaField; - -class HeapDumpHostedUtils { - - @Platforms(Platform.HOSTED_ONLY.class) - public static byte[] dumpFieldsMap(Collection types) { - UnsafeArrayTypeWriter writeBuffer = UnsafeArrayTypeWriter.create(ByteArrayReader.supportsUnalignedMemoryAccess()); - - writeFieldsInfo(writeBuffer, types); - int length = TypeConversion.asS4(writeBuffer.getBytesWritten()); - return writeBuffer.toArray(new byte[length]); - } - - @Platforms(Platform.HOSTED_ONLY.class) - public static void writeFieldsInfo(UnsafeArrayTypeWriter writeBuffer, Collection types) { - for (SharedType type : types) { - /* I am only interested in instance types. */ - if (type.isInstanceClass()) { - /* Get the direct fields of the class. */ - final ResolvedJavaField[] fields = type.getInstanceFields(false); - /* Get the static fields of the class. */ - final ResolvedJavaField[] sfields = type.getStaticFields(); - /* I am only interested in classes with some fields. */ - if (fields.length == 0 && sfields.length == 0) { - continue; - } - /* Write the class name */ - writeString(writeBuffer, type.toClassName()); - - /* Write each direct field and offset. */ - for (ResolvedJavaField resolvedJavaField : inHotSpotFieldOrder(fields)) { - if (resolvedJavaField instanceof SharedField) { - final SharedField field = (SharedField) resolvedJavaField; - - writeField(field, writeBuffer); - } - } - writeBuffer.putU1(0); - /* Write each static field and offset. */ - for (ResolvedJavaField resolvedJavaField : inHotSpotFieldOrder(sfields)) { - if (resolvedJavaField instanceof SharedField) { - final SharedField field = (SharedField) resolvedJavaField; - if (!field.isWritten()) { - /* I am only interested in fields that are not constants. */ - continue; - } - if (!field.isAccessed()) { - /* I am only interested in fields that are used. */ - continue; - } - writeField(field, writeBuffer); - } - } - writeBuffer.putU1(0); - } - } - } - - @Platforms(Platform.HOSTED_ONLY.class) - private static void writeField(final SharedField field, UnsafeArrayTypeWriter writeBuffer) { - final int location = field.getLocation(); - /* I am only interested in fields that have locations. */ - if (location < 0) { - return; - } - writeString(writeBuffer, field.getName()); - writeBuffer.putU1(field.getJavaKind().getTypeChar()); - writeBuffer.putU1(field.getStorageKind().getTypeChar()); - writeBuffer.putU1((location >>> 24) & 0xFF); - writeBuffer.putU1((location >>> 16) & 0xFF); - writeBuffer.putU1((location >>> 8) & 0xFF); - writeBuffer.putU1((location >>> 0) & 0xFF); - } - - /* - * Write fields in the same order as in HotSpot heap dump. This is the reverse order of what SVM - * hands out. See also GR-6758. - */ - @Platforms(Platform.HOSTED_ONLY.class) - private static ResolvedJavaField[] inHotSpotFieldOrder(ResolvedJavaField[] fields) { - ResolvedJavaField[] reversed = new ResolvedJavaField[fields.length]; - - for (int i = 0; i < fields.length; i++) { - reversed[fields.length - 1 - i] = fields[i]; - } - return reversed; - } - - @Platforms(Platform.HOSTED_ONLY.class) - private static void writeString(UnsafeArrayTypeWriter writeBuffer, String name) { - try { - byte[] buf = name.getBytes("UTF-8"); - for (byte b : buf) { - writeBuffer.putU1(b); - } - writeBuffer.putU1(0); - } catch (UnsupportedEncodingException ex) { - VMError.shouldNotReachHere(ex); - } - } -} - -@AutomaticallyRegisteredFeature -class HeapDumpFieldsMapFeature implements InternalFeature { - - /** - * Write out fields info and their offsets. - */ - @Override - @Platforms(Platform.HOSTED_ONLY.class) - public void afterCompilation(Feature.AfterCompilationAccess access) { - AfterCompilationAccessImpl accessImpl = (AfterCompilationAccessImpl) access; - byte[] fieldMap = HeapDumpHostedUtils.dumpFieldsMap(accessImpl.getTypes()); - - HeapDumpUtils.getHeapDumpUtils().setFieldsMap(fieldMap); - access.registerAsImmutable(fieldMap); - } -} From c0965cf1b583863492d8e59562b84cd65bd1b166 Mon Sep 17 00:00:00 2001 From: Christian Haeubl Date: Mon, 27 Feb 2023 18:27:59 +0100 Subject: [PATCH 2/5] Fixes and improved documentation. --- .../svm/core/code/RuntimeCodeInfoMemory.java | 2 +- ...wableArray.java => GrowableWordArray.java} | 6 ++- ...cess.java => GrowableWordArrayAccess.java} | 49 ++++++++++++++----- .../core/heapdump/HeapDumpSupportImpl.java | 33 +++++++------ .../svm/core/heapdump/HeapDumpWriter.java | 29 +++++------ .../core/os/BufferedFileOperationSupport.java | 29 ++++++----- .../svm/core/os/RawFileOperationSupport.java | 28 ++++++----- .../svm/core/util/coder/ByteStreamAccess.java | 3 ++ .../svm/core/util/coder/NativeCoder.java | 5 ++ .../svm/core/util/coder/Pack200Coder.java | 15 ++++-- 10 files changed, 123 insertions(+), 76 deletions(-) rename substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/collections/{GrowableArray.java => GrowableWordArray.java} (86%) rename substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/collections/{GrowableArrayAccess.java => GrowableWordArrayAccess.java} (64%) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/RuntimeCodeInfoMemory.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/RuntimeCodeInfoMemory.java index f05c8600ffcc..332d10d9eb59 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/RuntimeCodeInfoMemory.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/RuntimeCodeInfoMemory.java @@ -223,7 +223,7 @@ public void walkRuntimeMethodsDuringGC(CodeInfoVisitor visitor) { } } - @Uninterruptible(reason = "Prevent the GC from freeing the CodeInfo object.") + @Uninterruptible(reason = "Prevent the GC from freeing the CodeInfo object until it is tethered.") public void walkRuntimeMethods(CodeInfoVisitor visitor) { if (table.isNonNull()) { int length = NonmovableArrays.lengthOf(table); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/collections/GrowableArray.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/collections/GrowableWordArray.java similarity index 86% rename from substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/collections/GrowableArray.java rename to substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/collections/GrowableWordArray.java index 33280b604f77..a9fc178e9b17 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/collections/GrowableArray.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/collections/GrowableWordArray.java @@ -30,10 +30,12 @@ import org.graalvm.word.PointerBase; /** - * Growable array with word-sized elements. The Word[] is allocated on the C heap. + * Growable array with word-sized elements. The {@link #getData() array elements} are allocated on + * the C heap. The functions in {@link GrowableWordArrayAccess} should be used to access and modify + * this data structure. */ @RawStructure -public interface GrowableArray extends PointerBase { +public interface GrowableWordArray extends PointerBase { @RawField int getSize(); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/collections/GrowableArrayAccess.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/collections/GrowableWordArrayAccess.java similarity index 64% rename from substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/collections/GrowableArrayAccess.java rename to substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/collections/GrowableWordArrayAccess.java index 77014f006880..27103035e7f4 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/collections/GrowableArrayAccess.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/collections/GrowableWordArrayAccess.java @@ -24,24 +24,31 @@ */ package com.oracle.svm.core.collections; +import org.graalvm.compiler.api.replacements.Fold; import org.graalvm.compiler.word.Word; import org.graalvm.nativeimage.ImageSingletons; -import org.graalvm.nativeimage.c.struct.SizeOf; import org.graalvm.nativeimage.c.type.WordPointer; import org.graalvm.nativeimage.impl.UnmanagedMemorySupport; -import org.graalvm.word.UnsignedWord; import org.graalvm.word.WordFactory; +import com.oracle.svm.core.config.ConfigurationValues; import com.oracle.svm.core.headers.LibC; -public class GrowableArrayAccess { - public static void initialize(GrowableArray array) { +public class GrowableWordArrayAccess { + private static final int INITIAL_CAPACITY = 10; + + public static void initialize(GrowableWordArray array) { array.setSize(0); array.setCapacity(0); array.setData(WordFactory.nullPointer()); } - public static boolean add(GrowableArray array, Word element) { + public static Word get(GrowableWordArray array, int i) { + assert i >= 0 && i < array.getSize(); + return array.getData().addressOf(i).read(); + } + + public static boolean add(GrowableWordArray array, Word element) { if (array.getSize() == array.getCapacity() && !grow(array)) { return false; } @@ -51,28 +58,46 @@ public static boolean add(GrowableArray array, Word element) { return true; } - public static void freeData(GrowableArray array) { + public static void freeData(GrowableWordArray array) { ImageSingletons.lookup(UnmanagedMemorySupport.class).free(array.getData()); array.setData(WordFactory.nullPointer()); array.setSize(0); array.setCapacity(0); } - private static boolean grow(GrowableArray array) { - int newCapacity = array.getCapacity() * 2; - WordPointer oldData = array.getData(); + private static boolean grow(GrowableWordArray array) { + int newCapacity = computeNewCapacity(array); + if (newCapacity < 0) { + /* Overflow. */ + return false; + } - UnsignedWord wordSize = SizeOf.unsigned(WordPointer.class); - WordPointer newData = ImageSingletons.lookup(UnmanagedMemorySupport.class).malloc(wordSize.multiply(newCapacity)); + assert newCapacity >= INITIAL_CAPACITY; + WordPointer oldData = array.getData(); + WordPointer newData = ImageSingletons.lookup(UnmanagedMemorySupport.class).malloc(WordFactory.unsigned(newCapacity).multiply(wordSize())); if (newData.isNull()) { return false; } - LibC.memcpy(newData, oldData, wordSize.multiply(array.getSize())); + LibC.memcpy(newData, oldData, WordFactory.unsigned(array.getSize()).multiply(wordSize())); ImageSingletons.lookup(UnmanagedMemorySupport.class).free(oldData); array.setData(newData); array.setCapacity(newCapacity); return true; } + + private static int computeNewCapacity(GrowableWordArray array) { + int oldCapacity = array.getCapacity(); + if (oldCapacity == 0) { + return INITIAL_CAPACITY; + } else { + return oldCapacity * 2; + } + } + + @Fold + static int wordSize() { + return ConfigurationValues.getTarget().wordSize; + } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/HeapDumpSupportImpl.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/HeapDumpSupportImpl.java index b3dbbb5bd3bd..1a49224473b1 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/HeapDumpSupportImpl.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/HeapDumpSupportImpl.java @@ -44,6 +44,7 @@ import com.oracle.svm.core.heap.VMOperationInfos; import com.oracle.svm.core.os.RawFileOperationSupport; import com.oracle.svm.core.os.RawFileOperationSupport.FileCreationMode; +import com.oracle.svm.core.os.RawFileOperationSupport.RawFileDescriptor; import com.oracle.svm.core.thread.NativeVMOperation; import com.oracle.svm.core.thread.NativeVMOperationData; import com.oracle.svm.core.thread.VMOperation; @@ -69,28 +70,32 @@ public void dumpHeap(String outputFile, boolean live) throws IOException { } public void writeHeapTo(String filename, boolean gcBefore) throws IOException { - RawFileOperationSupport.RawFileDescriptor fd = getFileSupport().create(filename, FileCreationMode.CREATE_OR_REPLACE, RawFileOperationSupport.FileAccessMode.READ_WRITE); + RawFileDescriptor fd = getFileSupport().create(filename, FileCreationMode.CREATE_OR_REPLACE, RawFileOperationSupport.FileAccessMode.READ_WRITE); if (!getFileSupport().isValid(fd)) { throw new IOException("Could not create the heap dump file: " + filename); } try { - int size = SizeOf.get(HeapDumpVMOperationData.class); - HeapDumpVMOperationData data = StackValue.get(size); - UnmanagedMemoryUtil.fill((Pointer) data, WordFactory.unsigned(size), (byte) 0); - - data.setGCBefore(gcBefore); - data.setRawFileDescriptor(fd); - heapDumpOperation.enqueue(data); - - if (!data.getSuccess()) { - throw new IOException("An error occurred while writing the heap dump."); - } + writeHeapTo(fd, gcBefore); } finally { getFileSupport().close(fd); } } + public void writeHeapTo(RawFileDescriptor fd, boolean gcBefore) throws IOException { + int size = SizeOf.get(HeapDumpVMOperationData.class); + HeapDumpVMOperationData data = StackValue.get(size); + UnmanagedMemoryUtil.fill((Pointer) data, WordFactory.unsigned(size), (byte) 0); + + data.setGCBefore(gcBefore); + data.setRawFileDescriptor(fd); + heapDumpOperation.enqueue(data); + + if (!data.getSuccess()) { + throw new IOException("An error occurred while writing the heap dump."); + } + } + @Fold static RawFileOperationSupport getFileSupport() { return RawFileOperationSupport.bigEndian(); @@ -105,10 +110,10 @@ private interface HeapDumpVMOperationData extends NativeVMOperationData { void setGCBefore(boolean value); @RawField - RawFileOperationSupport.RawFileDescriptor getRawFileDescriptor(); + RawFileDescriptor getRawFileDescriptor(); @RawField - void setRawFileDescriptor(RawFileOperationSupport.RawFileDescriptor fd); + void setRawFileDescriptor(RawFileDescriptor fd); @RawField boolean getSuccess(); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/HeapDumpWriter.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/HeapDumpWriter.java index 05a056959fc5..0b603b53ef70 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/HeapDumpWriter.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/HeapDumpWriter.java @@ -37,7 +37,6 @@ import org.graalvm.nativeimage.Platforms; import org.graalvm.nativeimage.StackValue; import org.graalvm.nativeimage.c.function.CodePointer; -import org.graalvm.nativeimage.c.type.WordPointer; import org.graalvm.word.Pointer; import org.graalvm.word.UnsignedWord; import org.graalvm.word.WordBase; @@ -56,8 +55,8 @@ import com.oracle.svm.core.code.RuntimeCodeInfoAccess; import com.oracle.svm.core.code.RuntimeCodeInfoMemory; import com.oracle.svm.core.code.SimpleCodeInfoQueryResult; -import com.oracle.svm.core.collections.GrowableArray; -import com.oracle.svm.core.collections.GrowableArrayAccess; +import com.oracle.svm.core.collections.GrowableWordArray; +import com.oracle.svm.core.collections.GrowableWordArrayAccess; import com.oracle.svm.core.config.ConfigurationValues; import com.oracle.svm.core.deopt.DeoptimizedFrame; import com.oracle.svm.core.heap.CodeReferenceMapDecoder; @@ -452,34 +451,32 @@ private void writeStickyClasses() { } private void writeObjects() { - GrowableArray largeImageHeapObjects = StackValue.get(GrowableArray.class); - GrowableArrayAccess.initialize(largeImageHeapObjects); + GrowableWordArray largeImageHeapObjects = StackValue.get(GrowableWordArray.class); + GrowableWordArrayAccess.initialize(largeImageHeapObjects); dumpObjectsVisitor.initialize(true, largeImageHeapObjects); Heap.getHeap().walkImageHeapObjects(dumpObjectsVisitor); - GrowableArray largeCollectedHeapObjects = StackValue.get(GrowableArray.class); - GrowableArrayAccess.initialize(largeCollectedHeapObjects); + GrowableWordArray largeCollectedHeapObjects = StackValue.get(GrowableWordArray.class); + GrowableWordArrayAccess.initialize(largeCollectedHeapObjects); dumpObjectsVisitor.initialize(false, largeCollectedHeapObjects); Heap.getHeap().walkCollectedHeapObjects(dumpObjectsVisitor); /* Large objects are collected and written separately. */ writeLargeObjects(largeImageHeapObjects, true); - GrowableArrayAccess.freeData(largeImageHeapObjects); + GrowableWordArrayAccess.freeData(largeImageHeapObjects); largeImageHeapObjects = WordFactory.nullPointer(); writeLargeObjects(largeCollectedHeapObjects, false); - GrowableArrayAccess.freeData(largeCollectedHeapObjects); + GrowableWordArrayAccess.freeData(largeCollectedHeapObjects); largeCollectedHeapObjects = WordFactory.nullPointer(); } - private void writeLargeObjects(GrowableArray largeObjects, boolean inImageHeap) { + private void writeLargeObjects(GrowableWordArray largeObjects, boolean inImageHeap) { int count = largeObjects.getSize(); - WordPointer objects = largeObjects.getData(); - for (int i = 0; i < count; i++) { - Word rawObj = objects.addressOf(i).read(); + Word rawObj = GrowableWordArrayAccess.get(largeObjects, i); writeObject(rawObj.toObject(), inImageHeap); } } @@ -1077,14 +1074,14 @@ private int getLineNumber(FrameInfoQueryResult frame) { private class DumpObjectsVisitor implements ObjectVisitor { private boolean inImageHeap; - private GrowableArray largeObjects; + private GrowableWordArray largeObjects; @Platforms(Platform.HOSTED_ONLY.class) DumpObjectsVisitor() { } @SuppressWarnings("hiding") - public void initialize(boolean inImageHeap, GrowableArray largeObjects) { + public void initialize(boolean inImageHeap, GrowableWordArray largeObjects) { this.inImageHeap = inImageHeap; this.largeObjects = largeObjects; } @@ -1092,7 +1089,7 @@ public void initialize(boolean inImageHeap, GrowableArray largeObjects) { @Override public boolean visitObject(Object obj) { if (isLarge(obj)) { - boolean added = GrowableArrayAccess.add(largeObjects, Word.objectToUntrackedPointer(obj)); + boolean added = GrowableWordArrayAccess.add(largeObjects, Word.objectToUntrackedPointer(obj)); if (!added) { Log.log().string("Failed to add an element to the large object list. Heap dump will be incomplete.").newline(); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/os/BufferedFileOperationSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/os/BufferedFileOperationSupport.java index 452399d0ba58..98a1f8bce80b 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/os/BufferedFileOperationSupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/os/BufferedFileOperationSupport.java @@ -58,8 +58,7 @@ */ public class BufferedFileOperationSupport { /** - * Returns a {@link BufferedFileOperationSupport} singleton that uses little endian byte - * ordering. + * Returns a {@link BufferedFileOperationSupport} singleton that uses little endian byte order. */ @Fold public static BufferedFileOperationSupport littleEndian() { @@ -67,7 +66,7 @@ public static BufferedFileOperationSupport littleEndian() { } /** - * Returns a {@link BufferedFileOperationSupport} singleton that uses big endian byte ordering. + * Returns a {@link BufferedFileOperationSupport} singleton that uses big endian byte order. */ @Fold public static BufferedFileOperationSupport bigEndian() { @@ -75,8 +74,8 @@ public static BufferedFileOperationSupport bigEndian() { } /** - * Returns a {@link BufferedFileOperationSupport} singleton that uses the native byte ordering - * of the underlying architecture. + * Returns a {@link BufferedFileOperationSupport} singleton that uses the native byte order of + * the underlying architecture. */ @Fold public static BufferedFileOperationSupport nativeByteOrder() { @@ -243,7 +242,7 @@ public boolean writeByte(BufferedFile f, byte data) { } /** - * Writes a short value in the specified byte ordering to the current file position and advances + * Writes a short value in the specified byte order to the current file position and advances * the file position. * * @return true if the data was written, false otherwise. @@ -261,8 +260,8 @@ public boolean writeShort(BufferedFile f, short data) { } /** - * Writes a char value in the specified byte ordering to the current file position and advances - * the file position. + * Writes a char value in the specified byte order to the current file position and advances the + * file position. * * @return true if the data was written, false otherwise. */ @@ -279,8 +278,8 @@ public boolean writeChar(BufferedFile f, char data) { } /** - * Writes an integer value in the specified byte ordering to the current file position and - * advances the file position. + * Writes an integer value in the specified byte order to the current file position and advances + * the file position. * * @return true if the data was written, false otherwise. */ @@ -297,8 +296,8 @@ public boolean writeInt(BufferedFile f, int data) { } /** - * Writes a long value in the specified byte ordering to the current file position and advances - * the file position. + * Writes a long value in the specified byte order to the current file position and advances the + * file position. * * @return true if the v was written, false otherwise. */ @@ -315,7 +314,7 @@ public boolean writeLong(BufferedFile f, long v) { } /** - * Writes a float value in the specified byte ordering to the current file position and advances + * Writes a float value in the specified byte order to the current file position and advances * the file position. * * @return true if the data was written, false otherwise. @@ -326,8 +325,8 @@ public boolean writeFloat(BufferedFile f, float v) { } /** - * Writes a double value in the specified byte ordering to the current file position and - * advances the file position. + * Writes a double value in the specified byte order to the current file position and advances + * the file position. * * @return true if the data was written, false otherwise. */ diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/os/RawFileOperationSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/os/RawFileOperationSupport.java index 45cb10c7d108..d37efe523e85 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/os/RawFileOperationSupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/os/RawFileOperationSupport.java @@ -40,7 +40,7 @@ */ public interface RawFileOperationSupport { /** - * Returns a {@link RawFileOperationSupport} singleton that uses little endian byte ordering. + * Returns a {@link RawFileOperationSupport} singleton that uses little endian byte order. */ @Fold static RawFileOperationSupport littleEndian() { @@ -48,7 +48,7 @@ static RawFileOperationSupport littleEndian() { } /** - * Returns a {@link RawFileOperationSupport} singleton that uses big endian byte ordering. + * Returns a {@link RawFileOperationSupport} singleton that uses big endian byte order. */ @Fold static RawFileOperationSupport bigEndian() { @@ -56,7 +56,7 @@ static RawFileOperationSupport bigEndian() { } /** - * Returns a {@link RawFileOperationSupport} singleton that uses the native byte ordering of the + * Returns a {@link RawFileOperationSupport} singleton that uses the native byte order of the * underlying architecture. */ @Fold @@ -173,7 +173,7 @@ static RawFileOperationSupport nativeByteOrder() { boolean writeByte(RawFileDescriptor fd, byte data); /** - * Writes a short value in the specified byte ordering to the current file position and advances + * Writes a short value in the specified byte order to the current file position and advances * the file position. * * @return true if the data was written, false otherwise. @@ -182,8 +182,8 @@ static RawFileOperationSupport nativeByteOrder() { boolean writeShort(RawFileDescriptor fd, short data); /** - * Writes a char value in the specified byte ordering to the current file position and advances - * the file position. + * Writes a char value in the specified byte order to the current file position and advances the + * file position. * * @return true if the data was written, false otherwise. */ @@ -191,8 +191,8 @@ static RawFileOperationSupport nativeByteOrder() { boolean writeChar(RawFileDescriptor fd, char data); /** - * Writes an integer value in the specified byte ordering to the current file position and - * advances the file position. + * Writes an integer value in the specified byte order to the current file position and advances + * the file position. * * @return true if the data was written, false otherwise. */ @@ -200,8 +200,8 @@ static RawFileOperationSupport nativeByteOrder() { boolean writeInt(RawFileDescriptor fd, int data); /** - * Writes a long value in the specified byte ordering to the current file position and advances - * the file position. + * Writes a long value in the specified byte order to the current file position and advances the + * file position. * * @return true if the data was written, false otherwise. */ @@ -209,7 +209,7 @@ static RawFileOperationSupport nativeByteOrder() { boolean writeLong(RawFileDescriptor fd, long data); /** - * Writes a float value in the specified byte ordering to the current file position and advances + * Writes a float value in the specified byte order to the current file position and advances * the file position. * * @return true if the data was written, false otherwise. @@ -218,8 +218,8 @@ static RawFileOperationSupport nativeByteOrder() { boolean writeFloat(RawFileDescriptor fd, float data); /** - * Writes a double value in the specified byte ordering to the current file position and - * advances the file position. + * Writes a double value in the specified byte order to the current file position and advances + * the file position. * * @return true if the data was written, false otherwise. */ @@ -245,7 +245,9 @@ interface RawFileDescriptor extends WordBase { } enum FileCreationMode { + /** Create the file if it doesn't exist. Fail if it already exists. */ CREATE, + /** Create the file if it doesn't exist. If it already exists, then truncate the file. */ CREATE_OR_REPLACE, } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/coder/ByteStreamAccess.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/coder/ByteStreamAccess.java index 67c3ce021494..da76ccba9289 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/coder/ByteStreamAccess.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/coder/ByteStreamAccess.java @@ -26,7 +26,10 @@ import org.graalvm.word.Pointer; +import com.oracle.svm.core.Uninterruptible; + public class ByteStreamAccess { + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public static void initialize(ByteStream stream, Pointer data) { stream.setPosition(data); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/coder/NativeCoder.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/coder/NativeCoder.java index 4cd32e2dabe5..b11eeff2a0aa 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/coder/NativeCoder.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/coder/NativeCoder.java @@ -26,7 +26,11 @@ import org.graalvm.word.Pointer; +import com.oracle.svm.core.Uninterruptible; + +/** Uses the native, architecture-specific byte order to access {@link ByteStream} data. */ public class NativeCoder { + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public static byte readByte(ByteStream data) { Pointer position = data.getPosition(); byte result = position.readByte(0); @@ -34,6 +38,7 @@ public static byte readByte(ByteStream data) { return result; } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public static int readInt(ByteStream data) { Pointer position = data.getPosition(); int result = position.readInt(0); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/coder/Pack200Coder.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/coder/Pack200Coder.java index 1fce2c2ecf4f..2763e3e7b192 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/coder/Pack200Coder.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/coder/Pack200Coder.java @@ -24,12 +24,14 @@ */ package com.oracle.svm.core.util.coder; -import org.graalvm.compiler.core.common.NumUtil; import org.graalvm.compiler.core.common.util.UnsafeArrayTypeWriter; import org.graalvm.nativeimage.StackValue; import org.graalvm.word.Pointer; +import com.oracle.svm.core.Uninterruptible; + public class Pack200Coder { + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public static long readUV(Pointer data) { ByteStream stream = StackValue.get(ByteStream.class); ByteStreamAccess.initialize(stream, data); @@ -37,6 +39,7 @@ public static long readUV(Pointer data) { } /** This code is adapted from TypeReader.getUV(). */ + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public static long readUV(ByteStream data) { Pointer pos = data.getPosition(); long result = pos.readByte(0) & 0xFF; @@ -57,11 +60,17 @@ public static long readUV(ByteStream data) { return result; } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public static int readUVAsInt(Pointer data) { - return NumUtil.safeToInt(readUV(data)); + long result = readUV(data); + assert (int) result == result; + return (int) result; } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public static int readUVAsInt(ByteStream data) { - return NumUtil.safeToInt(readUV(data)); + long result = readUV(data); + assert (int) result == result; + return (int) result; } } From 2771a758f659b8da2f10e8e6637ca90d52f69206 Mon Sep 17 00:00:00 2001 From: Christian Haeubl Date: Tue, 28 Feb 2023 14:31:22 +0100 Subject: [PATCH 3/5] Various cleanups and more documentation. --- .../collections/GrowableWordArrayAccess.java | 15 +- .../svm/core/heapdump/HProfSubRecord.java | 2 +- .../core/heapdump/HProfTopLevelRecord.java | 2 +- .../oracle/svm/core/heapdump/HProfType.java | 24 +- .../svm/core/heapdump/HeapDumpMetadata.java | 47 +- .../core/heapdump/HeapDumpSupportImpl.java | 23 +- .../svm/core/heapdump/HeapDumpWriter.java | 406 +++++++++--------- .../oracle/svm/core/heapdump/StorageKind.java | 51 --- .../core/os/BufferedFileOperationSupport.java | 34 +- .../svm/core/util/coder/Pack200Coder.java | 1 + .../svm/hosted/heap/HeapDumpFeature.java | 40 +- 11 files changed, 316 insertions(+), 329 deletions(-) delete mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/StorageKind.java diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/collections/GrowableWordArrayAccess.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/collections/GrowableWordArrayAccess.java index 27103035e7f4..f1a190f7fcc1 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/collections/GrowableWordArrayAccess.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/collections/GrowableWordArrayAccess.java @@ -29,10 +29,11 @@ import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.c.type.WordPointer; import org.graalvm.nativeimage.impl.UnmanagedMemorySupport; +import org.graalvm.word.Pointer; import org.graalvm.word.WordFactory; +import com.oracle.svm.core.UnmanagedMemoryUtil; import com.oracle.svm.core.config.ConfigurationValues; -import com.oracle.svm.core.headers.LibC; public class GrowableWordArrayAccess { private static final int INITIAL_CAPACITY = 10; @@ -59,10 +60,12 @@ public static boolean add(GrowableWordArray array, Word element) { } public static void freeData(GrowableWordArray array) { - ImageSingletons.lookup(UnmanagedMemorySupport.class).free(array.getData()); - array.setData(WordFactory.nullPointer()); - array.setSize(0); - array.setCapacity(0); + if (array.isNonNull()) { + ImageSingletons.lookup(UnmanagedMemorySupport.class).free(array.getData()); + array.setData(WordFactory.nullPointer()); + array.setSize(0); + array.setCapacity(0); + } } private static boolean grow(GrowableWordArray array) { @@ -79,7 +82,7 @@ private static boolean grow(GrowableWordArray array) { return false; } - LibC.memcpy(newData, oldData, WordFactory.unsigned(array.getSize()).multiply(wordSize())); + UnmanagedMemoryUtil.copyForward((Pointer) oldData, (Pointer) newData, WordFactory.unsigned(array.getSize()).multiply(wordSize())); ImageSingletons.lookup(UnmanagedMemorySupport.class).free(oldData); array.setData(newData); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/HProfSubRecord.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/HProfSubRecord.java index 5de899cca585..044984dbc5a3 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/HProfSubRecord.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/HProfSubRecord.java @@ -26,7 +26,7 @@ import org.graalvm.compiler.core.common.NumUtil; -/** See hprofTag in HotSpot. */ +/* Enum of all relevant HPROF sub-records (see enum hprofTag in HotSpot). */ public enum HProfSubRecord { GC_ROOT_UNKNOWN(0xFF), GC_ROOT_JNI_GLOBAL(0x01), diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/HProfTopLevelRecord.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/HProfTopLevelRecord.java index 2e5208ca5901..4b083d71390b 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/HProfTopLevelRecord.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/HProfTopLevelRecord.java @@ -26,7 +26,7 @@ import org.graalvm.compiler.core.common.NumUtil; -/** See hprofTag in HotSpot. */ +/* Enum of all relevant HPROF top-level records (see enum hprofTag in HotSpot). */ public enum HProfTopLevelRecord { UTF8(0x01), LOAD_CLASS(0x02), diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/HProfType.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/HProfType.java index 7c3984b35aef..50618b9ebe1c 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/HProfType.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/HProfType.java @@ -27,9 +27,8 @@ import org.graalvm.compiler.core.common.NumUtil; import com.oracle.svm.core.config.ConfigurationValues; -import com.oracle.svm.core.util.VMError; -/** See hprofTag in HotSpot. */ +/* Enum of all relevant HPROF types (see enum hprofTag in HotSpot). */ public enum HProfType { NORMAL_OBJECT(0x2, 0), BOOLEAN(0x4, 1), @@ -41,6 +40,8 @@ public enum HProfType { INT(0xA, 4), LONG(0xB, 8); + private static final HProfType[] TYPES = HProfType.values(); + private final byte value; private final int size; @@ -49,6 +50,10 @@ public enum HProfType { this.size = size; } + public static HProfType get(byte value) { + return TYPES[value]; + } + public byte getValue() { return value; } @@ -59,19 +64,4 @@ public int getSize() { } return size; } - - public static HProfType fromStorageKind(byte storageKind) { - return switch (storageKind) { - case StorageKind.OBJECT -> HProfType.NORMAL_OBJECT; - case StorageKind.BOOLEAN -> HProfType.BOOLEAN; - case StorageKind.CHAR -> HProfType.CHAR; - case StorageKind.FLOAT -> HProfType.FLOAT; - case StorageKind.DOUBLE -> HProfType.DOUBLE; - case StorageKind.BYTE -> HProfType.BYTE; - case StorageKind.SHORT -> HProfType.SHORT; - case StorageKind.INT -> HProfType.INT; - case StorageKind.LONG -> HProfType.LONG; - default -> throw VMError.shouldNotReachHere("Unexpected storage kind."); - }; - } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/HeapDumpMetadata.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/HeapDumpMetadata.java index 6ad163be3dbe..f8b7a9325d44 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/HeapDumpMetadata.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/HeapDumpMetadata.java @@ -47,6 +47,9 @@ import com.oracle.svm.core.util.coder.NativeCoder; import com.oracle.svm.core.util.coder.Pack200Coder; +/** + * Provides access to the encoded heap dump metadata that was prepared at image build-time. + */ class HeapDumpMetadata { private static final ComputeHubDataVisitor COMPUTE_HUB_DATA_VISITOR = new ComputeHubDataVisitor(); @@ -56,7 +59,7 @@ class HeapDumpMetadata { private static FieldInfoPointer fieldInfoTable; private static FieldNamePointer fieldNameTable; - public static boolean allocate(byte[] metadata) { + public static boolean initialize(byte[] metadata) { assert classInfos.isNull() && fieldInfoTable.isNull() && fieldNameTable.isNull(); Pointer start = NonmovableArrays.getArrayBase(NonmovableArrays.fromImageHeap(metadata)); @@ -73,8 +76,9 @@ public static boolean allocate(byte[] metadata) { classInfoCount = maxTypeId + 1; /* - * Precompute some data structures so that the heap dumping can access the encoded data more - * efficiently. + * Precompute a few small data structures so that the heap dumping code can access the + * encoded data more efficiently. At first, we allocate some memory for those data + * structures so that we can abort right away in case that an allocation fails. */ UnsignedWord classInfosSize = WordFactory.unsigned(classInfoCount).multiply(SizeOf.get(ClassInfo.class)); classInfos = ImageSingletons.lookup(UnmanagedMemorySupport.class).calloc(classInfosSize); @@ -137,7 +141,7 @@ public static boolean allocate(byte[] metadata) { COMPUTE_HUB_DATA_VISITOR.initialize(); Heap.getHeap().walkImageHeapObjects(COMPUTE_HUB_DATA_VISITOR); - /* Compute the size that the instance fields of the classes. */ + /* Compute the size of the instance fields per class. */ for (int i = 0; i < classInfoCount; i++) { ClassInfo classInfo = HeapDumpMetadata.getClassInfo(i); if (ClassInfoAccess.isValid(classInfo)) { @@ -147,7 +151,8 @@ public static boolean allocate(byte[] metadata) { return true; } - public static void free() { + /** Must always be called, regardless if {@link #initialize} returned true or false. */ + public static void teardown() { ImageSingletons.lookup(UnmanagedMemorySupport.class).free(classInfos); classInfos = WordFactory.nullPointer(); @@ -163,6 +168,9 @@ public static int getClassInfoCount() { } public static ClassInfo getClassInfo(Class clazz) { + if (clazz == null) { + return WordFactory.nullPointer(); + } return getClassInfo(DynamicHub.fromClass(clazz)); } @@ -181,8 +189,8 @@ public static int getFieldNameCount() { return fieldNameCount; } - public static FieldNamePointer getFieldNameTable() { - return fieldNameTable; + public static FieldName getFieldName(int index) { + return fieldNameTable.addressOf(index).read(); } /** @@ -213,8 +221,8 @@ static int computeFieldsDumpSize(FieldInfoPointer fields, int fieldCount) { int result = 0; for (int i = 0; i < fieldCount; i++) { FieldInfo field = fields.addressOf(i).read(); - byte storageKind = FieldInfoAccess.getStorageKind(field); - result += StorageKind.getSize(storageKind); + HProfType type = FieldInfoAccess.getType(field); + result += type.getSize(); } return result; } @@ -269,38 +277,45 @@ public interface ClassInfo extends PointerBase { } public static class ClassInfoAccess { + /** + * When iterating over all {@link ClassInfo} objects, it is possible to encounter invalid + * {@link ClassInfo} objects because {@link DynamicHub#getTypeID()} is not necessarily + * continuous (i.e., there may be more type ids than {@link DynamicHub}s). This method can + * be used to determine if a {@link ClassInfo} object is valid. + */ static boolean isValid(ClassInfo classInfo) { return classInfo.getHub() != null; } } + /** Data structure has a variable size. */ @RawStructure public interface FieldInfo extends PointerBase { - // u1 storageKind + // u1 type // uv fieldNameIndex // uv location } public static class FieldInfoAccess { - static byte getStorageKind(FieldInfo field) { - return ((Pointer) field).readByte(0); + static HProfType getType(FieldInfo field) { + return HProfType.get(((Pointer) field).readByte(0)); } static FieldName getFieldName(FieldInfo field) { int fieldNameIndex = Pack200Coder.readUVAsInt(getFieldNameIndexAddress(field)); - return HeapDumpMetadata.getFieldNameTable().addressOf(fieldNameIndex).read(); + return HeapDumpMetadata.getFieldName(fieldNameIndex); } static int getLocation(FieldInfo field) { - Pointer fieldNameIndex = getFieldNameIndexAddress(field); // skip storageKind + Pointer fieldNameIndex = getFieldNameIndexAddress(field); // skip type ByteStream stream = StackValue.get(ByteStream.class); ByteStreamAccess.initialize(stream, fieldNameIndex); - Pack200Coder.readUVAsInt(stream); // skip fieldNameIndex + Pack200Coder.readUVAsInt(stream); // skip field name index return Pack200Coder.readUVAsInt(stream); } static void skipFieldInfo(ByteStream stream) { - NativeCoder.readByte(stream); // storage kind + NativeCoder.readByte(stream); // type Pack200Coder.readUVAsInt(stream); // field name index Pack200Coder.readUVAsInt(stream); // location } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/HeapDumpSupportImpl.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/HeapDumpSupportImpl.java index 1a49224473b1..cc1107343de5 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/HeapDumpSupportImpl.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/HeapDumpSupportImpl.java @@ -25,6 +25,7 @@ package com.oracle.svm.core.heapdump; import static com.oracle.svm.core.heap.RestrictHeapAccess.Access.NO_ALLOCATION; +import static com.oracle.svm.core.heap.RestrictHeapAccess.Access.UNRESTRICTED; import java.io.IOException; @@ -42,6 +43,7 @@ import com.oracle.svm.core.UnmanagedMemoryUtil; import com.oracle.svm.core.heap.RestrictHeapAccess; import com.oracle.svm.core.heap.VMOperationInfos; +import com.oracle.svm.core.log.Log; import com.oracle.svm.core.os.RawFileOperationSupport; import com.oracle.svm.core.os.RawFileOperationSupport.FileCreationMode; import com.oracle.svm.core.os.RawFileOperationSupport.RawFileDescriptor; @@ -65,11 +67,7 @@ public void setMetadata(byte[] value) { } @Override - public void dumpHeap(String outputFile, boolean live) throws IOException { - writeHeapTo(outputFile, live); - } - - public void writeHeapTo(String filename, boolean gcBefore) throws IOException { + public void dumpHeap(String filename, boolean gcBefore) throws IOException { RawFileDescriptor fd = getFileSupport().create(filename, FileCreationMode.CREATE_OR_REPLACE, RawFileOperationSupport.FileAccessMode.READ_WRITE); if (!getFileSupport().isValid(fd)) { throw new IOException("Could not create the heap dump file: " + filename); @@ -135,8 +133,19 @@ protected void operate(NativeVMOperationData d) { if (data.getGCBefore()) { System.gc(); } - boolean success = writer.dumpHeap(data.getRawFileDescriptor()); - data.setSuccess(success); + + try { + boolean success = writer.dumpHeap(data.getRawFileDescriptor()); + data.setSuccess(success); + } catch (Throwable e) { + reportError(e); + } + } + + @RestrictHeapAccess(access = UNRESTRICTED, reason = "Error reporting may allocate.") + private void reportError(Throwable e) { + Log.log().string("An exception occurred during heap dumping. The data in the heap dump file may be corrupt.").newline().string(e.getClass().getName()).string(": ") + .string(e.getMessage()); } } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/HeapDumpWriter.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/HeapDumpWriter.java index 0b603b53ef70..ad4856a016c2 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/HeapDumpWriter.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/HeapDumpWriter.java @@ -75,7 +75,6 @@ import com.oracle.svm.core.heapdump.HeapDumpMetadata.FieldInfoPointer; import com.oracle.svm.core.heapdump.HeapDumpMetadata.FieldName; import com.oracle.svm.core.heapdump.HeapDumpMetadata.FieldNameAccess; -import com.oracle.svm.core.heapdump.HeapDumpMetadata.FieldNamePointer; import com.oracle.svm.core.hub.DynamicHub; import com.oracle.svm.core.hub.LayoutEncoding; import com.oracle.svm.core.log.Log; @@ -94,14 +93,13 @@ /** * This class dumps the image heap and the Java heap into a file (HPROF binary format), similar to - * the HotSpot class {@code heapDumper}. The heap dumping needs additional metadata that is encoded - * during the image build and that is stored in the image heap in a compact binary format (see - * {@code HeapDumpFeature}). + * the HotSpot classes in {@code heapDumper.cpp}. The heap dumping needs additional metadata that is + * encoded during the image build and that is stored in the image heap in a compact binary format + * (see {@code HeapDumpFeature} and {@link HeapDumpMetadata}). * * The actual heap dumping logic is executed in a VM operation, so it is safe to iterate over all - * live threads and to, for example, access their thread local values. The heap dumper is - * implemented as a singleton and only a single heap dumping operation can be in progress at a given - * time. + * live threads. The heap dumper is implemented as a singleton and only a single heap dumping + * operation can be in progress at a given time. * * The heap dumping code must not modify the Java heap in any way as this would pollute or corrupt * the heap dump. Therefore, no Java datastructures can be used and all data structures must be @@ -121,13 +119,16 @@ * Other relevant file format aspects: *
    *
  • References to objects or symbols are encoded using their word-sized address. This is done - * regardless if compressed references are enabled or not.
  • + * regardless if compressed references are enabled or not. Tools (such as VisualVM) that read HPROF + * files, therefore use heuristics to detect if compressed references were used at run-time. *
  • Symbols such as class or method names are encoded as UTF8.
  • - *
  • The size of individual records is limited to {@code MAX_UINT}. So, very large arrays that - * have a larger size than {@code MAX_UINT} need to be truncated.
  • + *
  • The size of individual records is limited to {@link #MAX_UNSIGNED_INT}. So, very large arrays + * that have a larger size than {@link #MAX_UNSIGNED_INT} need to be truncated.
  • *
*/ public class HeapDumpWriter { + private static final long MAX_UNSIGNED_INT = (1L << 32) - 1; + private static final int DUMMY_STACK_TRACE_ID = 1; private static final int LARGE_OBJECT_THRESHOLD = 1 * 1024 * 1024; private static final int HEAP_DUMP_SEGMENT_TARGET_SIZE = 1 * 1024 * 1024; @@ -136,7 +137,7 @@ public class HeapDumpWriter { private final DumpStackFrameVisitor dumpStackFrameVisitor = new DumpStackFrameVisitor(); private final DumpObjectsVisitor dumpObjectsVisitor = new DumpObjectsVisitor(); private final CodeMetadataVisitor codeMetadataVisitor = new CodeMetadataVisitor(); - private final ThreadLocalVisitor threadLocalVisitor = new ThreadLocalVisitor(); + private final ThreadLocalsVisitor threadLocalsVisitor = new ThreadLocalsVisitor(); @UnknownObjectField(types = {byte[].class}) private byte[] metadata; private BufferedFile f; @@ -156,6 +157,7 @@ public void setMetadata(byte[] value) { public boolean dumpHeap(RawFileDescriptor fd) { assert VMOperation.isInProgressAtSafepoint(); assert ThreadingSupportImpl.isRecurringCallbackPaused(); + noAllocationVerifier.open(); try { Heap.getHeap().suspendAllocation(); @@ -175,36 +177,45 @@ private boolean dumpHeap0(RawFileDescriptor fd) { return false; } } finally { - reset(); + /* teardown must always be executed, even if the initialization failed. */ + teardown(); } } private boolean initialize(RawFileDescriptor fd) { assert topLevelRecordBegin == -1 && subRecordBegin == -1 && !error; - this.f = getFileSupport().allocate(fd); + + this.f = file().allocate(fd); if (f.isNull()) { return false; } - return HeapDumpMetadata.allocate(metadata); + return HeapDumpMetadata.initialize(metadata); } - private void reset() { - getFileSupport().free(f); + private void teardown() { + HeapDumpMetadata.teardown(); + + assert f.isNull() || error || file().getUnflushedDataSize(f) == 0; + file().free(f); this.f = WordFactory.nullPointer(); + this.topLevelRecordBegin = -1; this.subRecordBegin = -1; this.error = false; - HeapDumpMetadata.free(); } @NeverInline("Starting a stack walk in the caller frame.") private boolean writeHeapDump() { + /* + * Only read the stack pointer for the current thread once. This ensures consistency for all + * the information that we dump about the stack of the current thread. + */ Pointer currentThreadSp = KnownIntrinsics.readCallerStackPointer(); writeHeader(); writeClassNames(); // UTF-8 symbols writeFieldNames(); // UTF-8 symbols - writeLoadClassRecords(); // LOAD_CLASS + writeLoadedClasses(); // LOAD_CLASS writeStackTraces(currentThreadSp); // FRAME and TRACE /* 1..n HEAP_DUMP_SEGMENT records */ @@ -231,7 +242,7 @@ private boolean writeHeapDump() { private void writeHeader() { writeUTF8("JAVA PROFILE 1.0.2"); writeByte((byte) 0); - writeInt(getWordSize()); + writeInt(wordSize()); writeLong(System.currentTimeMillis()); } @@ -244,7 +255,7 @@ private void startTopLevelRecord(HProfTopLevelRecord tag) { } private void endTopLevelRecord() { - assert topLevelRecordBegin >= 0; + assert topLevelRecordBegin > 0; long currentPosition = getPosition(); setPosition(topLevelRecordBegin - Integer.BYTES); writeInt(NumUtil.safeToUInt(currentPosition - topLevelRecordBegin)); @@ -253,7 +264,7 @@ private void endTopLevelRecord() { } private void startSubRecord(HProfSubRecord tag, long size) { - assert topLevelRecordBegin >= 0 : "must be within a HEAP_DUMP_SEGMENT"; + assert topLevelRecordBegin > 0 : "must be within a HEAP_DUMP_SEGMENT"; long heapDumpSegmentSize = getPosition() - topLevelRecordBegin; if (heapDumpSegmentSize > 0 && heapDumpSegmentSize + size > HEAP_DUMP_SEGMENT_TARGET_SIZE) { endTopLevelRecord(); @@ -265,7 +276,7 @@ private void startSubRecord(HProfSubRecord tag, long size) { } private void endSubRecord(long recordSize) { - assert subRecordBegin >= 0; + assert subRecordBegin > 0; assert subRecordBegin + recordSize == getPosition(); subRecordBegin = -1; } @@ -294,14 +305,13 @@ private void writeSymbol(FieldName fieldName) { } private void writeFieldNames() { - FieldNamePointer fieldNameTable = HeapDumpMetadata.getFieldNameTable(); for (int i = 0; i < HeapDumpMetadata.getFieldNameCount(); i++) { - FieldName fieldName = fieldNameTable.addressOf(i).read(); + FieldName fieldName = HeapDumpMetadata.getFieldName(i); writeSymbol(fieldName); } } - private void writeLoadClassRecords() { + private void writeLoadedClasses() { for (int i = 0; i < HeapDumpMetadata.getClassInfoCount(); i++) { ClassInfo classInfo = HeapDumpMetadata.getClassInfo(i); if (ClassInfoAccess.isValid(classInfo)) { @@ -374,12 +384,11 @@ private void writeClasses() { } private void writeClassDumpRecord(ClassInfo classInfo) { - int wordSize = getWordSize(); int staticFieldsCount = classInfo.getStaticFieldCount(); - int staticFieldsSize = staticFieldsCount * (wordSize + 1) + HeapDumpMetadata.computeFieldsDumpSize(classInfo.getStaticFields(), classInfo.getStaticFieldCount()); + int staticFieldsSize = staticFieldsCount * (wordSize() + 1) + HeapDumpMetadata.computeFieldsDumpSize(classInfo.getStaticFields(), classInfo.getStaticFieldCount()); int instanceFieldsCount = classInfo.getInstanceFieldCount(); - int instanceFieldsSize = instanceFieldsCount * (wordSize + 1); - int recordSize = 1 + wordSize + 4 + 6 * wordSize + 4 + 2 + 2 + staticFieldsSize + 2 + instanceFieldsSize; + int instanceFieldsSize = instanceFieldsCount * (wordSize() + 1); + int recordSize = 1 + wordSize() + 4 + 6 * wordSize() + 4 + 2 + 2 + staticFieldsSize + 2 + instanceFieldsSize; Class clazz = DynamicHub.toClass(classInfo.getHub()); startSubRecord(HProfSubRecord.GC_CLASS_DUMP, recordSize); @@ -398,6 +407,47 @@ private void writeClassDumpRecord(ClassInfo classInfo) { endSubRecord(recordSize); } + private void writeFieldDescriptors(int fieldCount, FieldInfoPointer fieldInfos, boolean isStatic) { + writeShort(NumUtil.safeToUShort(fieldCount)); + for (int i = 0; i < fieldCount; i++) { + FieldInfo field = fieldInfos.addressOf(i).read(); + writeFieldNameId(FieldInfoAccess.getFieldName(field)); + HProfType type = FieldInfoAccess.getType(field); + writeType(type); + + if (isStatic) { + /* For static fields, write the field value to the heap dump as well. */ + Object dataHolder = getStaticFieldDataHolder(type); + writeFieldData(dataHolder, field); + } + } + } + + private static Object getStaticFieldDataHolder(HProfType type) { + if (type == HProfType.NORMAL_OBJECT) { + return StaticFieldsSupport.getStaticObjectFields(); + } else { + return StaticFieldsSupport.getStaticPrimitiveFields(); + } + } + + private void writeFieldData(Object dataHolder, FieldInfo field) { + Pointer p = Word.objectToUntrackedPointer(dataHolder); + int location = FieldInfoAccess.getLocation(field); + HProfType type = FieldInfoAccess.getType(field); + switch (type) { + case BOOLEAN, BYTE -> writeByte(p.readByte(location)); + case CHAR -> writeChar(p.readChar(location)); + case SHORT -> writeShort(p.readShort(location)); + case INT -> writeInt(p.readInt(location)); + case LONG -> writeLong(p.readLong(location)); + case FLOAT -> writeFloat(p.readFloat(location)); + case DOUBLE -> writeDouble(p.readDouble(location)); + case NORMAL_OBJECT -> writeObjectId(ReferenceAccess.singleton().readObjectAt(p.add(location), true)); + default -> throw VMError.shouldNotReachHere("Unexpected type."); + } + } + private void writeThreads(Pointer currentThreadSp) { long nextFrameId = 1; int threadSerialNum = 1; @@ -418,7 +468,7 @@ private void writeThreads(Pointer currentThreadSp) { } private void writeThread(Thread threadObj, int threadSerialNum, int stackTraceSerialNum) { - int recordSize = 1 + getWordSize() + 4 + 4; + int recordSize = 1 + wordSize() + 4 + 4; startSubRecord(HProfSubRecord.GC_ROOT_THREAD_OBJ, recordSize); writeObjectId(threadObj); writeInt(threadSerialNum); @@ -428,8 +478,8 @@ private void writeThread(Thread threadObj, int threadSerialNum, int stackTraceSe private void writeThreadLocals(IsolateThread isolateThread, int threadSerialNum) { if (SubstrateOptions.MultiThreaded.getValue()) { - threadLocalVisitor.initialize(threadSerialNum); - VMThreadLocalMTSupport.singleton().walk(isolateThread, threadLocalVisitor); + threadLocalsVisitor.initialize(threadSerialNum); + VMThreadLocalMTSupport.singleton().walk(isolateThread, threadLocalsVisitor); } } @@ -442,7 +492,7 @@ private void writeStickyClasses() { for (int i = 0; i < HeapDumpMetadata.getClassInfoCount(); i++) { ClassInfo classInfo = HeapDumpMetadata.getClassInfo(i); if (ClassInfoAccess.isValid(classInfo)) { - int recordSize = 1 + getWordSize(); + int recordSize = 1 + wordSize(); startSubRecord(HProfSubRecord.GC_ROOT_STICKY_CLASS, recordSize); writeClassId(classInfo.getHub()); endSubRecord(recordSize); @@ -451,33 +501,28 @@ private void writeStickyClasses() { } private void writeObjects() { - GrowableWordArray largeImageHeapObjects = StackValue.get(GrowableWordArray.class); - GrowableWordArrayAccess.initialize(largeImageHeapObjects); - - dumpObjectsVisitor.initialize(true, largeImageHeapObjects); - Heap.getHeap().walkImageHeapObjects(dumpObjectsVisitor); - - GrowableWordArray largeCollectedHeapObjects = StackValue.get(GrowableWordArray.class); - GrowableWordArrayAccess.initialize(largeCollectedHeapObjects); - - dumpObjectsVisitor.initialize(false, largeCollectedHeapObjects); - Heap.getHeap().walkCollectedHeapObjects(dumpObjectsVisitor); + GrowableWordArray largeObjects = StackValue.get(GrowableWordArray.class); + GrowableWordArrayAccess.initialize(largeObjects); + try { + dumpObjectsVisitor.initialize(largeObjects); + Heap.getHeap().walkImageHeapObjects(dumpObjectsVisitor); - /* Large objects are collected and written separately. */ - writeLargeObjects(largeImageHeapObjects, true); - GrowableWordArrayAccess.freeData(largeImageHeapObjects); - largeImageHeapObjects = WordFactory.nullPointer(); + dumpObjectsVisitor.initialize(largeObjects); + Heap.getHeap().walkCollectedHeapObjects(dumpObjectsVisitor); - writeLargeObjects(largeCollectedHeapObjects, false); - GrowableWordArrayAccess.freeData(largeCollectedHeapObjects); - largeCollectedHeapObjects = WordFactory.nullPointer(); + /* Large objects are collected and written separately. */ + writeLargeObjects(largeObjects); + } finally { + GrowableWordArrayAccess.freeData(largeObjects); + largeObjects = WordFactory.nullPointer(); + } } - private void writeLargeObjects(GrowableWordArray largeObjects, boolean inImageHeap) { + private void writeLargeObjects(GrowableWordArray largeObjects) { int count = largeObjects.getSize(); for (int i = 0; i < count; i++) { Word rawObj = GrowableWordArrayAccess.get(largeObjects, i); - writeObject(rawObj.toObject(), inImageHeap); + writeObject(rawObj.toObject()); } } @@ -508,48 +553,7 @@ private static int getObjectSizeInHeap(Class cls) { } } - private void writeFieldDescriptors(int fieldCount, FieldInfoPointer fieldInfos, boolean staticFields) { - writeShort(NumUtil.safeToUShort(fieldCount)); - for (int i = 0; i < fieldCount; i++) { - FieldInfo field = fieldInfos.addressOf(i).read(); - writeFieldNameId(FieldInfoAccess.getFieldName(field)); - byte typeCode = FieldInfoAccess.getStorageKind(field); - writeType(HProfType.fromStorageKind(typeCode)); - - if (staticFields) { - /* For static fields, write the field value to the heap dump as well. */ - boolean isObjectField = typeCode == StorageKind.OBJECT; - writeField(getStaticFieldData(isObjectField), field); - } - } - } - - private static Object getStaticFieldData(boolean isObjectField) { - if (isObjectField) { - return StaticFieldsSupport.getStaticObjectFields(); - } else { - return StaticFieldsSupport.getStaticPrimitiveFields(); - } - } - - private void writeField(Object obj, FieldInfo field) { - Pointer p = Word.objectToUntrackedPointer(obj); - int location = FieldInfoAccess.getLocation(field); - byte storageKind = FieldInfoAccess.getStorageKind(field); - switch (storageKind) { - case StorageKind.BOOLEAN, StorageKind.BYTE -> writeByte(p.readByte(location)); - case StorageKind.CHAR -> writeChar(p.readChar(location)); - case StorageKind.SHORT -> writeShort(p.readShort(location)); - case StorageKind.INT -> writeInt(p.readInt(location)); - case StorageKind.LONG -> writeLong(p.readLong(location)); - case StorageKind.FLOAT -> writeFloat(p.readFloat(location)); - case StorageKind.DOUBLE -> writeDouble(p.readDouble(location)); - case StorageKind.OBJECT -> writeObjectId(ReferenceAccess.singleton().readObjectAt(p.add(location), true)); - default -> throw VMError.shouldNotReachHere("Unexpected storage kind."); - } - } - - private void writeObject(Object obj, boolean inImageHeap) { + private void writeObject(Object obj) { DynamicHub hub = KnownIntrinsics.readHub(obj); int layoutEncoding = hub.getLayoutEncoding(); if (LayoutEncoding.isArray(layoutEncoding)) { @@ -561,20 +565,20 @@ private void writeObject(Object obj, boolean inImageHeap) { } else { /* * Hybrid objects are handled here as well. This means that the array part of hybrid - * objects is currently skipped. It would be better to dump the array part as a separate - * array object. + * objects is currently skipped. Eventually, we should probably dump the array part as a + * separate object. */ writeInstance(obj); } - if (inImageHeap) { + if (Heap.getHeap().isInImageHeap(obj)) { markImageHeapObjectAsGCRoot(obj); } /* - * Ideally, we would model Java monitors as instance fields (they are only reachable if the - * object that owns the monitor is reachable), however that is not possible because the - * synthetic monitor field may overlap or collide with a normal field of a subclass. + * Ideally, we would model Java monitors as instance fields as they are only reachable if + * the object that owns the monitor is reachable. However, that is not possible because the + * synthetic monitor field could overlap or collide with a normal field of a subclass. * Therefore, we simply mark all monitors as GC roots. */ int monitorOffset = hub.getMonitorOffset(); @@ -586,48 +590,31 @@ private void writeObject(Object obj, boolean inImageHeap) { } } - private void markImageHeapObjectAsGCRoot(Object obj) { - assert Heap.getHeap().isInImageHeap(obj); - markAsGCRoot0(obj); - } - - private void markCodeMetadataAsGCRoot(Object obj) { - markAsGCRoot0(obj); - } - - /** - * We use GC_ROOT_JNI_GLOBAL for all concepts that don't exist on HotSpot, e.g., the image heap - * or {@link DeoptimizedFrame}s. - */ - private void markAsGCRoot0(Object obj) { - int recordSize = 1 + 2 * getWordSize(); - startSubRecord(HProfSubRecord.GC_ROOT_JNI_GLOBAL, recordSize); - writeObjectId(obj); - writeObjectId(null); // global ref ID - endSubRecord(recordSize); - } - private void markMonitorAsGCRoot(Object monitor) { - int recordSize = 1 + getWordSize(); + int recordSize = 1 + wordSize(); startSubRecord(HProfSubRecord.GC_ROOT_MONITOR_USED, recordSize); writeObjectId(monitor); endSubRecord(recordSize); } - private void markThreadLocalAsGCRoot(Object obj, int threadSerialNum) { - int recordSize = 1 + getWordSize() + 4 + 4; - startSubRecord(HProfSubRecord.GC_ROOT_JNI_LOCAL, recordSize); + /** We mark image heap objects as GC_ROOT_JNI_GLOBAL. */ + private void markImageHeapObjectAsGCRoot(Object obj) { + assert Heap.getHeap().isInImageHeap(obj); + markAsJniGlobalGCRoot(obj); + } + + private void markAsJniGlobalGCRoot(Object obj) { + int recordSize = 1 + 2 * wordSize(); + startSubRecord(HProfSubRecord.GC_ROOT_JNI_GLOBAL, recordSize); writeObjectId(obj); - writeInt(threadSerialNum); - writeInt(-1); // empty stack + writeObjectId(null); // global ref ID endSubRecord(recordSize); } private void writeInstance(Object obj) { ClassInfo classInfo = HeapDumpMetadata.getClassInfo(obj.getClass()); - int wordSize = getWordSize(); int instanceFieldsSize = classInfo.getInstanceFieldsDumpSize(); - int recordSize = 1 + wordSize + 4 + wordSize + 4 + instanceFieldsSize; + int recordSize = 1 + wordSize() + 4 + wordSize() + 4 + instanceFieldsSize; startSubRecord(HProfSubRecord.GC_INSTANCE_DUMP, recordSize); writeObjectId(obj); @@ -641,7 +628,7 @@ private void writeInstance(Object obj) { FieldInfoPointer instanceFields = classInfo.getInstanceFields(); for (int i = 0; i < instanceFieldCount; i++) { FieldInfo field = instanceFields.addressOf(i).read(); - writeField(obj, field); + writeFieldData(obj, field); } classInfo = HeapDumpMetadata.getClassInfo(classInfo.getHub().getSuperHub()); } while (classInfo.isNonNull()); @@ -653,7 +640,7 @@ private void writePrimitiveArray(Object array, int layoutEncoding) { int arrayBaseOffset = LayoutEncoding.getArrayBaseOffsetAsInt(layoutEncoding); int elementSize = LayoutEncoding.getArrayIndexScale(layoutEncoding); - int recordHeaderSize = 1 + getWordSize() + 2 * 4 + 1; + int recordHeaderSize = 1 + wordSize() + 2 * 4 + 1; int length = calculateMaxArrayLength(array, elementSize, recordHeaderSize); long recordSize = recordHeaderSize + ((long) length) * elementSize; @@ -690,18 +677,17 @@ private void writePrimitiveArray(Object array, int layoutEncoding) { } else { /* Word arrays are primitive arrays as well */ assert WordBase.class.isAssignableFrom(array.getClass().getComponentType()); - assert elementSize == getWordSize(); + assert elementSize == wordSize(); writeWordArray(array, length, arrayBaseOffset); } endSubRecord(recordSize); } private void writeObjectArray(Object array) { - int wordSize = getWordSize(); - int recordHeaderSize = 1 + 2 * 4 + 2 * wordSize; + int recordHeaderSize = 1 + 2 * 4 + 2 * wordSize(); /* In the heap dump, object array elements are always uncompressed. */ - int length = calculateMaxArrayLength(array, wordSize, recordHeaderSize); - long recordSize = recordHeaderSize + ((long) length) * wordSize; + int length = calculateMaxArrayLength(array, wordSize(), recordHeaderSize); + long recordSize = recordHeaderSize + ((long) length) * wordSize(); startSubRecord(HProfSubRecord.GC_OBJ_ARRAY_DUMP, recordSize); writeObjectId(array); @@ -717,13 +703,13 @@ private void writeObjectArray(Object array) { } /* - * Hprof uses an u4 as the record length field, which means we need to truncate arrays that are - * too long. + * The size of individual HPROF records is limited to {@link #MAX_UNSIGNED_INT}, which means + * that very large arrays need to truncated. */ private static int calculateMaxArrayLength(Object array, int elementSize, int recordHeaderSize) { int length = ArrayLengthNode.arrayLength(array); UnsignedWord lengthInBytes = WordFactory.unsigned(length).multiply(elementSize); - UnsignedWord maxBytes = WordFactory.unsigned((1L << 32) - 1).subtract(recordHeaderSize); + UnsignedWord maxBytes = WordFactory.unsigned(MAX_UNSIGNED_INT).subtract(recordHeaderSize); if (lengthInBytes.belowOrEqual(maxBytes)) { return length; @@ -735,11 +721,11 @@ private static int calculateMaxArrayLength(Object array, int elementSize, int re } private void writeWordArray(Object array, int length, int arrayBaseOffset) { - if (getWordSize() == 8) { + if (wordSize() == 8) { writeType(HProfType.LONG); writeU8ArrayData(array, length, arrayBaseOffset); } else { - assert getWordSize() == 4; + assert wordSize() == 4; writeType(HProfType.INT); writeU4ArrayData(array, length, arrayBaseOffset); } @@ -779,46 +765,46 @@ private static Pointer getArrayData(Object array, int arrayBaseOffset) { } private void writeByte(byte value) { - boolean success = getFileSupport().writeByte(f, value); + boolean success = file().writeByte(f, value); handleError(success); } - private void writeType(HProfType type) { - writeByte(type.getValue()); - } - private void writeShort(short value) { - boolean success = getFileSupport().writeShort(f, value); + boolean success = file().writeShort(f, value); handleError(success); } private void writeChar(char value) { - boolean success = getFileSupport().writeChar(f, value); + boolean success = file().writeChar(f, value); handleError(success); } private void writeInt(int value) { - boolean success = getFileSupport().writeInt(f, value); + boolean success = file().writeInt(f, value); handleError(success); } private void writeLong(long value) { - boolean success = getFileSupport().writeLong(f, value); + boolean success = file().writeLong(f, value); handleError(success); } private void writeFloat(float value) { - boolean success = getFileSupport().writeFloat(f, value); + boolean success = file().writeFloat(f, value); handleError(success); } private void writeDouble(double value) { - boolean success = getFileSupport().writeDouble(f, value); + boolean success = file().writeDouble(f, value); handleError(success); } + private void writeType(HProfType type) { + writeByte(type.getValue()); + } + private void writeObjectId(Object obj) { - writeId(Word.objectToUntrackedPointer(obj).rawValue()); + writeId0(Word.objectToUntrackedPointer(obj).rawValue()); } private void writeClassId(Class clazz) { @@ -840,53 +826,51 @@ private void writeClassId(DynamicHub hub) { if (hubAddress.isNonNull()) { hubAddress = hubAddress.add(1); } - writeId(hubAddress.rawValue()); - } - - private void writeId(long value) { - boolean success; - if (getWordSize() == 8) { - success = getFileSupport().writeLong(f, value); - } else { - assert getWordSize() == 4; - success = getFileSupport().writeInt(f, (int) value); - } - handleError(success); + writeId0(hubAddress.rawValue()); } private void writeFieldNameId(FieldName fieldName) { - boolean success = getFileSupport().writeLong(f, fieldName.rawValue()); - handleError(success); + writeId0(fieldName.rawValue()); } private void writeFrameId(long frameId) { - boolean success = getFileSupport().writeLong(f, frameId); + writeId0(frameId); + } + + private void writeId0(long value) { + boolean success; + if (wordSize() == 8) { + success = file().writeLong(f, value); + } else { + assert wordSize() == 4; + success = file().writeInt(f, (int) value); + } handleError(success); } private void writeUTF8(String value) { - boolean success = getFileSupport().writeUTF8(f, value); + boolean success = file().writeUTF8(f, value); handleError(success); } private void write(Pointer data, UnsignedWord size) { - boolean success = getFileSupport().write(f, data, size); + boolean success = file().write(f, data, size); handleError(success); } private long getPosition() { - long result = getFileSupport().position(f); + long result = file().position(f); handleError(result >= 0); return result; } private void setPosition(long newPos) { - boolean success = getFileSupport().seek(f, newPos); + boolean success = file().seek(f, newPos); handleError(success); } private void flush() { - boolean success = getFileSupport().flush(f); + boolean success = file().flush(f); handleError(success); } @@ -897,12 +881,12 @@ private void handleError(boolean success) { } @Fold - static BufferedFileOperationSupport getFileSupport() { + static BufferedFileOperationSupport file() { return BufferedFileOperationSupport.bigEndian(); } @Fold - static int getWordSize() { + static int wordSize() { return ConfigurationValues.getTarget().wordSize; } @@ -911,14 +895,14 @@ static int getWordSize() { *
    *
  • Write a {@link HProfTopLevelRecord#FRAME} top-level record for every frame on the * stack.
  • - *
  • Write a GC_ROOT sub-record for every deoptimized frame and every references that is on + *
  • Write a GC_ROOT sub-record for every deoptimized frame and for every reference that is on * the stack.
  • *
* * Unfortunately, it is not possible to write all the information in a single pass because the * data needs to end up in different HPROF records (top-level vs. sub-records). The data from - * the different passes needs to match though, therefore we use a {@link #markGCRoots field} to - * determine which data should be written. + * the different passes needs to match, so it is easier to implement both passes in one class + * and to use a {@link #markGCRoots field} to determine which data should be written. */ private class DumpStackFrameVisitor extends StackFrameVisitor implements ObjectReferenceVisitor { private static final int LINE_NUM_NATIVE_METHOD = -3; @@ -963,10 +947,10 @@ protected boolean visitFrame(Pointer sp, CodePointer ip, CodeInfo codeInfo, Deop } else { /* * All references that are on the stack need to be marked as GC roots. Our - * information is not necessarily precise enough to identify to which Java-level - * stack frame a reference belongs. Therefore, we just dump the data in a way that - * it gets associated with the deepest inlined Java-level stack frame of each - * compilation unit. + * information is not necessarily precise enough to identify the exact Java-level + * stack frame to which a reference belongs. Therefore, we just dump the data in a + * way that it gets associated with the deepest inlined Java-level stack frame of + * each compilation unit. */ markStackValuesAsGCRoots(sp, ip, codeInfo); @@ -982,7 +966,7 @@ protected boolean visitFrame(Pointer sp, CodePointer ip, CodeInfo codeInfo, Deop private void markAsGCRoot(DeoptimizedFrame frame) { if (markGCRoots) { - markAsGCRoot0(frame); + markAsJniGlobalGCRoot(frame); } } @@ -1001,12 +985,13 @@ private void markStackValuesAsGCRoots(Pointer sp, CodePointer ip, CodeInfo codeI } @Override + @RestrictHeapAccess(access = NO_ALLOCATION, reason = "Heap dumping must not allocate.") public boolean visitObjectReference(Pointer objRef, boolean compressed, Object holderObject) { assert markGCRoots; Object obj = ReferenceAccess.singleton().readObjectAt(objRef, compressed); if (obj != null) { - int recordSize = 1 + getWordSize() + 4 + 4; + int recordSize = 1 + wordSize() + 4 + 4; startSubRecord(HProfSubRecord.GC_ROOT_JAVA_FRAME, recordSize); writeObjectId(obj); writeInt(threadSerialNum); @@ -1020,42 +1005,36 @@ private void visitFrame(FrameInfoQueryResult frame) { if (!markGCRoots) { /* * Write all UTF-8 symbols that are needed when writing the frame. Ideally, we would - * de-duplicate the symbols but it is not really crucial to do so. + * de-duplicate the symbols, but doing so is not crucial. We also don't support the + * method signature at the moment. */ - String methodName = getMethodName(frame); + String methodName = frame.getSourceMethodName(); + String methodSignature = ""; String sourceFileName = getSourceFileName(frame); writeSymbol(methodName); - writeSymbol(""); // method signature + writeSymbol(methodSignature); writeSymbol(sourceFileName); /* Write the FRAME record. */ ClassInfo classInfo = HeapDumpMetadata.getClassInfo(frame.getSourceClass()); int lineNumber = getLineNumber(frame); - writeFrame(classInfo.getSerialNum(), lineNumber, methodName, sourceFileName); + writeFrame(classInfo.getSerialNum(), lineNumber, methodName, methodSignature, sourceFileName); } } - private void writeFrame(int classSerialNum, int lineNumber, String methodName, String sourceFileName) { + private void writeFrame(int classSerialNum, int lineNumber, String methodName, String methodSignature, String sourceFileName) { assert !markGCRoots; startTopLevelRecord(HProfTopLevelRecord.FRAME); writeFrameId(nextFrameId); writeObjectId(methodName); - writeObjectId(""); // method signature + writeObjectId(methodSignature); writeObjectId(sourceFileName); writeInt(classSerialNum); writeInt(lineNumber); endTopLevelRecord(); } - private String getMethodName(FrameInfoQueryResult frame) { - String methodName = frame.getSourceMethodName(); - if (methodName == null || methodName.isEmpty()) { - methodName = ""; - } - return methodName; - } - private String getSourceFileName(FrameInfoQueryResult frame) { String sourceFileName = frame.getSourceFileName(); if (sourceFileName == null || sourceFileName.isEmpty()) { @@ -1073,7 +1052,6 @@ private int getLineNumber(FrameInfoQueryResult frame) { } private class DumpObjectsVisitor implements ObjectVisitor { - private boolean inImageHeap; private GrowableWordArray largeObjects; @Platforms(Platform.HOSTED_ONLY.class) @@ -1081,22 +1059,21 @@ private class DumpObjectsVisitor implements ObjectVisitor { } @SuppressWarnings("hiding") - public void initialize(boolean inImageHeap, GrowableWordArray largeObjects) { - this.inImageHeap = inImageHeap; + public void initialize(GrowableWordArray largeObjects) { this.largeObjects = largeObjects; } @Override + @RestrictHeapAccess(access = NO_ALLOCATION, reason = "Heap dumping must not allocate.") public boolean visitObject(Object obj) { if (isLarge(obj)) { boolean added = GrowableWordArrayAccess.add(largeObjects, Word.objectToUntrackedPointer(obj)); if (!added) { Log.log().string("Failed to add an element to the large object list. Heap dump will be incomplete.").newline(); } - return true; + } else { + writeObject(obj); } - - writeObject(obj, inImageHeap); return true; } @@ -1111,7 +1088,7 @@ private UnsignedWord getObjectSize(Object obj) { if (LayoutEncoding.isPrimitiveArray(layoutEncoding)) { elementSize = LayoutEncoding.getArrayIndexScale(layoutEncoding); } else { - elementSize = getWordSize(); + elementSize = wordSize(); } int length = ArrayLengthNode.arrayLength(obj); return WordFactory.unsigned(length).multiply(elementSize); @@ -1134,20 +1111,21 @@ public boolean visitCode(CodeInfo info) { } @Override + @RestrictHeapAccess(access = NO_ALLOCATION, reason = "Heap dumping must not allocate.") public boolean visitObjectReference(Pointer objRef, boolean compressed, Object holderObject) { Object obj = ReferenceAccess.singleton().readObjectAt(objRef, compressed); if (obj != null) { - markCodeMetadataAsGCRoot(obj); + markAsJniGlobalGCRoot(obj); } return true; } } - private class ThreadLocalVisitor implements ObjectReferenceVisitor { + private class ThreadLocalsVisitor implements ObjectReferenceVisitor { private int threadSerialNum; @Platforms(Platform.HOSTED_ONLY.class) - ThreadLocalVisitor() { + ThreadLocalsVisitor() { } @SuppressWarnings("hiding") @@ -1156,6 +1134,7 @@ public void initialize(int threadSerialNum) { } @Override + @RestrictHeapAccess(access = NO_ALLOCATION, reason = "Heap dumping must not allocate.") public boolean visitObjectReference(Pointer objRef, boolean compressed, Object holderObject) { Object obj = ReferenceAccess.singleton().readObjectAt(objRef, compressed); if (obj != null) { @@ -1163,5 +1142,14 @@ public boolean visitObjectReference(Pointer objRef, boolean compressed, Object h } return true; } + + private void markThreadLocalAsGCRoot(Object obj, int threadSerialNum) { + int recordSize = 1 + wordSize() + 4 + 4; + startSubRecord(HProfSubRecord.GC_ROOT_JNI_LOCAL, recordSize); + writeObjectId(obj); + writeInt(threadSerialNum); + writeInt(-1); // empty stack + endSubRecord(recordSize); + } } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/StorageKind.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/StorageKind.java deleted file mode 100644 index db946ad45673..000000000000 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/StorageKind.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (c) 2023, 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. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * 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. - */ -package com.oracle.svm.core.heapdump; - -import com.oracle.svm.core.config.ConfigurationValues; -import com.oracle.svm.core.util.VMError; - -public class StorageKind { - public static final byte BOOLEAN = 'Z'; - public static final byte BYTE = 'B'; - public static final byte SHORT = 'S'; - public static final byte CHAR = 'C'; - public static final byte INT = 'I'; - public static final byte FLOAT = 'F'; - public static final byte LONG = 'J'; - public static final byte DOUBLE = 'D'; - public static final byte OBJECT = 'A'; - - public static int getSize(byte storageKind) { - return switch (storageKind) { - case StorageKind.BOOLEAN, StorageKind.BYTE -> 1; - case StorageKind.CHAR, StorageKind.SHORT -> 2; - case StorageKind.INT, StorageKind.FLOAT -> 4; - case StorageKind.OBJECT -> ConfigurationValues.getTarget().wordSize; - case StorageKind.LONG, StorageKind.DOUBLE -> 8; - default -> throw VMError.shouldNotReachHere("Unexpected storage kind."); - }; - } -} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/os/BufferedFileOperationSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/os/BufferedFileOperationSupport.java index 98a1f8bce80b..244b6f0a2ab9 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/os/BufferedFileOperationSupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/os/BufferedFileOperationSupport.java @@ -83,6 +83,7 @@ public static BufferedFileOperationSupport nativeByteOrder() { } private static final int BUFFER_SIZE = 4 * 1024; + private static final int LARGE_DATA_THRESHOLD = 1024; private final boolean useNativeByteOrder; @@ -93,7 +94,11 @@ protected BufferedFileOperationSupport(boolean useNativeByteOrder) { /** * Allocate a {@link BufferedFile} for a {@link RawFileDescriptor}. - **/ + * + * @return a {@link BufferedFile} if the {@link RawFileDescriptor} was + * {@link RawFileOperationSupport#isValid valid} and if the allocation was successful. + * Returns a null pointer otherwise. + */ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public BufferedFile allocate(RawFileDescriptor fd) { if (!rawFiles().isValid(fd)) { @@ -118,8 +123,8 @@ public BufferedFile allocate(RawFileDescriptor fd) { } /** - * Free the {@link BufferedFile} and its corresponding buffer. This operation does neither flush - * pending data nor close the underlying {@link RawFileDescriptor}. + * Free the {@link BufferedFile} and its corresponding buffer. Be aware that this operation does + * neither flush pending data nor close the underlying {@link RawFileDescriptor}. */ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public void free(BufferedFile f) { @@ -127,9 +132,9 @@ public void free(BufferedFile f) { } /** - * Flush the buffered data to the disk. + * Flush the buffered data to the file. * - * @return true if the operation is successful or there was no pending data that needed + * @return true if the data was flushed successful or there was no pending data that needed * flushing. */ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) @@ -143,12 +148,13 @@ public boolean flush(BufferedFile f) { if (success) { f.setBufferPos(getBufferStart(f)); f.setFilePosition(f.getFilePosition() + unflushed); + assert f.getFilePosition() == rawFiles().position(f.getFileDescriptor()); } return success; } /** - * Gets the current file position within a file. + * Gets the current position within a file. * * @return If the operation is successful, it returns the current file position. Otherwise, it * returns a value less than 0. @@ -159,7 +165,8 @@ public long position(BufferedFile f) { } /** - * Sets the current file position within a file. + * Sets the current position within a file. As a side effect of this operation, pending data may + * be flushed. * * @return true if the file position was updated to the given value, false otherwise. */ @@ -179,10 +186,11 @@ public boolean seek(BufferedFile f, long position) { */ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public boolean write(BufferedFile f, Pointer data, UnsignedWord size) { - /* Large data may bypass the buffer and is written directly to the file. */ - if (size.aboveOrEqual(BUFFER_SIZE)) { + /* Large data is written directly to the file without any buffering. */ + if (size.aboveOrEqual(LARGE_DATA_THRESHOLD)) { if (flush(f) && rawFiles().write(f.getFileDescriptor(), data, size)) { f.setFilePosition(f.getFilePosition() + size.rawValue()); + assert f.getFilePosition() == rawFiles().position(f.getFileDescriptor()); assert f.getBufferPos() == getBufferStart(f); return true; } @@ -336,8 +344,8 @@ public boolean writeDouble(BufferedFile f, double v) { } /** - * Writes the String characters UTF8 encoded to the current file position and advances the file - * position. + * Writes the String characters encoded as UTF8 to the current file position and advances the + * file position. * * @return true if the data was written, false otherwise. */ @@ -367,7 +375,7 @@ private boolean writeUTF8(BufferedFile f, char c) { } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - private static int getUnflushedDataSize(BufferedFile f) { + public int getUnflushedDataSize(BufferedFile f) { UnsignedWord result = f.getBufferPos().subtract(getBufferStart(f)); assert result.belowOrEqual(BUFFER_SIZE); return (int) result.rawValue(); @@ -380,7 +388,7 @@ private static Pointer getBufferStart(BufferedFile f) { @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) private boolean ensureBufferSpace(BufferedFile f, int size) { - assert size < BUFFER_SIZE : "only called for small data"; + assert size <= BUFFER_SIZE : "only called for small data"; if (getUnflushedDataSize(f) + size >= BUFFER_SIZE) { return flush(f); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/coder/Pack200Coder.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/coder/Pack200Coder.java index 2763e3e7b192..ac7cf239ea80 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/coder/Pack200Coder.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/coder/Pack200Coder.java @@ -30,6 +30,7 @@ import com.oracle.svm.core.Uninterruptible; +/** Read Pack200 encoded values from a {@link ByteStream} or a raw {@link Pointer}. */ public class Pack200Coder { @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public static long readUV(Pointer data) { diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/HeapDumpFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/HeapDumpFeature.java index 0fd5f8f68da1..89d3bbdce9cc 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/HeapDumpFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/HeapDumpFeature.java @@ -40,22 +40,31 @@ import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature; import com.oracle.svm.core.feature.InternalFeature; +import com.oracle.svm.core.heapdump.HProfType; import com.oracle.svm.core.heapdump.HeapDumpSupportImpl; import com.oracle.svm.core.meta.SharedField; import com.oracle.svm.core.meta.SharedType; import com.oracle.svm.core.util.ByteArrayReader; +import com.oracle.svm.core.util.VMError; import com.oracle.svm.hosted.FeatureImpl.AfterCompilationAccessImpl; import jdk.vm.ci.meta.ResolvedJavaField; +/** + * Heap dumping on Native Image needs some extra metadata about all the classes and fields that are + * present in the image. The necessary information is encoded as binary data at image build time + * (see below). When the heap dumping is triggered at run-time, the metadata is decoded on the fly + * (see {@link com.oracle.svm.core.heapdump.HeapDumpMetadata}) and used for writing the heap dump + * (see {@link com.oracle.svm.core.heapdump.HeapDumpWriter}). + */ @AutomaticallyRegisteredFeature public class HeapDumpFeature implements InternalFeature { @Override public boolean isInConfiguration(IsInConfigurationAccess access) { /* * Include the feature unconditionally on Linux and macOS. The code and all its data are - * only present in the final image if the heap dumping infrastructure is called by some - * code. + * only present in the final image if the heap dumping infrastructure is actually called by + * any code (e.g., VMRuntime.dumpHeap(...) or --enable-monitoring=heapdump). */ return Platform.includedIn(Platform.LINUX.class) || Platform.includedIn(Platform.DARWIN.class); } @@ -81,7 +90,7 @@ public void afterCompilation(Feature.AfterCompilationAccess access) { * *
      * |----------------------------|
-     * | data in the byte[]         |
+     * | metadata byte[]            |
      * |----------------------------|
      * | s4 totalFieldCount         |
      * | s4 classCount              |
@@ -104,7 +113,7 @@ public void afterCompilation(Feature.AfterCompilationAccess access) {
      * |----------------------------|
      * | information per field      |
      * |----------------------------|
-     * | u1 storageKind             |
+     * | u1 type             |
      * | uv fieldNameIndex          |
      * | uv location                |
      * |----------------------------|
@@ -145,8 +154,8 @@ private static void writeMetadata(UnsafeArrayTypeWriter output, Collection fieldNames = EconomicMap.create();
         for (SharedType type : types) {
             if (type.isInstanceClass()) {
-                ArrayList instanceFields = prepareFields(type.getInstanceFields(false));
-                ArrayList staticFields = prepareFields(type.getStaticFields());
+                ArrayList instanceFields = collectFields(type.getInstanceFields(false));
+                ArrayList staticFields = collectFields(type.getStaticFields());
                 if (instanceFields.size() == 0 && staticFields.size() == 0) {
                     continue;
                 }
@@ -189,7 +198,7 @@ private static void writeMetadata(UnsafeArrayTypeWriter output, Collection prepareFields(ResolvedJavaField[] input) {
+    private static ArrayList collectFields(ResolvedJavaField[] input) {
         /* Collect all fields that have a location. */
         ArrayList result = new ArrayList<>();
         for (ResolvedJavaField f : input) {
@@ -208,7 +217,7 @@ private static ArrayList prepareFields(ResolvedJavaField[] input) {
     private static void writeField(SharedField field, UnsafeArrayTypeWriter output, EconomicMap fieldNames) {
         int location = field.getLocation();
         assert location >= 0;
-        output.putU1(field.getStorageKind().getTypeChar());
+        output.putU1(getType(field).ordinal());
         output.putUV(addFieldName(field.getName(), fieldNames));
         output.putUV(location);
     }
@@ -223,4 +232,19 @@ private static int addFieldName(String fieldName, EconomicMap f
         fieldNames.put(fieldName, result);
         return result;
     }
+
+    private static HProfType getType(SharedField field) {
+        return switch (field.getStorageKind()) {
+            case Object -> HProfType.NORMAL_OBJECT;
+            case Boolean -> HProfType.BOOLEAN;
+            case Char -> HProfType.CHAR;
+            case Float -> HProfType.FLOAT;
+            case Double -> HProfType.DOUBLE;
+            case Byte -> HProfType.BYTE;
+            case Short -> HProfType.SHORT;
+            case Int -> HProfType.INT;
+            case Long -> HProfType.LONG;
+            default -> throw VMError.shouldNotReachHere("Unexpected storage kind.");
+        };
+    }
 }

From aac37d4b5dec0dac22e50996cd8311b36884df26 Mon Sep 17 00:00:00 2001
From: Christian Haeubl 
Date: Wed, 1 Mar 2023 12:38:05 +0100
Subject: [PATCH 4/5] Keep the legacy heap dump implementation for now.

---
 .../AllocationFreeFileOutputStreamPosix.java  |  151 ++
 .../dump}/HProfSubRecord.java                 |    2 +-
 .../dump}/HProfTopLevelRecord.java            |    2 +-
 .../{heapdump => heap/dump}/HProfType.java    |    2 +-
 .../dump}/HeapDumpMetadata.java               |    4 +-
 .../core/heap/dump/HeapDumpSupportImpl.java   |  151 ++
 .../svm/core/heap/dump/HeapDumpWriter.java    | 1155 +++++++++++
 .../heapdump/AllocationFreeOutputStream.java  |   49 +
 .../core/heapdump/HeapDumpSupportImpl.java    |  122 +-
 .../svm/core/heapdump/HeapDumpUtils.java      |  351 ++++
 .../svm/core/heapdump/HeapDumpWriter.java     | 1139 +----------
 .../svm/core/heapdump/HeapDumpWriterImpl.java | 1761 +++++++++++++++++
 .../core/os/BufferedFileOperationSupport.java |    5 +-
 .../svm/core/os/RawFileOperationSupport.java  |    6 +
 .../svm/hosted/heap/HeapDumpFeature.java      |   42 +-
 .../hosted/heap/HeapDumpFieldsMapFeature.java |  141 ++
 16 files changed, 3829 insertions(+), 1254 deletions(-)
 create mode 100644 substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/heapdump/AllocationFreeFileOutputStreamPosix.java
 rename substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/{heapdump => heap/dump}/HProfSubRecord.java (98%)
 rename substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/{heapdump => heap/dump}/HProfTopLevelRecord.java (97%)
 rename substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/{heapdump => heap/dump}/HProfType.java (98%)
 rename substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/{heapdump => heap/dump}/HeapDumpMetadata.java (99%)
 create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/dump/HeapDumpSupportImpl.java
 create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/dump/HeapDumpWriter.java
 create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/AllocationFreeOutputStream.java
 create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/HeapDumpUtils.java
 create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/HeapDumpWriterImpl.java
 create mode 100644 substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/HeapDumpFieldsMapFeature.java

diff --git a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/heapdump/AllocationFreeFileOutputStreamPosix.java b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/heapdump/AllocationFreeFileOutputStreamPosix.java
new file mode 100644
index 000000000000..64298d6cc20e
--- /dev/null
+++ b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/heapdump/AllocationFreeFileOutputStreamPosix.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (c) 2022, 2022, 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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.
+ */
+package com.oracle.svm.core.posix.heapdump;
+
+import java.io.FileDescriptor;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+import org.graalvm.nativeimage.Platform;
+import org.graalvm.nativeimage.Platforms;
+import org.graalvm.nativeimage.c.type.CCharPointer;
+import org.graalvm.word.WordFactory;
+
+import com.oracle.svm.core.feature.AutomaticallyRegisteredImageSingleton;
+import com.oracle.svm.core.graal.stackvalue.UnsafeStackValue;
+import com.oracle.svm.core.heapdump.HeapDumpWriterImpl.AllocationFreeFileOutputStream;
+import com.oracle.svm.core.posix.PosixUtils;
+import com.oracle.svm.core.posix.headers.Unistd;
+import com.oracle.svm.core.util.VMError;
+
+/**
+ * Legacy implementation, only used by other legacy code (see GR-44538).
+ *
+ * Posix implementation of allocation-free output stream created from FileOutputStream.
+ *
+ * The limitation to Linux and Darwin is necessary because the implementation currently uses
+ * posix-dependent low-level code. See GR-9725.
+ */
+@AutomaticallyRegisteredImageSingleton(AllocationFreeFileOutputStream.class)
+@Platforms({Platform.LINUX.class, Platform.DARWIN.class})
+final class AllocationFreeFileOutputStreamPosix extends AllocationFreeFileOutputStream {
+
+    /**
+     * Pre-allocated exceptions, for throwing from code that must not allocate.
+     */
+    private static final IOException preallocatedIOException = new IOException("Write failed.");
+    private static final ArrayIndexOutOfBoundsException preallocatedArrayIndexOutOfBoundsException = new ArrayIndexOutOfBoundsException();
+
+    private FileOutputStream fos;
+    private FileDescriptor fileDescriptor;
+
+    AllocationFreeFileOutputStreamPosix() {
+
+    }
+
+    private AllocationFreeFileOutputStreamPosix(FileOutputStream fileOutputStream) throws IOException {
+        fos = fileOutputStream;
+        fileDescriptor = fos.getFD();
+
+        if (!(Platform.includedIn(Platform.LINUX.class) || Platform.includedIn(Platform.DARWIN.class))) {
+            /* See GR-9725 */
+            throw VMError.unsupportedFeature("Heap dump writing currently contains Posix specific code");
+        }
+    }
+
+    @Override
+    public AllocationFreeFileOutputStream newStreamFor(FileOutputStream fileOutputStream) throws IOException {
+        return new AllocationFreeFileOutputStreamPosix(fileOutputStream);
+    }
+
+    @Override
+    public void write(int b) throws IOException {
+        final CCharPointer buffer = UnsafeStackValue.get(CCharPointer.class);
+        buffer.write((byte) b);
+        final boolean writeResult = PosixUtils.writeBytes(fileDescriptor, buffer, WordFactory.unsigned(1));
+        if (!writeResult) {
+            throw preallocatedIOException;
+        }
+    }
+
+    @Override
+    public void write(byte[] b, int off, int len) throws IOException {
+        /* Sanity check the arguments. */
+        if ((b == null) || ((off < 0) || (len < 0) || ((b.length - off) < len))) {
+            throw preallocatedArrayIndexOutOfBoundsException;
+        }
+
+        /*
+         * Stack allocation needs an allocation size that is a compile time constant, so we split
+         * the byte array up in multiple chunks and write them separately.
+         */
+        final int chunkSize = 256;
+        final CCharPointer bytes = UnsafeStackValue.get(chunkSize);
+
+        int chunkOffset = off;
+        int inputLength = len;
+        while (inputLength > 0) {
+            int chunkLength = Math.min(inputLength, chunkSize);
+
+            for (int i = 0; i < chunkLength; i++) {
+                bytes.write(i, b[chunkOffset + i]);
+            }
+
+            if (!PosixUtils.writeBytes(fileDescriptor, bytes, WordFactory.unsigned(chunkLength))) {
+                throw preallocatedIOException;
+            }
+
+            chunkOffset += chunkLength;
+            inputLength -= chunkLength;
+        }
+    }
+
+    @Override
+    public void close() throws IOException {
+        fos.close();
+    }
+
+    @Override
+    public void flush() throws IOException {
+    }
+
+    /**
+     * Read the current position in a file descriptor.
+     */
+    @Override
+    protected long position() {
+        int fd = PosixUtils.getFD(fileDescriptor);
+        return Unistd.lseek(fd, WordFactory.zero(), Unistd.SEEK_CUR()).rawValue();
+    }
+
+    /**
+     * Set the current position in a file descriptor.
+     */
+    @Override
+    protected long position(long offset) {
+        int fd = PosixUtils.getFD(fileDescriptor);
+        return Unistd.lseek(fd, WordFactory.signed(offset), Unistd.SEEK_SET()).rawValue();
+    }
+}
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/HProfSubRecord.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/dump/HProfSubRecord.java
similarity index 98%
rename from substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/HProfSubRecord.java
rename to substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/dump/HProfSubRecord.java
index 044984dbc5a3..ae9714454e3f 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/HProfSubRecord.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/dump/HProfSubRecord.java
@@ -22,7 +22,7 @@
  * or visit www.oracle.com if you need additional information or have any
  * questions.
  */
-package com.oracle.svm.core.heapdump;
+package com.oracle.svm.core.heap.dump;
 
 import org.graalvm.compiler.core.common.NumUtil;
 
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/HProfTopLevelRecord.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/dump/HProfTopLevelRecord.java
similarity index 97%
rename from substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/HProfTopLevelRecord.java
rename to substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/dump/HProfTopLevelRecord.java
index 4b083d71390b..09a737071269 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/HProfTopLevelRecord.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/dump/HProfTopLevelRecord.java
@@ -22,7 +22,7 @@
  * or visit www.oracle.com if you need additional information or have any
  * questions.
  */
-package com.oracle.svm.core.heapdump;
+package com.oracle.svm.core.heap.dump;
 
 import org.graalvm.compiler.core.common.NumUtil;
 
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/HProfType.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/dump/HProfType.java
similarity index 98%
rename from substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/HProfType.java
rename to substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/dump/HProfType.java
index 50618b9ebe1c..3f0e870d94cc 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/HProfType.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/dump/HProfType.java
@@ -22,7 +22,7 @@
  * or visit www.oracle.com if you need additional information or have any
  * questions.
  */
-package com.oracle.svm.core.heapdump;
+package com.oracle.svm.core.heap.dump;
 
 import org.graalvm.compiler.core.common.NumUtil;
 
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/HeapDumpMetadata.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/dump/HeapDumpMetadata.java
similarity index 99%
rename from substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/HeapDumpMetadata.java
rename to substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/dump/HeapDumpMetadata.java
index f8b7a9325d44..8a78133716e4 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/HeapDumpMetadata.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/dump/HeapDumpMetadata.java
@@ -22,7 +22,7 @@
  * or visit www.oracle.com if you need additional information or have any
  * questions.
  */
-package com.oracle.svm.core.heapdump;
+package com.oracle.svm.core.heap.dump;
 
 import org.graalvm.nativeimage.ImageSingletons;
 import org.graalvm.nativeimage.StackValue;
@@ -50,7 +50,7 @@
 /**
  * Provides access to the encoded heap dump metadata that was prepared at image build-time.
  */
-class HeapDumpMetadata {
+public class HeapDumpMetadata {
     private static final ComputeHubDataVisitor COMPUTE_HUB_DATA_VISITOR = new ComputeHubDataVisitor();
 
     private static int fieldNameCount;
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/dump/HeapDumpSupportImpl.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/dump/HeapDumpSupportImpl.java
new file mode 100644
index 000000000000..c71bd2f7e61e
--- /dev/null
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/dump/HeapDumpSupportImpl.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (c) 2023, 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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.
+ */
+package com.oracle.svm.core.heap.dump;
+
+import static com.oracle.svm.core.heap.RestrictHeapAccess.Access.NO_ALLOCATION;
+import static com.oracle.svm.core.heap.RestrictHeapAccess.Access.UNRESTRICTED;
+
+import java.io.IOException;
+
+import org.graalvm.compiler.api.replacements.Fold;
+import org.graalvm.nativeimage.Platform;
+import org.graalvm.nativeimage.Platforms;
+import org.graalvm.nativeimage.StackValue;
+import org.graalvm.nativeimage.c.struct.RawField;
+import org.graalvm.nativeimage.c.struct.RawStructure;
+import org.graalvm.nativeimage.c.struct.SizeOf;
+import org.graalvm.nativeimage.impl.HeapDumpSupport;
+import org.graalvm.word.Pointer;
+import org.graalvm.word.WordFactory;
+
+import com.oracle.svm.core.UnmanagedMemoryUtil;
+import com.oracle.svm.core.heap.RestrictHeapAccess;
+import com.oracle.svm.core.heap.VMOperationInfos;
+import com.oracle.svm.core.log.Log;
+import com.oracle.svm.core.os.RawFileOperationSupport;
+import com.oracle.svm.core.os.RawFileOperationSupport.FileCreationMode;
+import com.oracle.svm.core.os.RawFileOperationSupport.RawFileDescriptor;
+import com.oracle.svm.core.thread.NativeVMOperation;
+import com.oracle.svm.core.thread.NativeVMOperationData;
+import com.oracle.svm.core.thread.VMOperation;
+
+public class HeapDumpSupportImpl implements HeapDumpSupport {
+    private final HeapDumpWriter writer;
+    private final HeapDumpOperation heapDumpOperation;
+
+    @Platforms(Platform.HOSTED_ONLY.class)
+    public HeapDumpSupportImpl() {
+        this.writer = new HeapDumpWriter();
+        this.heapDumpOperation = new HeapDumpOperation();
+    }
+
+    @Platforms(Platform.HOSTED_ONLY.class)
+    public void setMetadata(byte[] value) {
+        this.writer.setMetadata(value);
+    }
+
+    @Override
+    public void dumpHeap(String filename, boolean gcBefore) throws IOException {
+        RawFileDescriptor fd = getFileSupport().create(filename, FileCreationMode.CREATE_OR_REPLACE, RawFileOperationSupport.FileAccessMode.READ_WRITE);
+        if (!getFileSupport().isValid(fd)) {
+            throw new IOException("Could not create the heap dump file: " + filename);
+        }
+
+        try {
+            writeHeapTo(fd, gcBefore);
+        } finally {
+            getFileSupport().close(fd);
+        }
+    }
+
+    public void writeHeapTo(RawFileDescriptor fd, boolean gcBefore) throws IOException {
+        int size = SizeOf.get(HeapDumpVMOperationData.class);
+        HeapDumpVMOperationData data = StackValue.get(size);
+        UnmanagedMemoryUtil.fill((Pointer) data, WordFactory.unsigned(size), (byte) 0);
+
+        data.setGCBefore(gcBefore);
+        data.setRawFileDescriptor(fd);
+        heapDumpOperation.enqueue(data);
+
+        if (!data.getSuccess()) {
+            throw new IOException("An error occurred while writing the heap dump.");
+        }
+    }
+
+    @Fold
+    static RawFileOperationSupport getFileSupport() {
+        return RawFileOperationSupport.bigEndian();
+    }
+
+    @RawStructure
+    private interface HeapDumpVMOperationData extends NativeVMOperationData {
+        @RawField
+        boolean getGCBefore();
+
+        @RawField
+        void setGCBefore(boolean value);
+
+        @RawField
+        RawFileDescriptor getRawFileDescriptor();
+
+        @RawField
+        void setRawFileDescriptor(RawFileDescriptor fd);
+
+        @RawField
+        boolean getSuccess();
+
+        @RawField
+        void setSuccess(boolean value);
+    }
+
+    private class HeapDumpOperation extends NativeVMOperation {
+        @Platforms(Platform.HOSTED_ONLY.class)
+        HeapDumpOperation() {
+            super(VMOperationInfos.get(HeapDumpOperation.class, "Write heap dump", VMOperation.SystemEffect.SAFEPOINT));
+        }
+
+        @Override
+        @RestrictHeapAccess(access = NO_ALLOCATION, reason = "Heap dumping must not allocate.")
+        protected void operate(NativeVMOperationData d) {
+            HeapDumpVMOperationData data = (HeapDumpVMOperationData) d;
+            if (data.getGCBefore()) {
+                System.gc();
+            }
+
+            try {
+                boolean success = writer.dumpHeap(data.getRawFileDescriptor());
+                data.setSuccess(success);
+            } catch (Throwable e) {
+                reportError(e);
+            }
+        }
+
+        @RestrictHeapAccess(access = UNRESTRICTED, reason = "Error reporting may allocate.")
+        private void reportError(Throwable e) {
+            Log.log().string("An exception occurred during heap dumping. The data in the heap dump file may be corrupt.").newline().string(e.getClass().getName()).string(": ")
+                            .string(e.getMessage());
+        }
+    }
+}
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/dump/HeapDumpWriter.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/dump/HeapDumpWriter.java
new file mode 100644
index 000000000000..d88310af18e6
--- /dev/null
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/dump/HeapDumpWriter.java
@@ -0,0 +1,1155 @@
+/*
+ * Copyright (c) 2023, 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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.
+ */
+package com.oracle.svm.core.heap.dump;
+
+import static com.oracle.svm.core.heap.RestrictHeapAccess.Access.NO_ALLOCATION;
+
+import org.graalvm.compiler.api.replacements.Fold;
+import org.graalvm.compiler.core.common.NumUtil;
+import org.graalvm.compiler.nodes.java.ArrayLengthNode;
+import org.graalvm.compiler.word.ObjectAccess;
+import org.graalvm.compiler.word.Word;
+import org.graalvm.nativeimage.CurrentIsolate;
+import org.graalvm.nativeimage.IsolateThread;
+import org.graalvm.nativeimage.Platform;
+import org.graalvm.nativeimage.Platforms;
+import org.graalvm.nativeimage.StackValue;
+import org.graalvm.nativeimage.c.function.CodePointer;
+import org.graalvm.word.Pointer;
+import org.graalvm.word.UnsignedWord;
+import org.graalvm.word.WordBase;
+import org.graalvm.word.WordFactory;
+
+import com.oracle.svm.core.NeverInline;
+import com.oracle.svm.core.StaticFieldsSupport;
+import com.oracle.svm.core.SubstrateOptions;
+import com.oracle.svm.core.c.NonmovableArray;
+import com.oracle.svm.core.code.CodeInfo;
+import com.oracle.svm.core.code.CodeInfoAccess;
+import com.oracle.svm.core.code.CodeInfoDecoder;
+import com.oracle.svm.core.code.CodeInfoTable;
+import com.oracle.svm.core.code.FrameInfoQueryResult;
+import com.oracle.svm.core.code.RuntimeCodeCache;
+import com.oracle.svm.core.code.RuntimeCodeInfoAccess;
+import com.oracle.svm.core.code.RuntimeCodeInfoMemory;
+import com.oracle.svm.core.code.SimpleCodeInfoQueryResult;
+import com.oracle.svm.core.collections.GrowableWordArray;
+import com.oracle.svm.core.collections.GrowableWordArrayAccess;
+import com.oracle.svm.core.config.ConfigurationValues;
+import com.oracle.svm.core.deopt.DeoptimizedFrame;
+import com.oracle.svm.core.heap.CodeReferenceMapDecoder;
+import com.oracle.svm.core.heap.Heap;
+import com.oracle.svm.core.heap.NoAllocationVerifier;
+import com.oracle.svm.core.heap.ObjectReferenceVisitor;
+import com.oracle.svm.core.heap.ObjectVisitor;
+import com.oracle.svm.core.heap.ReferenceAccess;
+import com.oracle.svm.core.heap.ReferenceMapIndex;
+import com.oracle.svm.core.heap.RestrictHeapAccess;
+import com.oracle.svm.core.heap.UnknownObjectField;
+import com.oracle.svm.core.heap.dump.HeapDumpMetadata.ClassInfo;
+import com.oracle.svm.core.heap.dump.HeapDumpMetadata.ClassInfoAccess;
+import com.oracle.svm.core.heap.dump.HeapDumpMetadata.FieldInfo;
+import com.oracle.svm.core.heap.dump.HeapDumpMetadata.FieldInfoAccess;
+import com.oracle.svm.core.heap.dump.HeapDumpMetadata.FieldInfoPointer;
+import com.oracle.svm.core.heap.dump.HeapDumpMetadata.FieldName;
+import com.oracle.svm.core.heap.dump.HeapDumpMetadata.FieldNameAccess;
+import com.oracle.svm.core.hub.DynamicHub;
+import com.oracle.svm.core.hub.LayoutEncoding;
+import com.oracle.svm.core.log.Log;
+import com.oracle.svm.core.os.BufferedFileOperationSupport;
+import com.oracle.svm.core.os.BufferedFileOperationSupport.BufferedFile;
+import com.oracle.svm.core.os.RawFileOperationSupport.RawFileDescriptor;
+import com.oracle.svm.core.snippets.KnownIntrinsics;
+import com.oracle.svm.core.stack.JavaStackWalker;
+import com.oracle.svm.core.stack.StackFrameVisitor;
+import com.oracle.svm.core.thread.PlatformThreads;
+import com.oracle.svm.core.thread.ThreadingSupportImpl;
+import com.oracle.svm.core.thread.VMOperation;
+import com.oracle.svm.core.thread.VMThreads;
+import com.oracle.svm.core.threadlocal.VMThreadLocalMTSupport;
+import com.oracle.svm.core.util.VMError;
+
+/**
+ * This class dumps the image heap and the Java heap into a file (HPROF binary format), similar to
+ * the HotSpot classes in {@code heapDumper.cpp}. The heap dumping needs additional metadata that is
+ * encoded during the image build and that is stored in the image heap in a compact binary format
+ * (see {@code HeapDumpFeature} and {@link HeapDumpMetadata}).
+ *
+ * The actual heap dumping logic is executed in a VM operation, so it is safe to iterate over all
+ * live threads. The heap dumper is implemented as a singleton and only a single heap dumping
+ * operation can be in progress at a given time.
+ *
+ * The heap dumping code must not modify the Java heap in any way as this would pollute or corrupt
+ * the heap dump. Therefore, no Java datastructures can be used and all data structures must be
+ * allocated using native memory instead. This also guarantees that the heap dumping works in case
+ * that Native Image is completely out of Java heap memory (see option
+ * {@code -XX:HeapDumpOnOutOfMemoryError}).
+ *
+ * The heap dump is stored in the HPROF binary format (big endian byte order). The high-level
+ * structure of the file is roughly as follows:
+ *
+ * 
+ *   file header
+ *   ([top level record] [sub record]*)+
+ *   end marker
+ * 
+ * + * Other relevant file format aspects: + *
    + *
  • References to objects or symbols are encoded using their word-sized address. This is done + * regardless if compressed references are enabled or not. Tools (such as VisualVM) that read HPROF + * files, therefore use heuristics to detect if compressed references were used at run-time.
  • + *
  • Symbols such as class or method names are encoded as UTF8.
  • + *
  • The size of individual records is limited to {@link #MAX_UNSIGNED_INT}. So, very large arrays + * that have a larger size than {@link #MAX_UNSIGNED_INT} need to be truncated.
  • + *
+ */ +public class HeapDumpWriter { + private static final long MAX_UNSIGNED_INT = (1L << 32) - 1; + + private static final int DUMMY_STACK_TRACE_ID = 1; + private static final int LARGE_OBJECT_THRESHOLD = 1 * 1024 * 1024; + private static final int HEAP_DUMP_SEGMENT_TARGET_SIZE = 1 * 1024 * 1024; + + private final NoAllocationVerifier noAllocationVerifier = NoAllocationVerifier.factory("HeapDumpWriter", false); + private final DumpStackFrameVisitor dumpStackFrameVisitor = new DumpStackFrameVisitor(); + private final DumpObjectsVisitor dumpObjectsVisitor = new DumpObjectsVisitor(); + private final CodeMetadataVisitor codeMetadataVisitor = new CodeMetadataVisitor(); + private final ThreadLocalsVisitor threadLocalsVisitor = new ThreadLocalsVisitor(); + @UnknownObjectField(types = {byte[].class}) private byte[] metadata; + + private BufferedFile f; + private long topLevelRecordBegin = -1; + private long subRecordBegin = -1; + private boolean error; + + @Platforms(Platform.HOSTED_ONLY.class) + public HeapDumpWriter() { + } + + @Platforms(Platform.HOSTED_ONLY.class) + public void setMetadata(byte[] value) { + this.metadata = value; + } + + public boolean dumpHeap(RawFileDescriptor fd) { + assert VMOperation.isInProgressAtSafepoint(); + assert ThreadingSupportImpl.isRecurringCallbackPaused(); + + noAllocationVerifier.open(); + try { + Heap.getHeap().suspendAllocation(); + return dumpHeap0(fd); + } finally { + noAllocationVerifier.close(); + } + } + + private boolean dumpHeap0(RawFileDescriptor fd) { + boolean initialized = initialize(fd); + try { + if (initialized) { + return writeHeapDump(); + } else { + Log.log().string("An error occurred while initializing the heap dump infrastructure. No heap data will be dumped.").newline(); + return false; + } + } finally { + /* teardown must always be executed, even if the initialization failed. */ + teardown(); + } + } + + private boolean initialize(RawFileDescriptor fd) { + assert topLevelRecordBegin == -1 && subRecordBegin == -1 && !error; + + this.f = file().allocate(fd); + if (f.isNull()) { + return false; + } + return HeapDumpMetadata.initialize(metadata); + } + + private void teardown() { + HeapDumpMetadata.teardown(); + + assert f.isNull() || error || file().getUnflushedDataSize(f) == 0; + file().free(f); + this.f = WordFactory.nullPointer(); + + this.topLevelRecordBegin = -1; + this.subRecordBegin = -1; + this.error = false; + } + + @NeverInline("Starting a stack walk in the caller frame.") + private boolean writeHeapDump() { + /* + * Only read the stack pointer for the current thread once. This ensures consistency for all + * the information that we dump about the stack of the current thread. + */ + Pointer currentThreadSp = KnownIntrinsics.readCallerStackPointer(); + + writeHeader(); + writeClassNames(); // UTF-8 symbols + writeFieldNames(); // UTF-8 symbols + writeLoadedClasses(); // LOAD_CLASS + writeStackTraces(currentThreadSp); // FRAME and TRACE + + /* 1..n HEAP_DUMP_SEGMENT records */ + startTopLevelRecord(HProfTopLevelRecord.HEAP_DUMP_SEGMENT); + writeClasses(); // GC_CLASS_DUMP + writeThreads(currentThreadSp); // GC_ROOT_THREAD_OBJ, GC_ROOT_JAVA_FRAME, GC_ROOT_JNI_LOCAL + writeJNIGlobals(); // GC_ROOT_JNI_GLOBAL + writeStickyClasses(); // GC_ROOT_STICKY_CLASS + writeObjects(); // GC_INSTANCE_DUMP, GC_OBJ_ARRAY_DUMP, GC_PRIM_ARRAY_DUMP + endTopLevelRecord(); + + startTopLevelRecord(HProfTopLevelRecord.HEAP_DUMP_END); + endTopLevelRecord(); + + flush(); + + if (error) { + Log.log().string("An error occurred while writing the heap dump data. The data in the heap dump file may be corrupt.").newline(); + return false; + } + return true; + } + + private void writeHeader() { + writeUTF8("JAVA PROFILE 1.0.2"); + writeByte((byte) 0); + writeInt(wordSize()); + writeLong(System.currentTimeMillis()); + } + + private void startTopLevelRecord(HProfTopLevelRecord tag) { + assert topLevelRecordBegin == -1; + writeByte(tag.getValue()); + writeInt(0); // timestamp + writeInt(0); // length (patched later on) + topLevelRecordBegin = getPosition(); + } + + private void endTopLevelRecord() { + assert topLevelRecordBegin > 0; + long currentPosition = getPosition(); + setPosition(topLevelRecordBegin - Integer.BYTES); + writeInt(NumUtil.safeToUInt(currentPosition - topLevelRecordBegin)); + setPosition(currentPosition); + topLevelRecordBegin = -1; + } + + private void startSubRecord(HProfSubRecord tag, long size) { + assert topLevelRecordBegin > 0 : "must be within a HEAP_DUMP_SEGMENT"; + long heapDumpSegmentSize = getPosition() - topLevelRecordBegin; + if (heapDumpSegmentSize > 0 && heapDumpSegmentSize + size > HEAP_DUMP_SEGMENT_TARGET_SIZE) { + endTopLevelRecord(); + startTopLevelRecord(HProfTopLevelRecord.HEAP_DUMP_SEGMENT); + } + + subRecordBegin = getPosition(); + writeByte(tag.getValue()); + } + + private void endSubRecord(long recordSize) { + assert subRecordBegin > 0; + assert subRecordBegin + recordSize == getPosition(); + subRecordBegin = -1; + } + + private void writeClassNames() { + for (int i = 0; i < HeapDumpMetadata.getClassInfoCount(); i++) { + ClassInfo classInfo = HeapDumpMetadata.getClassInfo(i); + if (ClassInfoAccess.isValid(classInfo)) { + writeSymbol(classInfo.getHub().getName()); + } + } + } + + private void writeSymbol(String value) { + startTopLevelRecord(HProfTopLevelRecord.UTF8); + writeObjectId(value); + writeUTF8(value); + endTopLevelRecord(); + } + + private void writeSymbol(FieldName fieldName) { + startTopLevelRecord(HProfTopLevelRecord.UTF8); + writeFieldNameId(fieldName); + write((Pointer) FieldNameAccess.getChars(fieldName), WordFactory.unsigned(FieldNameAccess.getLength(fieldName))); + endTopLevelRecord(); + } + + private void writeFieldNames() { + for (int i = 0; i < HeapDumpMetadata.getFieldNameCount(); i++) { + FieldName fieldName = HeapDumpMetadata.getFieldName(i); + writeSymbol(fieldName); + } + } + + private void writeLoadedClasses() { + for (int i = 0; i < HeapDumpMetadata.getClassInfoCount(); i++) { + ClassInfo classInfo = HeapDumpMetadata.getClassInfo(i); + if (ClassInfoAccess.isValid(classInfo)) { + DynamicHub hub = classInfo.getHub(); + if (hub.isLoaded()) { + startTopLevelRecord(HProfTopLevelRecord.LOAD_CLASS); + writeInt(classInfo.getSerialNum()); + writeClassId(hub); + writeInt(DUMMY_STACK_TRACE_ID); + writeObjectId(hub.getName()); + endTopLevelRecord(); + } + } + } + } + + private void writeStackTraces(Pointer currentThreadSp) { + writeDummyStackTrace(); + + /* Write the stack traces of all threads. */ + long nextFrameId = 1; + int threadSerialNum = 1; + for (IsolateThread isolateThread = VMThreads.firstThread(); isolateThread.isNonNull(); isolateThread = VMThreads.nextThread(isolateThread)) { + /* Write FRAME records. */ + int writtenFrames = dumpStackData(isolateThread, currentThreadSp, threadSerialNum, nextFrameId, false); + + /* Write TRACE record that references the FRAME records. */ + startTopLevelRecord(HProfTopLevelRecord.TRACE); + int stackSerialNum = threadSerialNum + DUMMY_STACK_TRACE_ID; + writeInt(stackSerialNum); + writeInt(threadSerialNum); + writeInt(writtenFrames); + for (int i = 0; i < writtenFrames; i++) { + writeFrameId(nextFrameId++); + } + endTopLevelRecord(); + + threadSerialNum++; + } + } + + private int dumpStackData(IsolateThread isolateThread, Pointer currentThreadSp, int threadSerialNum, long nextFrameId, boolean markGCRoots) { + dumpStackFrameVisitor.initialize(threadSerialNum, nextFrameId, markGCRoots); + if (isolateThread == CurrentIsolate.getCurrentThread()) { + JavaStackWalker.walkCurrentThread(currentThreadSp, dumpStackFrameVisitor); + } else { + JavaStackWalker.walkThread(isolateThread, dumpStackFrameVisitor); + } + return dumpStackFrameVisitor.getWrittenFrames(); + } + + /** Writes an empty TRACE record that can be used in other records. */ + private void writeDummyStackTrace() { + startTopLevelRecord(HProfTopLevelRecord.TRACE); + writeInt(DUMMY_STACK_TRACE_ID); + writeInt(0); // thread serial number + writeInt(0); // number of frames + endTopLevelRecord(); + } + + private void writeClasses() { + for (int i = 0; i < HeapDumpMetadata.getClassInfoCount(); i++) { + ClassInfo classInfo = HeapDumpMetadata.getClassInfo(i); + if (ClassInfoAccess.isValid(classInfo)) { + if (classInfo.getHub().isLoaded()) { + writeClassDumpRecord(classInfo); + } + } + } + } + + private void writeClassDumpRecord(ClassInfo classInfo) { + int staticFieldsCount = classInfo.getStaticFieldCount(); + int staticFieldsSize = staticFieldsCount * (wordSize() + 1) + HeapDumpMetadata.computeFieldsDumpSize(classInfo.getStaticFields(), classInfo.getStaticFieldCount()); + int instanceFieldsCount = classInfo.getInstanceFieldCount(); + int instanceFieldsSize = instanceFieldsCount * (wordSize() + 1); + int recordSize = 1 + wordSize() + 4 + 6 * wordSize() + 4 + 2 + 2 + staticFieldsSize + 2 + instanceFieldsSize; + + Class clazz = DynamicHub.toClass(classInfo.getHub()); + startSubRecord(HProfSubRecord.GC_CLASS_DUMP, recordSize); + writeClassId(clazz); + writeInt(DUMMY_STACK_TRACE_ID); + writeClassId(clazz.getSuperclass()); + writeObjectId(getClassLoader(clazz)); + writeObjectId(clazz.getSigners()); + writeObjectId(null); // protection domain + writeObjectId(null); // reserved field + writeObjectId(null); // reserved field + writeInt(getObjectSizeInHeap(clazz)); + writeShort((short) 0); // size of constant pool + writeFieldDescriptors(staticFieldsCount, classInfo.getStaticFields(), true); + writeFieldDescriptors(instanceFieldsCount, classInfo.getInstanceFields(), false); + endSubRecord(recordSize); + } + + private void writeFieldDescriptors(int fieldCount, FieldInfoPointer fieldInfos, boolean isStatic) { + writeShort(NumUtil.safeToUShort(fieldCount)); + for (int i = 0; i < fieldCount; i++) { + FieldInfo field = fieldInfos.addressOf(i).read(); + writeFieldNameId(FieldInfoAccess.getFieldName(field)); + HProfType type = FieldInfoAccess.getType(field); + writeType(type); + + if (isStatic) { + /* For static fields, write the field value to the heap dump as well. */ + Object dataHolder = getStaticFieldDataHolder(type); + writeFieldData(dataHolder, field); + } + } + } + + private static Object getStaticFieldDataHolder(HProfType type) { + if (type == HProfType.NORMAL_OBJECT) { + return StaticFieldsSupport.getStaticObjectFields(); + } else { + return StaticFieldsSupport.getStaticPrimitiveFields(); + } + } + + private void writeFieldData(Object dataHolder, FieldInfo field) { + Pointer p = Word.objectToUntrackedPointer(dataHolder); + int location = FieldInfoAccess.getLocation(field); + HProfType type = FieldInfoAccess.getType(field); + switch (type) { + case BOOLEAN, BYTE -> writeByte(p.readByte(location)); + case CHAR -> writeChar(p.readChar(location)); + case SHORT -> writeShort(p.readShort(location)); + case INT -> writeInt(p.readInt(location)); + case LONG -> writeLong(p.readLong(location)); + case FLOAT -> writeFloat(p.readFloat(location)); + case DOUBLE -> writeDouble(p.readDouble(location)); + case NORMAL_OBJECT -> writeObjectId(ReferenceAccess.singleton().readObjectAt(p.add(location), true)); + default -> throw VMError.shouldNotReachHere("Unexpected type."); + } + } + + private void writeThreads(Pointer currentThreadSp) { + long nextFrameId = 1; + int threadSerialNum = 1; + for (IsolateThread isolateThread = VMThreads.firstThread(); isolateThread.isNonNull(); isolateThread = VMThreads.nextThread(isolateThread)) { + int stackTraceSerialNum = threadSerialNum + DUMMY_STACK_TRACE_ID; + /* + * If a thread is not fully initialized yet, then the java.lang.Thread object may still + * be null. In this case, a thread object id of 0 is written, which is fine according to + * the HPROF specification. + */ + Thread thread = PlatformThreads.fromVMThread(isolateThread); + writeThread(thread, threadSerialNum, stackTraceSerialNum); + nextFrameId += dumpStackData(isolateThread, currentThreadSp, threadSerialNum, nextFrameId, true); + + writeThreadLocals(isolateThread, threadSerialNum); + threadSerialNum++; + } + } + + private void writeThread(Thread threadObj, int threadSerialNum, int stackTraceSerialNum) { + int recordSize = 1 + wordSize() + 4 + 4; + startSubRecord(HProfSubRecord.GC_ROOT_THREAD_OBJ, recordSize); + writeObjectId(threadObj); + writeInt(threadSerialNum); + writeInt(stackTraceSerialNum); + endSubRecord(recordSize); + } + + private void writeThreadLocals(IsolateThread isolateThread, int threadSerialNum) { + if (SubstrateOptions.MultiThreaded.getValue()) { + threadLocalsVisitor.initialize(threadSerialNum); + VMThreadLocalMTSupport.singleton().walk(isolateThread, threadLocalsVisitor); + } + } + + private void writeJNIGlobals() { + /* All objects that are referenced by the runtime code cache are GC roots. */ + RuntimeCodeInfoMemory.singleton().walkRuntimeMethods(codeMetadataVisitor); + } + + private void writeStickyClasses() { + for (int i = 0; i < HeapDumpMetadata.getClassInfoCount(); i++) { + ClassInfo classInfo = HeapDumpMetadata.getClassInfo(i); + if (ClassInfoAccess.isValid(classInfo)) { + int recordSize = 1 + wordSize(); + startSubRecord(HProfSubRecord.GC_ROOT_STICKY_CLASS, recordSize); + writeClassId(classInfo.getHub()); + endSubRecord(recordSize); + } + } + } + + private void writeObjects() { + GrowableWordArray largeObjects = StackValue.get(GrowableWordArray.class); + GrowableWordArrayAccess.initialize(largeObjects); + try { + dumpObjectsVisitor.initialize(largeObjects); + Heap.getHeap().walkImageHeapObjects(dumpObjectsVisitor); + + dumpObjectsVisitor.initialize(largeObjects); + Heap.getHeap().walkCollectedHeapObjects(dumpObjectsVisitor); + + /* Large objects are collected and written separately. */ + writeLargeObjects(largeObjects); + } finally { + GrowableWordArrayAccess.freeData(largeObjects); + largeObjects = WordFactory.nullPointer(); + } + } + + private void writeLargeObjects(GrowableWordArray largeObjects) { + int count = largeObjects.getSize(); + for (int i = 0; i < count; i++) { + Word rawObj = GrowableWordArrayAccess.get(largeObjects, i); + writeObject(rawObj.toObject()); + } + } + + private static ClassLoader getClassLoader(Class clazz) { + Class c = clazz; + while (c.isArray()) { + c = c.getComponentType(); + } + return c.getClassLoader(); + } + + private static int getObjectSizeInHeap(Class cls) { + DynamicHub hub = DynamicHub.fromClass(cls); + int encoding = hub.getLayoutEncoding(); + if (LayoutEncoding.isPureInstance(encoding)) { + /* + * May underestimate the object size if the identity hashcode field is optional. This is + * the best that what can do because the HPROF format does not support that instances of + * one class have different object sizes. + */ + return (int) LayoutEncoding.getPureInstanceAllocationSize(encoding).rawValue(); + } else if (LayoutEncoding.isHybrid(encoding)) { + /* For hybrid objects, return the size of the fields. */ + return LayoutEncoding.getArrayBaseOffsetAsInt(encoding); + } else { + /* Variable size. */ + return 0; + } + } + + private void writeObject(Object obj) { + DynamicHub hub = KnownIntrinsics.readHub(obj); + int layoutEncoding = hub.getLayoutEncoding(); + if (LayoutEncoding.isArray(layoutEncoding)) { + if (LayoutEncoding.isPrimitiveArray(layoutEncoding)) { + writePrimitiveArray(obj, layoutEncoding); + } else { + writeObjectArray(obj); + } + } else { + /* + * Hybrid objects are handled here as well. This means that the array part of hybrid + * objects is currently skipped. Eventually, we should probably dump the array part as a + * separate object. + */ + writeInstance(obj); + } + + if (Heap.getHeap().isInImageHeap(obj)) { + markImageHeapObjectAsGCRoot(obj); + } + + /* + * Ideally, we would model Java monitors as instance fields as they are only reachable if + * the object that owns the monitor is reachable. However, that is not possible because the + * synthetic monitor field could overlap or collide with a normal field of a subclass. + * Therefore, we simply mark all monitors as GC roots. + */ + int monitorOffset = hub.getMonitorOffset(); + if (monitorOffset != 0) { + Object monitor = ObjectAccess.readObject(obj, monitorOffset); + if (monitor != null) { + markMonitorAsGCRoot(monitor); + } + } + } + + private void markMonitorAsGCRoot(Object monitor) { + int recordSize = 1 + wordSize(); + startSubRecord(HProfSubRecord.GC_ROOT_MONITOR_USED, recordSize); + writeObjectId(monitor); + endSubRecord(recordSize); + } + + /** We mark image heap objects as GC_ROOT_JNI_GLOBAL. */ + private void markImageHeapObjectAsGCRoot(Object obj) { + assert Heap.getHeap().isInImageHeap(obj); + markAsJniGlobalGCRoot(obj); + } + + private void markAsJniGlobalGCRoot(Object obj) { + int recordSize = 1 + 2 * wordSize(); + startSubRecord(HProfSubRecord.GC_ROOT_JNI_GLOBAL, recordSize); + writeObjectId(obj); + writeObjectId(null); // global ref ID + endSubRecord(recordSize); + } + + private void writeInstance(Object obj) { + ClassInfo classInfo = HeapDumpMetadata.getClassInfo(obj.getClass()); + int instanceFieldsSize = classInfo.getInstanceFieldsDumpSize(); + int recordSize = 1 + wordSize() + 4 + wordSize() + 4 + instanceFieldsSize; + + startSubRecord(HProfSubRecord.GC_INSTANCE_DUMP, recordSize); + writeObjectId(obj); + writeInt(DUMMY_STACK_TRACE_ID); + writeClassId(obj.getClass()); + writeInt(instanceFieldsSize); + + /* Write the field data. */ + do { + int instanceFieldCount = classInfo.getInstanceFieldCount(); + FieldInfoPointer instanceFields = classInfo.getInstanceFields(); + for (int i = 0; i < instanceFieldCount; i++) { + FieldInfo field = instanceFields.addressOf(i).read(); + writeFieldData(obj, field); + } + classInfo = HeapDumpMetadata.getClassInfo(classInfo.getHub().getSuperHub()); + } while (classInfo.isNonNull()); + + endSubRecord(recordSize); + } + + private void writePrimitiveArray(Object array, int layoutEncoding) { + int arrayBaseOffset = LayoutEncoding.getArrayBaseOffsetAsInt(layoutEncoding); + int elementSize = LayoutEncoding.getArrayIndexScale(layoutEncoding); + + int recordHeaderSize = 1 + wordSize() + 2 * 4 + 1; + int length = calculateMaxArrayLength(array, elementSize, recordHeaderSize); + long recordSize = recordHeaderSize + ((long) length) * elementSize; + + startSubRecord(HProfSubRecord.GC_PRIM_ARRAY_DUMP, recordSize); + writeObjectId(array); + writeInt(DUMMY_STACK_TRACE_ID); + writeInt(length); + + /* The file is big endian, so we need to read & write the data element-wise. */ + if (array instanceof boolean[]) { + writeType(HProfType.BOOLEAN); + writeU1ArrayData(array, length, arrayBaseOffset); + } else if (array instanceof byte[]) { + writeType(HProfType.BYTE); + writeU1ArrayData(array, length, arrayBaseOffset); + } else if (array instanceof short[]) { + writeType(HProfType.SHORT); + writeU2ArrayData(array, length, arrayBaseOffset); + } else if (array instanceof char[]) { + writeType(HProfType.CHAR); + writeU2ArrayData(array, length, arrayBaseOffset); + } else if (array instanceof int[]) { + writeType(HProfType.INT); + writeU4ArrayData(array, length, arrayBaseOffset); + } else if (array instanceof float[]) { + writeType(HProfType.FLOAT); + writeU4ArrayData(array, length, arrayBaseOffset); + } else if (array instanceof long[]) { + writeType(HProfType.LONG); + writeU8ArrayData(array, length, arrayBaseOffset); + } else if (array instanceof double[]) { + writeType(HProfType.DOUBLE); + writeU8ArrayData(array, length, arrayBaseOffset); + } else { + /* Word arrays are primitive arrays as well */ + assert WordBase.class.isAssignableFrom(array.getClass().getComponentType()); + assert elementSize == wordSize(); + writeWordArray(array, length, arrayBaseOffset); + } + endSubRecord(recordSize); + } + + private void writeObjectArray(Object array) { + int recordHeaderSize = 1 + 2 * 4 + 2 * wordSize(); + /* In the heap dump, object array elements are always uncompressed. */ + int length = calculateMaxArrayLength(array, wordSize(), recordHeaderSize); + long recordSize = recordHeaderSize + ((long) length) * wordSize(); + + startSubRecord(HProfSubRecord.GC_OBJ_ARRAY_DUMP, recordSize); + writeObjectId(array); + writeInt(DUMMY_STACK_TRACE_ID); + writeInt(length); + writeClassId(array.getClass()); + + Object[] data = (Object[]) array; + for (int i = 0; i < length; i++) { + writeObjectId(data[i]); + } + endSubRecord(recordSize); + } + + /* + * The size of individual HPROF records is limited to {@link #MAX_UNSIGNED_INT}, which means + * that very large arrays need to truncated. + */ + private static int calculateMaxArrayLength(Object array, int elementSize, int recordHeaderSize) { + int length = ArrayLengthNode.arrayLength(array); + UnsignedWord lengthInBytes = WordFactory.unsigned(length).multiply(elementSize); + UnsignedWord maxBytes = WordFactory.unsigned(MAX_UNSIGNED_INT).subtract(recordHeaderSize); + + if (lengthInBytes.belowOrEqual(maxBytes)) { + return length; + } + + UnsignedWord newLength = maxBytes.unsignedDivide(elementSize); + Log.log().string("Cannot dump very large arrays. Array is truncated to ").unsigned(newLength).string(" elements.").newline(); + return NumUtil.safeToInt(newLength.rawValue()); + } + + private void writeWordArray(Object array, int length, int arrayBaseOffset) { + if (wordSize() == 8) { + writeType(HProfType.LONG); + writeU8ArrayData(array, length, arrayBaseOffset); + } else { + assert wordSize() == 4; + writeType(HProfType.INT); + writeU4ArrayData(array, length, arrayBaseOffset); + } + } + + private void writeU1ArrayData(Object array, int length, int arrayBaseOffset) { + Pointer data = getArrayData(array, arrayBaseOffset); + write(data, WordFactory.unsigned(length)); + } + + private void writeU2ArrayData(Object array, int length, int arrayBaseOffset) { + Pointer cur = getArrayData(array, arrayBaseOffset); + for (int i = 0; i < length; i++) { + writeChar(cur.readChar(0)); + cur = cur.add(2); + } + } + + private void writeU4ArrayData(Object array, int length, int arrayBaseOffset) { + Pointer cur = getArrayData(array, arrayBaseOffset); + for (int i = 0; i < length; i++) { + writeInt(cur.readInt(0)); + cur = cur.add(4); + } + } + + private void writeU8ArrayData(Object array, int length, int arrayBaseOffset) { + Pointer cur = getArrayData(array, arrayBaseOffset); + for (int i = 0; i < length; i++) { + writeLong(cur.readLong(0)); + cur = cur.add(8); + } + } + + private static Pointer getArrayData(Object array, int arrayBaseOffset) { + return Word.objectToUntrackedPointer(array).add(arrayBaseOffset); + } + + private void writeByte(byte value) { + boolean success = file().writeByte(f, value); + handleError(success); + } + + private void writeShort(short value) { + boolean success = file().writeShort(f, value); + handleError(success); + } + + private void writeChar(char value) { + boolean success = file().writeChar(f, value); + handleError(success); + } + + private void writeInt(int value) { + boolean success = file().writeInt(f, value); + handleError(success); + } + + private void writeLong(long value) { + boolean success = file().writeLong(f, value); + handleError(success); + } + + private void writeFloat(float value) { + boolean success = file().writeFloat(f, value); + handleError(success); + } + + private void writeDouble(double value) { + boolean success = file().writeDouble(f, value); + handleError(success); + } + + private void writeType(HProfType type) { + writeByte(type.getValue()); + } + + private void writeObjectId(Object obj) { + writeId0(Word.objectToUntrackedPointer(obj).rawValue()); + } + + private void writeClassId(Class clazz) { + writeClassId(DynamicHub.fromClass(clazz)); + } + + private void writeClassId(DynamicHub hub) { + /* + * HotSpot writes Class objects only as GC_CLASS_DUMP and never as GC_INSTANCE_DUMP records. + * It also always uses the address of the mirror class as the class id. This has the effect + * that the heap dump only contains a very limited set of information for class objects. + * + * It is handy to have detailed information about the DynamicHub in the heap dump. Ideally, + * we would just write both a GC_CLASS_DUMP and a GC_INSTANCE_DUMP record with the same id + * but that breaks VisualVM in a weird way. Therefore, we are using different ids for the + * GC_CLASS_DUMP and GC_INSTANCE_DUMP records. + */ + Word hubAddress = Word.objectToUntrackedPointer(hub); + if (hubAddress.isNonNull()) { + hubAddress = hubAddress.add(1); + } + writeId0(hubAddress.rawValue()); + } + + private void writeFieldNameId(FieldName fieldName) { + writeId0(fieldName.rawValue()); + } + + private void writeFrameId(long frameId) { + writeId0(frameId); + } + + private void writeId0(long value) { + boolean success; + if (wordSize() == 8) { + success = file().writeLong(f, value); + } else { + assert wordSize() == 4; + success = file().writeInt(f, (int) value); + } + handleError(success); + } + + private void writeUTF8(String value) { + boolean success = file().writeUTF8(f, value); + handleError(success); + } + + private void write(Pointer data, UnsignedWord size) { + boolean success = file().write(f, data, size); + handleError(success); + } + + private long getPosition() { + long result = file().position(f); + handleError(result >= 0); + return result; + } + + private void setPosition(long newPos) { + boolean success = file().seek(f, newPos); + handleError(success); + } + + private void flush() { + boolean success = file().flush(f); + handleError(success); + } + + private void handleError(boolean success) { + if (!success) { + error = true; + } + } + + @Fold + static BufferedFileOperationSupport file() { + return BufferedFileOperationSupport.bigEndian(); + } + + @Fold + static int wordSize() { + return ConfigurationValues.getTarget().wordSize; + } + + /** + * This class is used for different purposes: + *
    + *
  • Write a {@link HProfTopLevelRecord#FRAME} top-level record for every frame on the + * stack.
  • + *
  • Write a GC_ROOT sub-record for every deoptimized frame and for every reference that is on + * the stack.
  • + *
+ * + * Unfortunately, it is not possible to write all the information in a single pass because the + * data needs to end up in different HPROF records (top-level vs. sub-records). The data from + * the different passes needs to match, so it is easier to implement both passes in one class + * and to use a {@link #markGCRoots field} to determine which data should be written. + */ + private class DumpStackFrameVisitor extends StackFrameVisitor implements ObjectReferenceVisitor { + private static final int LINE_NUM_NATIVE_METHOD = -3; + + private final CodeInfoDecoder.FrameInfoCursor frameInfoCursor = new CodeInfoDecoder.FrameInfoCursor(); + + private int threadSerialNum; + private long initialNextFrameId; + private long nextFrameId; + private boolean markGCRoots; + + @Platforms(Platform.HOSTED_ONLY.class) + DumpStackFrameVisitor() { + } + + @SuppressWarnings("hiding") + public void initialize(int threadSerialNum, long nextFrameId, boolean markGCRoots) { + assert nextFrameId > 0; + assert threadSerialNum > 0; + assert nextFrameId > 0; + + this.threadSerialNum = threadSerialNum; + this.initialNextFrameId = nextFrameId; + this.nextFrameId = nextFrameId; + this.markGCRoots = markGCRoots; + } + + public int getWrittenFrames() { + return NumUtil.safeToInt(nextFrameId - initialNextFrameId); + } + + @Override + @RestrictHeapAccess(access = NO_ALLOCATION, reason = "Heap dumping must not allocate.") + protected boolean visitFrame(Pointer sp, CodePointer ip, CodeInfo codeInfo, DeoptimizedFrame deoptimizedFrame) { + if (deoptimizedFrame != null) { + markAsGCRoot(deoptimizedFrame); + + for (DeoptimizedFrame.VirtualFrame frame = deoptimizedFrame.getTopFrame(); frame != null; frame = frame.getCaller()) { + visitFrame(frame.getFrameInfo()); + nextFrameId++; + } + } else { + /* + * All references that are on the stack need to be marked as GC roots. Our + * information is not necessarily precise enough to identify the exact Java-level + * stack frame to which a reference belongs. Therefore, we just dump the data in a + * way that it gets associated with the deepest inlined Java-level stack frame of + * each compilation unit. + */ + markStackValuesAsGCRoots(sp, ip, codeInfo); + + frameInfoCursor.initialize(codeInfo, ip); + while (frameInfoCursor.advance()) { + FrameInfoQueryResult frame = frameInfoCursor.get(); + visitFrame(frame); + nextFrameId++; + } + } + return true; + } + + private void markAsGCRoot(DeoptimizedFrame frame) { + if (markGCRoots) { + markAsJniGlobalGCRoot(frame); + } + } + + private void markStackValuesAsGCRoots(Pointer sp, CodePointer ip, CodeInfo codeInfo) { + if (markGCRoots) { + SimpleCodeInfoQueryResult queryResult = StackValue.get(SimpleCodeInfoQueryResult.class); + CodeInfoAccess.lookupCodeInfo(codeInfo, CodeInfoAccess.relativeIP(codeInfo, ip), queryResult); + + NonmovableArray referenceMapEncoding = CodeInfoAccess.getStackReferenceMapEncoding(codeInfo); + long referenceMapIndex = queryResult.getReferenceMapIndex(); + if (referenceMapIndex == ReferenceMapIndex.NO_REFERENCE_MAP) { + throw CodeInfoTable.reportNoReferenceMap(sp, ip, codeInfo); + } + CodeReferenceMapDecoder.walkOffsetsFromPointer(sp, referenceMapEncoding, referenceMapIndex, this, null); + } + } + + @Override + @RestrictHeapAccess(access = NO_ALLOCATION, reason = "Heap dumping must not allocate.") + public boolean visitObjectReference(Pointer objRef, boolean compressed, Object holderObject) { + assert markGCRoots; + + Object obj = ReferenceAccess.singleton().readObjectAt(objRef, compressed); + if (obj != null) { + int recordSize = 1 + wordSize() + 4 + 4; + startSubRecord(HProfSubRecord.GC_ROOT_JAVA_FRAME, recordSize); + writeObjectId(obj); + writeInt(threadSerialNum); + /* Position of the stack frame in the stack trace. */ + writeInt(getWrittenFrames()); + } + return true; + } + + private void visitFrame(FrameInfoQueryResult frame) { + if (!markGCRoots) { + /* + * Write all UTF-8 symbols that are needed when writing the frame. Ideally, we would + * de-duplicate the symbols, but doing so is not crucial. We also don't support the + * method signature at the moment. + */ + String methodName = frame.getSourceMethodName(); + String methodSignature = ""; + String sourceFileName = getSourceFileName(frame); + writeSymbol(methodName); + writeSymbol(methodSignature); + writeSymbol(sourceFileName); + + /* Write the FRAME record. */ + ClassInfo classInfo = HeapDumpMetadata.getClassInfo(frame.getSourceClass()); + int lineNumber = getLineNumber(frame); + writeFrame(classInfo.getSerialNum(), lineNumber, methodName, methodSignature, sourceFileName); + } + } + + private void writeFrame(int classSerialNum, int lineNumber, String methodName, String methodSignature, String sourceFileName) { + assert !markGCRoots; + + startTopLevelRecord(HProfTopLevelRecord.FRAME); + writeFrameId(nextFrameId); + writeObjectId(methodName); + writeObjectId(methodSignature); + writeObjectId(sourceFileName); + writeInt(classSerialNum); + writeInt(lineNumber); + endTopLevelRecord(); + } + + private String getSourceFileName(FrameInfoQueryResult frame) { + String sourceFileName = frame.getSourceFileName(); + if (sourceFileName == null || sourceFileName.isEmpty()) { + sourceFileName = "Unknown Source"; + } + return sourceFileName; + } + + private int getLineNumber(FrameInfoQueryResult frame) { + if (frame.isNativeMethod()) { + return LINE_NUM_NATIVE_METHOD; + } + return frame.getSourceLineNumber(); + } + } + + private class DumpObjectsVisitor implements ObjectVisitor { + private GrowableWordArray largeObjects; + + @Platforms(Platform.HOSTED_ONLY.class) + DumpObjectsVisitor() { + } + + @SuppressWarnings("hiding") + public void initialize(GrowableWordArray largeObjects) { + this.largeObjects = largeObjects; + } + + @Override + @RestrictHeapAccess(access = NO_ALLOCATION, reason = "Heap dumping must not allocate.") + public boolean visitObject(Object obj) { + if (isLarge(obj)) { + boolean added = GrowableWordArrayAccess.add(largeObjects, Word.objectToUntrackedPointer(obj)); + if (!added) { + Log.log().string("Failed to add an element to the large object list. Heap dump will be incomplete.").newline(); + } + } else { + writeObject(obj); + } + return true; + } + + private boolean isLarge(Object obj) { + return getObjectSize(obj).aboveThan(LARGE_OBJECT_THRESHOLD); + } + + private UnsignedWord getObjectSize(Object obj) { + int layoutEncoding = KnownIntrinsics.readHub(obj).getLayoutEncoding(); + if (LayoutEncoding.isArray(layoutEncoding)) { + int elementSize; + if (LayoutEncoding.isPrimitiveArray(layoutEncoding)) { + elementSize = LayoutEncoding.getArrayIndexScale(layoutEncoding); + } else { + elementSize = wordSize(); + } + int length = ArrayLengthNode.arrayLength(obj); + return WordFactory.unsigned(length).multiply(elementSize); + } else { + ClassInfo classInfo = HeapDumpMetadata.getClassInfo(obj.getClass()); + return WordFactory.unsigned(classInfo.getInstanceFieldsDumpSize()); + } + } + } + + private class CodeMetadataVisitor implements RuntimeCodeCache.CodeInfoVisitor, ObjectReferenceVisitor { + @Platforms(Platform.HOSTED_ONLY.class) + CodeMetadataVisitor() { + } + + @Override + public boolean visitCode(CodeInfo info) { + RuntimeCodeInfoAccess.walkObjectFields(info, this); + return true; + } + + @Override + @RestrictHeapAccess(access = NO_ALLOCATION, reason = "Heap dumping must not allocate.") + public boolean visitObjectReference(Pointer objRef, boolean compressed, Object holderObject) { + Object obj = ReferenceAccess.singleton().readObjectAt(objRef, compressed); + if (obj != null) { + markAsJniGlobalGCRoot(obj); + } + return true; + } + } + + private class ThreadLocalsVisitor implements ObjectReferenceVisitor { + private int threadSerialNum; + + @Platforms(Platform.HOSTED_ONLY.class) + ThreadLocalsVisitor() { + } + + @SuppressWarnings("hiding") + public void initialize(int threadSerialNum) { + this.threadSerialNum = threadSerialNum; + } + + @Override + @RestrictHeapAccess(access = NO_ALLOCATION, reason = "Heap dumping must not allocate.") + public boolean visitObjectReference(Pointer objRef, boolean compressed, Object holderObject) { + Object obj = ReferenceAccess.singleton().readObjectAt(objRef, compressed); + if (obj != null) { + markThreadLocalAsGCRoot(obj); + } + return true; + } + + private void markThreadLocalAsGCRoot(Object obj) { + int recordSize = 1 + wordSize() + 4 + 4; + startSubRecord(HProfSubRecord.GC_ROOT_JNI_LOCAL, recordSize); + writeObjectId(obj); + writeInt(threadSerialNum); + writeInt(-1); // empty stack + endSubRecord(recordSize); + } + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/AllocationFreeOutputStream.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/AllocationFreeOutputStream.java new file mode 100644 index 000000000000..46248182c3f4 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/AllocationFreeOutputStream.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2022, 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package com.oracle.svm.core.heapdump; + +import java.io.IOException; + +/** + * Legacy implementation, only used by other legacy code (see GR-44538). + * + * Simple interface for writing heap dump to arbitrary data source. + */ +public interface AllocationFreeOutputStream { + + void write(int b) throws IOException; + + void write(byte[] b, int offset, int length) throws IOException; + + /** + * close method is called outside of critical section and can allocate objects. + * + * @throws IOException + */ + void close() throws IOException; + + void flush() throws IOException; + +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/HeapDumpSupportImpl.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/HeapDumpSupportImpl.java index cc1107343de5..1ac7046f8550 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/HeapDumpSupportImpl.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/HeapDumpSupportImpl.java @@ -24,128 +24,16 @@ */ package com.oracle.svm.core.heapdump; -import static com.oracle.svm.core.heap.RestrictHeapAccess.Access.NO_ALLOCATION; -import static com.oracle.svm.core.heap.RestrictHeapAccess.Access.UNRESTRICTED; +import java.io.FileOutputStream; -import java.io.IOException; - -import org.graalvm.compiler.api.replacements.Fold; -import org.graalvm.nativeimage.Platform; -import org.graalvm.nativeimage.Platforms; -import org.graalvm.nativeimage.StackValue; -import org.graalvm.nativeimage.c.struct.RawField; -import org.graalvm.nativeimage.c.struct.RawStructure; -import org.graalvm.nativeimage.c.struct.SizeOf; import org.graalvm.nativeimage.impl.HeapDumpSupport; -import org.graalvm.word.Pointer; -import org.graalvm.word.WordFactory; - -import com.oracle.svm.core.UnmanagedMemoryUtil; -import com.oracle.svm.core.heap.RestrictHeapAccess; -import com.oracle.svm.core.heap.VMOperationInfos; -import com.oracle.svm.core.log.Log; -import com.oracle.svm.core.os.RawFileOperationSupport; -import com.oracle.svm.core.os.RawFileOperationSupport.FileCreationMode; -import com.oracle.svm.core.os.RawFileOperationSupport.RawFileDescriptor; -import com.oracle.svm.core.thread.NativeVMOperation; -import com.oracle.svm.core.thread.NativeVMOperationData; -import com.oracle.svm.core.thread.VMOperation; +/* Legacy implementation, only used by other legacy code (see GR-44538). */ public class HeapDumpSupportImpl implements HeapDumpSupport { - private final HeapDumpWriter writer; - private final HeapDumpOperation heapDumpOperation; - - @Platforms(Platform.HOSTED_ONLY.class) - public HeapDumpSupportImpl() { - this.writer = new HeapDumpWriter(); - this.heapDumpOperation = new HeapDumpOperation(); - } - - @Platforms(Platform.HOSTED_ONLY.class) - public void setMetadata(byte[] value) { - this.writer.setMetadata(value); - } - @Override - public void dumpHeap(String filename, boolean gcBefore) throws IOException { - RawFileDescriptor fd = getFileSupport().create(filename, FileCreationMode.CREATE_OR_REPLACE, RawFileOperationSupport.FileAccessMode.READ_WRITE); - if (!getFileSupport().isValid(fd)) { - throw new IOException("Could not create the heap dump file: " + filename); - } - - try { - writeHeapTo(fd, gcBefore); - } finally { - getFileSupport().close(fd); - } - } - - public void writeHeapTo(RawFileDescriptor fd, boolean gcBefore) throws IOException { - int size = SizeOf.get(HeapDumpVMOperationData.class); - HeapDumpVMOperationData data = StackValue.get(size); - UnmanagedMemoryUtil.fill((Pointer) data, WordFactory.unsigned(size), (byte) 0); - - data.setGCBefore(gcBefore); - data.setRawFileDescriptor(fd); - heapDumpOperation.enqueue(data); - - if (!data.getSuccess()) { - throw new IOException("An error occurred while writing the heap dump."); - } - } - - @Fold - static RawFileOperationSupport getFileSupport() { - return RawFileOperationSupport.bigEndian(); - } - - @RawStructure - private interface HeapDumpVMOperationData extends NativeVMOperationData { - @RawField - boolean getGCBefore(); - - @RawField - void setGCBefore(boolean value); - - @RawField - RawFileDescriptor getRawFileDescriptor(); - - @RawField - void setRawFileDescriptor(RawFileDescriptor fd); - - @RawField - boolean getSuccess(); - - @RawField - void setSuccess(boolean value); - } - - private class HeapDumpOperation extends NativeVMOperation { - @Platforms(Platform.HOSTED_ONLY.class) - HeapDumpOperation() { - super(VMOperationInfos.get(HeapDumpOperation.class, "Write heap dump", VMOperation.SystemEffect.SAFEPOINT)); - } - - @Override - @RestrictHeapAccess(access = NO_ALLOCATION, reason = "Heap dumping must not allocate.") - protected void operate(NativeVMOperationData d) { - HeapDumpVMOperationData data = (HeapDumpVMOperationData) d; - if (data.getGCBefore()) { - System.gc(); - } - - try { - boolean success = writer.dumpHeap(data.getRawFileDescriptor()); - data.setSuccess(success); - } catch (Throwable e) { - reportError(e); - } - } - - @RestrictHeapAccess(access = UNRESTRICTED, reason = "Error reporting may allocate.") - private void reportError(Throwable e) { - Log.log().string("An exception occurred during heap dumping. The data in the heap dump file may be corrupt.").newline().string(e.getClass().getName()).string(": ") - .string(e.getMessage()); + public void dumpHeap(String outputFile, boolean live) throws java.io.IOException { + try (FileOutputStream fileOutputStream = new FileOutputStream(outputFile)) { + com.oracle.svm.core.heapdump.HeapDumpWriter.singleton().writeHeapTo(fileOutputStream, live); } } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/HeapDumpUtils.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/HeapDumpUtils.java new file mode 100644 index 000000000000..b359f9aaa5b4 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/HeapDumpUtils.java @@ -0,0 +1,351 @@ +/* + * Copyright (c) 2022, 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package com.oracle.svm.core.heapdump; + +import static com.oracle.svm.core.snippets.KnownIntrinsics.readCallerStackPointer; + +import org.graalvm.compiler.word.Word; +import org.graalvm.nativeimage.CurrentIsolate; +import org.graalvm.nativeimage.ImageSingletons; +import org.graalvm.nativeimage.IsolateThread; +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; +import org.graalvm.nativeimage.c.function.CodePointer; +import org.graalvm.word.Pointer; +import org.graalvm.word.UnsignedWord; +import org.graalvm.word.WordFactory; + +import com.oracle.svm.core.NeverInline; +import com.oracle.svm.core.SubstrateOptions; +import com.oracle.svm.core.code.CodeInfo; +import com.oracle.svm.core.code.CodeInfoTable; +import com.oracle.svm.core.deopt.DeoptimizedFrame; +import com.oracle.svm.core.heap.Heap; +import com.oracle.svm.core.heap.ObjectReferenceVisitor; +import com.oracle.svm.core.heap.ObjectVisitor; +import com.oracle.svm.core.heap.RestrictHeapAccess; +import com.oracle.svm.core.heap.UnknownObjectField; +import com.oracle.svm.core.heap.VMOperationInfos; +import com.oracle.svm.core.hub.DynamicHub; +import com.oracle.svm.core.hub.InteriorObjRefWalker; +import com.oracle.svm.core.hub.LayoutEncoding; +import com.oracle.svm.core.snippets.KnownIntrinsics; +import com.oracle.svm.core.stack.JavaStackWalker; +import com.oracle.svm.core.stack.StackFrameVisitor; +import com.oracle.svm.core.thread.JavaVMOperation; +import com.oracle.svm.core.thread.VMThreads; + +/** + * Legacy implementation, only used by other legacy code (see GR-44538). + * + * A collection of utilities that might assist heap dumps. + */ +public class HeapDumpUtils { + + @UnknownObjectField(types = {byte[].class}) private byte[] fieldsMap; + + /** Extra methods for testing. */ + private final TestingBackDoor testingBackDoor; + + /** Constructor. */ + public HeapDumpUtils() { + this.testingBackDoor = new TestingBackDoor(this); + } + + /** Accessor for the singleton. */ + public static HeapDumpUtils getHeapDumpUtils() { + return ImageSingletons.lookup(HeapDumpUtils.class); + } + + @Platforms(Platform.HOSTED_ONLY.class) + public void setFieldsMap(byte[] map) { + fieldsMap = map; + } + + /** + * Walk all the objects in the heap, both the image heap and the garbage collected heap applying + * a visitor to each. + */ + public boolean walkHeapObjects(ObjectVisitor imageHeapVisitor, ObjectVisitor collectedHeapVisitor) { + final WalkHeapObjectsOperation operation = new WalkHeapObjectsOperation(imageHeapVisitor, collectedHeapVisitor); + return walkHeapObjectsWithoutAllocating(operation); + } + + @RestrictHeapAccess(access = RestrictHeapAccess.Access.NO_ALLOCATION, reason = "Iterating the heap must not allocate in the heap.") + boolean walkHeapObjectsWithoutAllocating(WalkHeapObjectsOperation operation) { + operation.enqueue(); + return operation.getResult(); + } + + public int instanceSizeOf(Class cls) { + final int encoding = DynamicHub.fromClass(cls).getLayoutEncoding(); + if (LayoutEncoding.isPureInstance(encoding)) { + /* + * May underestimate the object size if the identity hashcode field is optional. This is + * the best that what can do because the HPROF format does not support that instances of + * one class have different object sizes. + */ + return (int) LayoutEncoding.getPureInstanceAllocationSize(encoding).rawValue(); + } else { + return 0; + } + } + + boolean isPrimitiveArray(Object obj) { + final int encoding = KnownIntrinsics.readHub(obj).getLayoutEncoding(); + return LayoutEncoding.isPrimitiveArray(encoding); + } + + public boolean isJavaPrimitiveArray(Object obj) { + return (isPrimitiveArray(obj) && + ((obj instanceof char[]) || + (obj instanceof byte[]) || + (obj instanceof int[]) || + (obj instanceof long[]) || + (obj instanceof boolean[]) || + (obj instanceof short[]) || + (obj instanceof double[]) || + (obj instanceof float[]))); + } + + /** + * Return a pointer to an object. The result is untracked by the collector: it is treated as an + * integer. + */ + public Pointer objectToPointer(Object obj) { + return Word.objectToUntrackedPointer(obj); + } + + public byte[] getFieldsMap() { + return fieldsMap; + } + + public boolean walkStacks(StacksSlotsVisitor stacksSlotsVisitor) { + final WalkStacksSlotsOperation walkStacksOperation = new WalkStacksSlotsOperation(stacksSlotsVisitor); + walkStacksOperation.enqueue(); + return walkStacksOperation.getResult(); + } + + private static final class WalkHeapObjectsOperation extends JavaVMOperation { + + /* Instance state. */ + private final ObjectVisitor imageHeapVisitor; + private final ObjectVisitor collectedHeapVisitor; + boolean result; + + /** Constructor. */ + WalkHeapObjectsOperation(ObjectVisitor imageHeapVisitor, ObjectVisitor collectedHeapVisitor) { + super(VMOperationInfos.get(WalkHeapObjectsOperation.class, "Walk Java heap for heap dump", SystemEffect.SAFEPOINT)); + this.imageHeapVisitor = imageHeapVisitor; + this.collectedHeapVisitor = collectedHeapVisitor; + } + + @Override + public void operate() { + operateWithoutAllocation(); + } + + @RestrictHeapAccess(access = RestrictHeapAccess.Access.NO_ALLOCATION, reason = "Do not allocate while walking the heap.") + private void operateWithoutAllocation() { + result = Heap.getHeap().walkImageHeapObjects(imageHeapVisitor) && Heap.getHeap().walkCollectedHeapObjects(collectedHeapVisitor); + } + + private boolean getResult() { + return result; + } + } + + private static final class WalkStacksSlotsOperation extends JavaVMOperation { + + private final StacksSlotsVisitor stacksSlotsVisitor; + private boolean result; + + WalkStacksSlotsOperation(StacksSlotsVisitor stacksSlotsVisitor) { + super(VMOperationInfos.get(WalkStacksSlotsOperation.class, "Walk stack for heap dump", SystemEffect.SAFEPOINT)); + this.stacksSlotsVisitor = stacksSlotsVisitor; + } + + @Override + public void operate() { + operateWithoutAllocation(); + } + + @RestrictHeapAccess(access = RestrictHeapAccess.Access.NO_ALLOCATION, reason = "Do not allocate while walking thread stacks.") + private void operateWithoutAllocation() { + result = stacksSlotsVisitor.visitStacksSlots(); + } + + private boolean getResult() { + return result; + } + } + + public abstract static class StacksSlotsVisitor extends StackFrameVisitor implements ObjectReferenceVisitor { + + private IsolateThread currentVMThread; + private Pointer currentStackSP; + private Pointer currentFrameSP; + private CodePointer currentFrameIP; + private DeoptimizedFrame currentDeoptimizedFrame; + + /** Constructor for subclasses. */ + public StacksSlotsVisitor() { + /* Nothing to do. */ + } + + /* + * Access methods for subclasses. + */ + + /** The current VMThread. */ + protected IsolateThread getVMThread() { + return currentVMThread; + } + + /** The stack pointer for the current VMThread. */ + protected Pointer getStackSP() { + return currentStackSP; + } + + /** The stack pointer for the current frame. */ + protected Pointer getFrameSP() { + return currentFrameSP; + } + + /** The instruction pointer for the current frame. */ + protected CodePointer getFrameIP() { + return currentFrameIP; + } + + /** The DeoptimizedFrame for the current frame. */ + protected DeoptimizedFrame getDeoptimizedFrame() { + return currentDeoptimizedFrame; + } + + @NeverInline("Starting a stack walk in the caller frame") + protected boolean visitStacksSlots() { + /* Visit the current thread, because it does not have a JavaFrameAnchor. */ + currentVMThread = CurrentIsolate.getCurrentThread(); + currentStackSP = readCallerStackPointer(); + JavaStackWalker.walkCurrentThread(currentStackSP, this); + if (SubstrateOptions.MultiThreaded.getValue()) { + /* + * Scan the stacks of all the threads. Other threads will be blocked at a safepoint + * (or in native code) so they will each have a JavaFrameAnchor in their VMThread. + */ + for (IsolateThread vmThread = VMThreads.firstThread(); vmThread.isNonNull(); vmThread = VMThreads.nextThread(vmThread)) { + if (vmThread == CurrentIsolate.getCurrentThread()) { + /* + * The current thread is already scanned by code above, so we do not have to + * do anything for it here. It might have a JavaFrameAnchor from earlier + * Java-to-C transitions, but certainly not at the top of the stack since it + * is running this code, so just this scan would be incomplete. + */ + continue; + } + currentVMThread = vmThread; + currentStackSP = WordFactory.nullPointer(); + currentFrameSP = WordFactory.nullPointer(); + currentFrameIP = WordFactory.nullPointer(); + currentDeoptimizedFrame = null; + JavaStackWalker.walkThread(vmThread, this); + } + } + return true; + } + + @Override + public boolean visitFrame(Pointer sp, CodePointer ip, CodeInfo codeInfo, DeoptimizedFrame deoptimizedFrame) { + /* Notice a change in thread. */ + if (currentStackSP.isNull()) { + currentStackSP = sp; + } + currentFrameSP = sp; + currentFrameIP = ip; + currentDeoptimizedFrame = deoptimizedFrame; + return CodeInfoTable.visitObjectReferences(sp, ip, codeInfo, deoptimizedFrame, this); + } + } + + public TestingBackDoor getTestingBackDoor() { + return testingBackDoor; + } + + /** Expose some {@link HeapDumpUtils} methods for testing. */ + public static class TestingBackDoor { + + /** The HeapDumpUtils instance to use. */ + private final HeapDumpUtils heapDumpUtils; + + /** Constructor. */ + TestingBackDoor(HeapDumpUtils utils) { + this.heapDumpUtils = utils; + } + + public byte[] getFieldsMap() { + return heapDumpUtils.getFieldsMap(); + } + + public int instanceSizeOf(Class cls) { + return heapDumpUtils.instanceSizeOf(cls); + } + + public boolean isJavaPrimitiveArray(Object obj) { + return heapDumpUtils.isJavaPrimitiveArray(obj); + } + + public boolean isPrimitiveArray(Object obj) { + return heapDumpUtils.isPrimitiveArray(obj); + } + + public Pointer objectToPointer(Object obj) { + return heapDumpUtils.objectToPointer(obj); + } + + public long sizeOf(Object obj) { + final long result; + if (obj == null) { + result = 0; + } else { + final UnsignedWord objectSize = LayoutEncoding.getMomentarySizeFromObject(obj); + result = objectSize.rawValue(); + } + return result; + } + + public boolean walkHeapObjects(ObjectVisitor imageHeapVisitor, ObjectVisitor collectedHeapVisitor) { + return heapDumpUtils.walkHeapObjects(imageHeapVisitor, collectedHeapVisitor); + } + + @RestrictHeapAccess(access = RestrictHeapAccess.Access.NO_ALLOCATION, reason = "Iterating the heap must not allocate in the heap.") + public boolean walkInteriorReferences(Object obj, ObjectReferenceVisitor visitor) { + return InteriorObjRefWalker.walkObject(obj, visitor); + } + + public boolean walkStacks(StacksSlotsVisitor stacksSlotsVisitor) { + return heapDumpUtils.walkStacks(stacksSlotsVisitor); + } + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/HeapDumpWriter.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/HeapDumpWriter.java index ad4856a016c2..176776c12a03 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/HeapDumpWriter.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/HeapDumpWriter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2022, 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 @@ -24,1132 +24,33 @@ */ package com.oracle.svm.core.heapdump; -import static com.oracle.svm.core.heap.RestrictHeapAccess.Access.NO_ALLOCATION; +import java.io.FileOutputStream; +import java.io.IOException; -import org.graalvm.compiler.api.replacements.Fold; -import org.graalvm.compiler.core.common.NumUtil; -import org.graalvm.compiler.nodes.java.ArrayLengthNode; -import org.graalvm.compiler.word.ObjectAccess; -import org.graalvm.compiler.word.Word; -import org.graalvm.nativeimage.CurrentIsolate; -import org.graalvm.nativeimage.IsolateThread; -import org.graalvm.nativeimage.Platform; -import org.graalvm.nativeimage.Platforms; -import org.graalvm.nativeimage.StackValue; -import org.graalvm.nativeimage.c.function.CodePointer; -import org.graalvm.word.Pointer; -import org.graalvm.word.UnsignedWord; -import org.graalvm.word.WordBase; -import org.graalvm.word.WordFactory; +import org.graalvm.nativeimage.ImageSingletons; -import com.oracle.svm.core.NeverInline; -import com.oracle.svm.core.StaticFieldsSupport; -import com.oracle.svm.core.SubstrateOptions; -import com.oracle.svm.core.c.NonmovableArray; -import com.oracle.svm.core.code.CodeInfo; -import com.oracle.svm.core.code.CodeInfoAccess; -import com.oracle.svm.core.code.CodeInfoDecoder; -import com.oracle.svm.core.code.CodeInfoTable; -import com.oracle.svm.core.code.FrameInfoQueryResult; -import com.oracle.svm.core.code.RuntimeCodeCache; -import com.oracle.svm.core.code.RuntimeCodeInfoAccess; -import com.oracle.svm.core.code.RuntimeCodeInfoMemory; -import com.oracle.svm.core.code.SimpleCodeInfoQueryResult; -import com.oracle.svm.core.collections.GrowableWordArray; -import com.oracle.svm.core.collections.GrowableWordArrayAccess; -import com.oracle.svm.core.config.ConfigurationValues; -import com.oracle.svm.core.deopt.DeoptimizedFrame; -import com.oracle.svm.core.heap.CodeReferenceMapDecoder; -import com.oracle.svm.core.heap.Heap; -import com.oracle.svm.core.heap.NoAllocationVerifier; -import com.oracle.svm.core.heap.ObjectReferenceVisitor; -import com.oracle.svm.core.heap.ObjectVisitor; -import com.oracle.svm.core.heap.ReferenceAccess; -import com.oracle.svm.core.heap.ReferenceMapIndex; -import com.oracle.svm.core.heap.RestrictHeapAccess; -import com.oracle.svm.core.heap.UnknownObjectField; -import com.oracle.svm.core.heapdump.HeapDumpMetadata.ClassInfo; -import com.oracle.svm.core.heapdump.HeapDumpMetadata.ClassInfoAccess; -import com.oracle.svm.core.heapdump.HeapDumpMetadata.FieldInfo; -import com.oracle.svm.core.heapdump.HeapDumpMetadata.FieldInfoAccess; -import com.oracle.svm.core.heapdump.HeapDumpMetadata.FieldInfoPointer; -import com.oracle.svm.core.heapdump.HeapDumpMetadata.FieldName; -import com.oracle.svm.core.heapdump.HeapDumpMetadata.FieldNameAccess; -import com.oracle.svm.core.hub.DynamicHub; -import com.oracle.svm.core.hub.LayoutEncoding; -import com.oracle.svm.core.log.Log; -import com.oracle.svm.core.os.BufferedFileOperationSupport; -import com.oracle.svm.core.os.BufferedFileOperationSupport.BufferedFile; -import com.oracle.svm.core.os.RawFileOperationSupport.RawFileDescriptor; -import com.oracle.svm.core.snippets.KnownIntrinsics; -import com.oracle.svm.core.stack.JavaStackWalker; -import com.oracle.svm.core.stack.StackFrameVisitor; -import com.oracle.svm.core.thread.PlatformThreads; -import com.oracle.svm.core.thread.ThreadingSupportImpl; -import com.oracle.svm.core.thread.VMOperation; -import com.oracle.svm.core.thread.VMThreads; -import com.oracle.svm.core.threadlocal.VMThreadLocalMTSupport; -import com.oracle.svm.core.util.VMError; - -/** - * This class dumps the image heap and the Java heap into a file (HPROF binary format), similar to - * the HotSpot classes in {@code heapDumper.cpp}. The heap dumping needs additional metadata that is - * encoded during the image build and that is stored in the image heap in a compact binary format - * (see {@code HeapDumpFeature} and {@link HeapDumpMetadata}). - * - * The actual heap dumping logic is executed in a VM operation, so it is safe to iterate over all - * live threads. The heap dumper is implemented as a singleton and only a single heap dumping - * operation can be in progress at a given time. - * - * The heap dumping code must not modify the Java heap in any way as this would pollute or corrupt - * the heap dump. Therefore, no Java datastructures can be used and all data structures must be - * allocated using native memory instead. This also guarantees that the heap dumping works in case - * that Native Image is completely out of Java heap memory (see option - * {@code -XX:HeapDumpOnOutOfMemoryError}). - * - * The heap dump is stored in the HPROF binary format (big endian byte order). The high-level - * structure of the file is roughly as follows: - * - *
- *   file header
- *   ([top level record] [sub record]*)+
- *   end marker
- * 
- * - * Other relevant file format aspects: - *
    - *
  • References to objects or symbols are encoded using their word-sized address. This is done - * regardless if compressed references are enabled or not. Tools (such as VisualVM) that read HPROF - * files, therefore use heuristics to detect if compressed references were used at run-time.
  • - *
  • Symbols such as class or method names are encoded as UTF8.
  • - *
  • The size of individual records is limited to {@link #MAX_UNSIGNED_INT}. So, very large arrays - * that have a larger size than {@link #MAX_UNSIGNED_INT} need to be truncated.
  • - *
- */ -public class HeapDumpWriter { - private static final long MAX_UNSIGNED_INT = (1L << 32) - 1; - - private static final int DUMMY_STACK_TRACE_ID = 1; - private static final int LARGE_OBJECT_THRESHOLD = 1 * 1024 * 1024; - private static final int HEAP_DUMP_SEGMENT_TARGET_SIZE = 1 * 1024 * 1024; - - private final NoAllocationVerifier noAllocationVerifier = NoAllocationVerifier.factory("HeapDumpWriter", false); - private final DumpStackFrameVisitor dumpStackFrameVisitor = new DumpStackFrameVisitor(); - private final DumpObjectsVisitor dumpObjectsVisitor = new DumpObjectsVisitor(); - private final CodeMetadataVisitor codeMetadataVisitor = new CodeMetadataVisitor(); - private final ThreadLocalsVisitor threadLocalsVisitor = new ThreadLocalsVisitor(); - @UnknownObjectField(types = {byte[].class}) private byte[] metadata; - - private BufferedFile f; - private long topLevelRecordBegin = -1; - private long subRecordBegin = -1; - private boolean error; - - @Platforms(Platform.HOSTED_ONLY.class) - public HeapDumpWriter() { - } - - @Platforms(Platform.HOSTED_ONLY.class) - public void setMetadata(byte[] value) { - this.metadata = value; - } - - public boolean dumpHeap(RawFileDescriptor fd) { - assert VMOperation.isInProgressAtSafepoint(); - assert ThreadingSupportImpl.isRecurringCallbackPaused(); - - noAllocationVerifier.open(); - try { - Heap.getHeap().suspendAllocation(); - return dumpHeap0(fd); - } finally { - noAllocationVerifier.close(); - } - } - - private boolean dumpHeap0(RawFileDescriptor fd) { - boolean initialized = initialize(fd); - try { - if (initialized) { - return writeHeapDump(); - } else { - Log.log().string("An error occurred while initializing the heap dump infrastructure. No heap data will be dumped.").newline(); - return false; - } - } finally { - /* teardown must always be executed, even if the initialization failed. */ - teardown(); - } - } - - private boolean initialize(RawFileDescriptor fd) { - assert topLevelRecordBegin == -1 && subRecordBegin == -1 && !error; - - this.f = file().allocate(fd); - if (f.isNull()) { - return false; - } - return HeapDumpMetadata.initialize(metadata); - } - - private void teardown() { - HeapDumpMetadata.teardown(); - - assert f.isNull() || error || file().getUnflushedDataSize(f) == 0; - file().free(f); - this.f = WordFactory.nullPointer(); - - this.topLevelRecordBegin = -1; - this.subRecordBegin = -1; - this.error = false; - } - - @NeverInline("Starting a stack walk in the caller frame.") - private boolean writeHeapDump() { - /* - * Only read the stack pointer for the current thread once. This ensures consistency for all - * the information that we dump about the stack of the current thread. - */ - Pointer currentThreadSp = KnownIntrinsics.readCallerStackPointer(); - - writeHeader(); - writeClassNames(); // UTF-8 symbols - writeFieldNames(); // UTF-8 symbols - writeLoadedClasses(); // LOAD_CLASS - writeStackTraces(currentThreadSp); // FRAME and TRACE - - /* 1..n HEAP_DUMP_SEGMENT records */ - startTopLevelRecord(HProfTopLevelRecord.HEAP_DUMP_SEGMENT); - writeClasses(); // GC_CLASS_DUMP - writeThreads(currentThreadSp); // GC_ROOT_THREAD_OBJ, GC_ROOT_JAVA_FRAME, GC_ROOT_JNI_LOCAL - writeJNIGlobals(); // GC_ROOT_JNI_GLOBAL - writeStickyClasses(); // GC_ROOT_STICKY_CLASS - writeObjects(); // GC_INSTANCE_DUMP, GC_OBJ_ARRAY_DUMP, GC_PRIM_ARRAY_DUMP - endTopLevelRecord(); - - startTopLevelRecord(HProfTopLevelRecord.HEAP_DUMP_END); - endTopLevelRecord(); - - flush(); - - if (error) { - Log.log().string("An error occurred while writing the heap dump data. The data in the heap dump file may be corrupt.").newline(); - return false; - } - return true; - } - - private void writeHeader() { - writeUTF8("JAVA PROFILE 1.0.2"); - writeByte((byte) 0); - writeInt(wordSize()); - writeLong(System.currentTimeMillis()); - } - - private void startTopLevelRecord(HProfTopLevelRecord tag) { - assert topLevelRecordBegin == -1; - writeByte(tag.getValue()); - writeInt(0); // timestamp - writeInt(0); // length (patched later on) - topLevelRecordBegin = getPosition(); - } - - private void endTopLevelRecord() { - assert topLevelRecordBegin > 0; - long currentPosition = getPosition(); - setPosition(topLevelRecordBegin - Integer.BYTES); - writeInt(NumUtil.safeToUInt(currentPosition - topLevelRecordBegin)); - setPosition(currentPosition); - topLevelRecordBegin = -1; - } - - private void startSubRecord(HProfSubRecord tag, long size) { - assert topLevelRecordBegin > 0 : "must be within a HEAP_DUMP_SEGMENT"; - long heapDumpSegmentSize = getPosition() - topLevelRecordBegin; - if (heapDumpSegmentSize > 0 && heapDumpSegmentSize + size > HEAP_DUMP_SEGMENT_TARGET_SIZE) { - endTopLevelRecord(); - startTopLevelRecord(HProfTopLevelRecord.HEAP_DUMP_SEGMENT); - } - - subRecordBegin = getPosition(); - writeByte(tag.getValue()); - } - - private void endSubRecord(long recordSize) { - assert subRecordBegin > 0; - assert subRecordBegin + recordSize == getPosition(); - subRecordBegin = -1; - } - - private void writeClassNames() { - for (int i = 0; i < HeapDumpMetadata.getClassInfoCount(); i++) { - ClassInfo classInfo = HeapDumpMetadata.getClassInfo(i); - if (ClassInfoAccess.isValid(classInfo)) { - writeSymbol(classInfo.getHub().getName()); - } - } - } - - private void writeSymbol(String value) { - startTopLevelRecord(HProfTopLevelRecord.UTF8); - writeObjectId(value); - writeUTF8(value); - endTopLevelRecord(); - } - - private void writeSymbol(FieldName fieldName) { - startTopLevelRecord(HProfTopLevelRecord.UTF8); - writeFieldNameId(fieldName); - write((Pointer) FieldNameAccess.getChars(fieldName), WordFactory.unsigned(FieldNameAccess.getLength(fieldName))); - endTopLevelRecord(); - } - - private void writeFieldNames() { - for (int i = 0; i < HeapDumpMetadata.getFieldNameCount(); i++) { - FieldName fieldName = HeapDumpMetadata.getFieldName(i); - writeSymbol(fieldName); - } - } - - private void writeLoadedClasses() { - for (int i = 0; i < HeapDumpMetadata.getClassInfoCount(); i++) { - ClassInfo classInfo = HeapDumpMetadata.getClassInfo(i); - if (ClassInfoAccess.isValid(classInfo)) { - DynamicHub hub = classInfo.getHub(); - if (hub.isLoaded()) { - startTopLevelRecord(HProfTopLevelRecord.LOAD_CLASS); - writeInt(classInfo.getSerialNum()); - writeClassId(hub); - writeInt(DUMMY_STACK_TRACE_ID); - writeObjectId(hub.getName()); - endTopLevelRecord(); - } - } - } - } - - private void writeStackTraces(Pointer currentThreadSp) { - writeDummyStackTrace(); - - /* Write the stack traces of all threads. */ - long nextFrameId = 1; - int threadSerialNum = 1; - for (IsolateThread isolateThread = VMThreads.firstThread(); isolateThread.isNonNull(); isolateThread = VMThreads.nextThread(isolateThread)) { - /* Write FRAME records. */ - int writtenFrames = dumpStackData(isolateThread, currentThreadSp, threadSerialNum, nextFrameId, false); - - /* Write TRACE record that references the FRAME records. */ - startTopLevelRecord(HProfTopLevelRecord.TRACE); - int stackSerialNum = threadSerialNum + DUMMY_STACK_TRACE_ID; - writeInt(stackSerialNum); - writeInt(threadSerialNum); - writeInt(writtenFrames); - for (int i = 0; i < writtenFrames; i++) { - writeFrameId(nextFrameId++); - } - endTopLevelRecord(); - - threadSerialNum++; - } - } - - private int dumpStackData(IsolateThread isolateThread, Pointer currentThreadSp, int threadSerialNum, long nextFrameId, boolean markGCRoots) { - dumpStackFrameVisitor.initialize(threadSerialNum, nextFrameId, markGCRoots); - if (isolateThread == CurrentIsolate.getCurrentThread()) { - JavaStackWalker.walkCurrentThread(currentThreadSp, dumpStackFrameVisitor); - } else { - JavaStackWalker.walkThread(isolateThread, dumpStackFrameVisitor); - } - return dumpStackFrameVisitor.getWrittenFrames(); - } - - /** Writes an empty TRACE record that can be used in other records. */ - private void writeDummyStackTrace() { - startTopLevelRecord(HProfTopLevelRecord.TRACE); - writeInt(DUMMY_STACK_TRACE_ID); - writeInt(0); // thread serial number - writeInt(0); // number of frames - endTopLevelRecord(); - } - - private void writeClasses() { - for (int i = 0; i < HeapDumpMetadata.getClassInfoCount(); i++) { - ClassInfo classInfo = HeapDumpMetadata.getClassInfo(i); - if (ClassInfoAccess.isValid(classInfo)) { - if (classInfo.getHub().isLoaded()) { - writeClassDumpRecord(classInfo); - } - } - } - } - - private void writeClassDumpRecord(ClassInfo classInfo) { - int staticFieldsCount = classInfo.getStaticFieldCount(); - int staticFieldsSize = staticFieldsCount * (wordSize() + 1) + HeapDumpMetadata.computeFieldsDumpSize(classInfo.getStaticFields(), classInfo.getStaticFieldCount()); - int instanceFieldsCount = classInfo.getInstanceFieldCount(); - int instanceFieldsSize = instanceFieldsCount * (wordSize() + 1); - int recordSize = 1 + wordSize() + 4 + 6 * wordSize() + 4 + 2 + 2 + staticFieldsSize + 2 + instanceFieldsSize; - - Class clazz = DynamicHub.toClass(classInfo.getHub()); - startSubRecord(HProfSubRecord.GC_CLASS_DUMP, recordSize); - writeClassId(clazz); - writeInt(DUMMY_STACK_TRACE_ID); - writeClassId(clazz.getSuperclass()); - writeObjectId(getClassLoader(clazz)); - writeObjectId(clazz.getSigners()); - writeObjectId(null); // protection domain - writeObjectId(null); // reserved field - writeObjectId(null); // reserved field - writeInt(getObjectSizeInHeap(clazz)); - writeShort((short) 0); // size of constant pool - writeFieldDescriptors(staticFieldsCount, classInfo.getStaticFields(), true); - writeFieldDescriptors(instanceFieldsCount, classInfo.getInstanceFields(), false); - endSubRecord(recordSize); - } - - private void writeFieldDescriptors(int fieldCount, FieldInfoPointer fieldInfos, boolean isStatic) { - writeShort(NumUtil.safeToUShort(fieldCount)); - for (int i = 0; i < fieldCount; i++) { - FieldInfo field = fieldInfos.addressOf(i).read(); - writeFieldNameId(FieldInfoAccess.getFieldName(field)); - HProfType type = FieldInfoAccess.getType(field); - writeType(type); - - if (isStatic) { - /* For static fields, write the field value to the heap dump as well. */ - Object dataHolder = getStaticFieldDataHolder(type); - writeFieldData(dataHolder, field); - } - } - } - - private static Object getStaticFieldDataHolder(HProfType type) { - if (type == HProfType.NORMAL_OBJECT) { - return StaticFieldsSupport.getStaticObjectFields(); - } else { - return StaticFieldsSupport.getStaticPrimitiveFields(); - } - } - - private void writeFieldData(Object dataHolder, FieldInfo field) { - Pointer p = Word.objectToUntrackedPointer(dataHolder); - int location = FieldInfoAccess.getLocation(field); - HProfType type = FieldInfoAccess.getType(field); - switch (type) { - case BOOLEAN, BYTE -> writeByte(p.readByte(location)); - case CHAR -> writeChar(p.readChar(location)); - case SHORT -> writeShort(p.readShort(location)); - case INT -> writeInt(p.readInt(location)); - case LONG -> writeLong(p.readLong(location)); - case FLOAT -> writeFloat(p.readFloat(location)); - case DOUBLE -> writeDouble(p.readDouble(location)); - case NORMAL_OBJECT -> writeObjectId(ReferenceAccess.singleton().readObjectAt(p.add(location), true)); - default -> throw VMError.shouldNotReachHere("Unexpected type."); - } - } - - private void writeThreads(Pointer currentThreadSp) { - long nextFrameId = 1; - int threadSerialNum = 1; - for (IsolateThread isolateThread = VMThreads.firstThread(); isolateThread.isNonNull(); isolateThread = VMThreads.nextThread(isolateThread)) { - int stackTraceSerialNum = threadSerialNum + DUMMY_STACK_TRACE_ID; - /* - * If a thread is not fully initialized yet, then the java.lang.Thread object may still - * be null. In this case, a thread object id of 0 is written, which is fine according to - * the HPROF specification. - */ - Thread thread = PlatformThreads.fromVMThread(isolateThread); - writeThread(thread, threadSerialNum, stackTraceSerialNum); - nextFrameId += dumpStackData(isolateThread, currentThreadSp, threadSerialNum, nextFrameId, true); - - writeThreadLocals(isolateThread, threadSerialNum); - threadSerialNum++; - } - } - - private void writeThread(Thread threadObj, int threadSerialNum, int stackTraceSerialNum) { - int recordSize = 1 + wordSize() + 4 + 4; - startSubRecord(HProfSubRecord.GC_ROOT_THREAD_OBJ, recordSize); - writeObjectId(threadObj); - writeInt(threadSerialNum); - writeInt(stackTraceSerialNum); - endSubRecord(recordSize); - } - - private void writeThreadLocals(IsolateThread isolateThread, int threadSerialNum) { - if (SubstrateOptions.MultiThreaded.getValue()) { - threadLocalsVisitor.initialize(threadSerialNum); - VMThreadLocalMTSupport.singleton().walk(isolateThread, threadLocalsVisitor); - } - } - - private void writeJNIGlobals() { - /* All objects that are referenced by the runtime code cache are GC roots. */ - RuntimeCodeInfoMemory.singleton().walkRuntimeMethods(codeMetadataVisitor); - } - - private void writeStickyClasses() { - for (int i = 0; i < HeapDumpMetadata.getClassInfoCount(); i++) { - ClassInfo classInfo = HeapDumpMetadata.getClassInfo(i); - if (ClassInfoAccess.isValid(classInfo)) { - int recordSize = 1 + wordSize(); - startSubRecord(HProfSubRecord.GC_ROOT_STICKY_CLASS, recordSize); - writeClassId(classInfo.getHub()); - endSubRecord(recordSize); - } - } - } - - private void writeObjects() { - GrowableWordArray largeObjects = StackValue.get(GrowableWordArray.class); - GrowableWordArrayAccess.initialize(largeObjects); - try { - dumpObjectsVisitor.initialize(largeObjects); - Heap.getHeap().walkImageHeapObjects(dumpObjectsVisitor); - - dumpObjectsVisitor.initialize(largeObjects); - Heap.getHeap().walkCollectedHeapObjects(dumpObjectsVisitor); - - /* Large objects are collected and written separately. */ - writeLargeObjects(largeObjects); - } finally { - GrowableWordArrayAccess.freeData(largeObjects); - largeObjects = WordFactory.nullPointer(); - } - } - - private void writeLargeObjects(GrowableWordArray largeObjects) { - int count = largeObjects.getSize(); - for (int i = 0; i < count; i++) { - Word rawObj = GrowableWordArrayAccess.get(largeObjects, i); - writeObject(rawObj.toObject()); - } - } - - private static ClassLoader getClassLoader(Class clazz) { - Class c = clazz; - while (c.isArray()) { - c = c.getComponentType(); - } - return c.getClassLoader(); - } - - private static int getObjectSizeInHeap(Class cls) { - DynamicHub hub = DynamicHub.fromClass(cls); - int encoding = hub.getLayoutEncoding(); - if (LayoutEncoding.isPureInstance(encoding)) { - /* - * May underestimate the object size if the identity hashcode field is optional. This is - * the best that what can do because the HPROF format does not support that instances of - * one class have different object sizes. - */ - return (int) LayoutEncoding.getPureInstanceAllocationSize(encoding).rawValue(); - } else if (LayoutEncoding.isHybrid(encoding)) { - /* For hybrid objects, return the size of the fields. */ - return LayoutEncoding.getArrayBaseOffsetAsInt(encoding); - } else { - /* Variable size. */ - return 0; - } - } - - private void writeObject(Object obj) { - DynamicHub hub = KnownIntrinsics.readHub(obj); - int layoutEncoding = hub.getLayoutEncoding(); - if (LayoutEncoding.isArray(layoutEncoding)) { - if (LayoutEncoding.isPrimitiveArray(layoutEncoding)) { - writePrimitiveArray(obj, layoutEncoding); - } else { - writeObjectArray(obj); - } - } else { - /* - * Hybrid objects are handled here as well. This means that the array part of hybrid - * objects is currently skipped. Eventually, we should probably dump the array part as a - * separate object. - */ - writeInstance(obj); - } - - if (Heap.getHeap().isInImageHeap(obj)) { - markImageHeapObjectAsGCRoot(obj); - } - - /* - * Ideally, we would model Java monitors as instance fields as they are only reachable if - * the object that owns the monitor is reachable. However, that is not possible because the - * synthetic monitor field could overlap or collide with a normal field of a subclass. - * Therefore, we simply mark all monitors as GC roots. - */ - int monitorOffset = hub.getMonitorOffset(); - if (monitorOffset != 0) { - Object monitor = ObjectAccess.readObject(obj, monitorOffset); - if (monitor != null) { - markMonitorAsGCRoot(monitor); - } - } - } - - private void markMonitorAsGCRoot(Object monitor) { - int recordSize = 1 + wordSize(); - startSubRecord(HProfSubRecord.GC_ROOT_MONITOR_USED, recordSize); - writeObjectId(monitor); - endSubRecord(recordSize); - } - - /** We mark image heap objects as GC_ROOT_JNI_GLOBAL. */ - private void markImageHeapObjectAsGCRoot(Object obj) { - assert Heap.getHeap().isInImageHeap(obj); - markAsJniGlobalGCRoot(obj); - } - - private void markAsJniGlobalGCRoot(Object obj) { - int recordSize = 1 + 2 * wordSize(); - startSubRecord(HProfSubRecord.GC_ROOT_JNI_GLOBAL, recordSize); - writeObjectId(obj); - writeObjectId(null); // global ref ID - endSubRecord(recordSize); - } - - private void writeInstance(Object obj) { - ClassInfo classInfo = HeapDumpMetadata.getClassInfo(obj.getClass()); - int instanceFieldsSize = classInfo.getInstanceFieldsDumpSize(); - int recordSize = 1 + wordSize() + 4 + wordSize() + 4 + instanceFieldsSize; - - startSubRecord(HProfSubRecord.GC_INSTANCE_DUMP, recordSize); - writeObjectId(obj); - writeInt(DUMMY_STACK_TRACE_ID); - writeClassId(obj.getClass()); - writeInt(instanceFieldsSize); - - /* Write the field data. */ - do { - int instanceFieldCount = classInfo.getInstanceFieldCount(); - FieldInfoPointer instanceFields = classInfo.getInstanceFields(); - for (int i = 0; i < instanceFieldCount; i++) { - FieldInfo field = instanceFields.addressOf(i).read(); - writeFieldData(obj, field); - } - classInfo = HeapDumpMetadata.getClassInfo(classInfo.getHub().getSuperHub()); - } while (classInfo.isNonNull()); - - endSubRecord(recordSize); - } - - private void writePrimitiveArray(Object array, int layoutEncoding) { - int arrayBaseOffset = LayoutEncoding.getArrayBaseOffsetAsInt(layoutEncoding); - int elementSize = LayoutEncoding.getArrayIndexScale(layoutEncoding); - - int recordHeaderSize = 1 + wordSize() + 2 * 4 + 1; - int length = calculateMaxArrayLength(array, elementSize, recordHeaderSize); - long recordSize = recordHeaderSize + ((long) length) * elementSize; - - startSubRecord(HProfSubRecord.GC_PRIM_ARRAY_DUMP, recordSize); - writeObjectId(array); - writeInt(DUMMY_STACK_TRACE_ID); - writeInt(length); - - /* The file is big endian, so we need to read & write the data element-wise. */ - if (array instanceof boolean[]) { - writeType(HProfType.BOOLEAN); - writeU1ArrayData(array, length, arrayBaseOffset); - } else if (array instanceof byte[]) { - writeType(HProfType.BYTE); - writeU1ArrayData(array, length, arrayBaseOffset); - } else if (array instanceof short[]) { - writeType(HProfType.SHORT); - writeU2ArrayData(array, length, arrayBaseOffset); - } else if (array instanceof char[]) { - writeType(HProfType.CHAR); - writeU2ArrayData(array, length, arrayBaseOffset); - } else if (array instanceof int[]) { - writeType(HProfType.INT); - writeU4ArrayData(array, length, arrayBaseOffset); - } else if (array instanceof float[]) { - writeType(HProfType.FLOAT); - writeU4ArrayData(array, length, arrayBaseOffset); - } else if (array instanceof long[]) { - writeType(HProfType.LONG); - writeU8ArrayData(array, length, arrayBaseOffset); - } else if (array instanceof double[]) { - writeType(HProfType.DOUBLE); - writeU8ArrayData(array, length, arrayBaseOffset); - } else { - /* Word arrays are primitive arrays as well */ - assert WordBase.class.isAssignableFrom(array.getClass().getComponentType()); - assert elementSize == wordSize(); - writeWordArray(array, length, arrayBaseOffset); - } - endSubRecord(recordSize); - } - - private void writeObjectArray(Object array) { - int recordHeaderSize = 1 + 2 * 4 + 2 * wordSize(); - /* In the heap dump, object array elements are always uncompressed. */ - int length = calculateMaxArrayLength(array, wordSize(), recordHeaderSize); - long recordSize = recordHeaderSize + ((long) length) * wordSize(); - - startSubRecord(HProfSubRecord.GC_OBJ_ARRAY_DUMP, recordSize); - writeObjectId(array); - writeInt(DUMMY_STACK_TRACE_ID); - writeInt(length); - writeClassId(array.getClass()); - - Object[] data = (Object[]) array; - for (int i = 0; i < length; i++) { - writeObjectId(data[i]); - } - endSubRecord(recordSize); - } - - /* - * The size of individual HPROF records is limited to {@link #MAX_UNSIGNED_INT}, which means - * that very large arrays need to truncated. +/** Legacy implementation, only used by other legacy code (see GR-44538). */ +public abstract class HeapDumpWriter { + /** + * Writes heap in hprof format to ordinary file. + * + * @param fileOutputStream Underlying file stream to write the bytes to + * @param gcBefore Run GC before dumping the heap. + * @throws IOException */ - private static int calculateMaxArrayLength(Object array, int elementSize, int recordHeaderSize) { - int length = ArrayLengthNode.arrayLength(array); - UnsignedWord lengthInBytes = WordFactory.unsigned(length).multiply(elementSize); - UnsignedWord maxBytes = WordFactory.unsigned(MAX_UNSIGNED_INT).subtract(recordHeaderSize); - - if (lengthInBytes.belowOrEqual(maxBytes)) { - return length; - } - - UnsignedWord newLength = maxBytes.unsignedDivide(elementSize); - Log.log().string("Cannot dump very large arrays. Array is truncated to ").unsigned(newLength).string(" elements.").newline(); - return NumUtil.safeToInt(newLength.rawValue()); - } - - private void writeWordArray(Object array, int length, int arrayBaseOffset) { - if (wordSize() == 8) { - writeType(HProfType.LONG); - writeU8ArrayData(array, length, arrayBaseOffset); - } else { - assert wordSize() == 4; - writeType(HProfType.INT); - writeU4ArrayData(array, length, arrayBaseOffset); - } - } - - private void writeU1ArrayData(Object array, int length, int arrayBaseOffset) { - Pointer data = getArrayData(array, arrayBaseOffset); - write(data, WordFactory.unsigned(length)); - } - - private void writeU2ArrayData(Object array, int length, int arrayBaseOffset) { - Pointer cur = getArrayData(array, arrayBaseOffset); - for (int i = 0; i < length; i++) { - writeChar(cur.readChar(0)); - cur = cur.add(2); - } - } - - private void writeU4ArrayData(Object array, int length, int arrayBaseOffset) { - Pointer cur = getArrayData(array, arrayBaseOffset); - for (int i = 0; i < length; i++) { - writeInt(cur.readInt(0)); - cur = cur.add(4); - } - } - - private void writeU8ArrayData(Object array, int length, int arrayBaseOffset) { - Pointer cur = getArrayData(array, arrayBaseOffset); - for (int i = 0; i < length; i++) { - writeLong(cur.readLong(0)); - cur = cur.add(8); - } - } - - private static Pointer getArrayData(Object array, int arrayBaseOffset) { - return Word.objectToUntrackedPointer(array).add(arrayBaseOffset); - } - - private void writeByte(byte value) { - boolean success = file().writeByte(f, value); - handleError(success); - } - - private void writeShort(short value) { - boolean success = file().writeShort(f, value); - handleError(success); - } - - private void writeChar(char value) { - boolean success = file().writeChar(f, value); - handleError(success); - } - - private void writeInt(int value) { - boolean success = file().writeInt(f, value); - handleError(success); - } - - private void writeLong(long value) { - boolean success = file().writeLong(f, value); - handleError(success); - } - - private void writeFloat(float value) { - boolean success = file().writeFloat(f, value); - handleError(success); - } - - private void writeDouble(double value) { - boolean success = file().writeDouble(f, value); - handleError(success); - } - - private void writeType(HProfType type) { - writeByte(type.getValue()); - } - - private void writeObjectId(Object obj) { - writeId0(Word.objectToUntrackedPointer(obj).rawValue()); - } - - private void writeClassId(Class clazz) { - writeClassId(DynamicHub.fromClass(clazz)); - } - - private void writeClassId(DynamicHub hub) { - /* - * HotSpot writes Class objects only as GC_CLASS_DUMP and never as GC_INSTANCE_DUMP records. - * It also always uses the address of the mirror class as the class id. This has the effect - * that the heap dump only contains a very limited set of information for class objects. - * - * It is handy to have detailed information about the DynamicHub in the heap dump. Ideally, - * we would just write both a GC_CLASS_DUMP and a GC_INSTANCE_DUMP record with the same id - * but that breaks VisualVM in a weird way. Therefore, we are using different ids for the - * GC_CLASS_DUMP and GC_INSTANCE_DUMP records. - */ - Word hubAddress = Word.objectToUntrackedPointer(hub); - if (hubAddress.isNonNull()) { - hubAddress = hubAddress.add(1); - } - writeId0(hubAddress.rawValue()); - } - - private void writeFieldNameId(FieldName fieldName) { - writeId0(fieldName.rawValue()); - } - - private void writeFrameId(long frameId) { - writeId0(frameId); - } - - private void writeId0(long value) { - boolean success; - if (wordSize() == 8) { - success = file().writeLong(f, value); - } else { - assert wordSize() == 4; - success = file().writeInt(f, (int) value); - } - handleError(success); - } - - private void writeUTF8(String value) { - boolean success = file().writeUTF8(f, value); - handleError(success); - } - - private void write(Pointer data, UnsignedWord size) { - boolean success = file().write(f, data, size); - handleError(success); - } - - private long getPosition() { - long result = file().position(f); - handleError(result >= 0); - return result; - } - - private void setPosition(long newPos) { - boolean success = file().seek(f, newPos); - handleError(success); - } - - private void flush() { - boolean success = file().flush(f); - handleError(success); - } - - private void handleError(boolean success) { - if (!success) { - error = true; - } - } - - @Fold - static BufferedFileOperationSupport file() { - return BufferedFileOperationSupport.bigEndian(); - } - - @Fold - static int wordSize() { - return ConfigurationValues.getTarget().wordSize; - } + public abstract void writeHeapTo(FileOutputStream fileOutputStream, boolean gcBefore) throws IOException; /** - * This class is used for different purposes: - *
    - *
  • Write a {@link HProfTopLevelRecord#FRAME} top-level record for every frame on the - * stack.
  • - *
  • Write a GC_ROOT sub-record for every deoptimized frame and for every reference that is on - * the stack.
  • - *
+ * Writes heap in hprof format to output stream, which does not support seeking. * - * Unfortunately, it is not possible to write all the information in a single pass because the - * data needs to end up in different HPROF records (top-level vs. sub-records). The data from - * the different passes needs to match, so it is easier to implement both passes in one class - * and to use a {@link #markGCRoots field} to determine which data should be written. + * @param outputStream Underlying stream to write the bytes to + * @param gcBefore Run GC before dumping the heap. + * @throws IOException */ - private class DumpStackFrameVisitor extends StackFrameVisitor implements ObjectReferenceVisitor { - private static final int LINE_NUM_NATIVE_METHOD = -3; - - private final CodeInfoDecoder.FrameInfoCursor frameInfoCursor = new CodeInfoDecoder.FrameInfoCursor(); - - private int threadSerialNum; - private long initialNextFrameId; - private long nextFrameId; - private boolean markGCRoots; - - @Platforms(Platform.HOSTED_ONLY.class) - DumpStackFrameVisitor() { - } - - @SuppressWarnings("hiding") - public void initialize(int threadSerialNum, long nextFrameId, boolean markGCRoots) { - assert nextFrameId > 0; - assert threadSerialNum > 0; - assert nextFrameId > 0; - - this.threadSerialNum = threadSerialNum; - this.initialNextFrameId = nextFrameId; - this.nextFrameId = nextFrameId; - this.markGCRoots = markGCRoots; - } - - public int getWrittenFrames() { - return NumUtil.safeToInt(nextFrameId - initialNextFrameId); - } - - @Override - @RestrictHeapAccess(access = NO_ALLOCATION, reason = "Heap dumping must not allocate.") - protected boolean visitFrame(Pointer sp, CodePointer ip, CodeInfo codeInfo, DeoptimizedFrame deoptimizedFrame) { - if (deoptimizedFrame != null) { - markAsGCRoot(deoptimizedFrame); - - for (DeoptimizedFrame.VirtualFrame frame = deoptimizedFrame.getTopFrame(); frame != null; frame = frame.getCaller()) { - visitFrame(frame.getFrameInfo()); - nextFrameId++; - } - } else { - /* - * All references that are on the stack need to be marked as GC roots. Our - * information is not necessarily precise enough to identify the exact Java-level - * stack frame to which a reference belongs. Therefore, we just dump the data in a - * way that it gets associated with the deepest inlined Java-level stack frame of - * each compilation unit. - */ - markStackValuesAsGCRoots(sp, ip, codeInfo); + public abstract void writeHeapTo(AllocationFreeOutputStream outputStream, boolean gcBefore) throws IOException; - frameInfoCursor.initialize(codeInfo, ip); - while (frameInfoCursor.advance()) { - FrameInfoQueryResult frame = frameInfoCursor.get(); - visitFrame(frame); - nextFrameId++; - } - } - return true; - } - - private void markAsGCRoot(DeoptimizedFrame frame) { - if (markGCRoots) { - markAsJniGlobalGCRoot(frame); - } - } - - private void markStackValuesAsGCRoots(Pointer sp, CodePointer ip, CodeInfo codeInfo) { - if (markGCRoots) { - SimpleCodeInfoQueryResult queryResult = StackValue.get(SimpleCodeInfoQueryResult.class); - CodeInfoAccess.lookupCodeInfo(codeInfo, CodeInfoAccess.relativeIP(codeInfo, ip), queryResult); - - NonmovableArray referenceMapEncoding = CodeInfoAccess.getStackReferenceMapEncoding(codeInfo); - long referenceMapIndex = queryResult.getReferenceMapIndex(); - if (referenceMapIndex == ReferenceMapIndex.NO_REFERENCE_MAP) { - throw CodeInfoTable.reportNoReferenceMap(sp, ip, codeInfo); - } - CodeReferenceMapDecoder.walkOffsetsFromPointer(sp, referenceMapEncoding, referenceMapIndex, this, null); - } - } - - @Override - @RestrictHeapAccess(access = NO_ALLOCATION, reason = "Heap dumping must not allocate.") - public boolean visitObjectReference(Pointer objRef, boolean compressed, Object holderObject) { - assert markGCRoots; - - Object obj = ReferenceAccess.singleton().readObjectAt(objRef, compressed); - if (obj != null) { - int recordSize = 1 + wordSize() + 4 + 4; - startSubRecord(HProfSubRecord.GC_ROOT_JAVA_FRAME, recordSize); - writeObjectId(obj); - writeInt(threadSerialNum); - /* Position of the stack frame in the stack trace. */ - writeInt(getWrittenFrames()); - } - return true; - } - - private void visitFrame(FrameInfoQueryResult frame) { - if (!markGCRoots) { - /* - * Write all UTF-8 symbols that are needed when writing the frame. Ideally, we would - * de-duplicate the symbols, but doing so is not crucial. We also don't support the - * method signature at the moment. - */ - String methodName = frame.getSourceMethodName(); - String methodSignature = ""; - String sourceFileName = getSourceFileName(frame); - writeSymbol(methodName); - writeSymbol(methodSignature); - writeSymbol(sourceFileName); - - /* Write the FRAME record. */ - ClassInfo classInfo = HeapDumpMetadata.getClassInfo(frame.getSourceClass()); - int lineNumber = getLineNumber(frame); - writeFrame(classInfo.getSerialNum(), lineNumber, methodName, methodSignature, sourceFileName); - } - } - - private void writeFrame(int classSerialNum, int lineNumber, String methodName, String methodSignature, String sourceFileName) { - assert !markGCRoots; - - startTopLevelRecord(HProfTopLevelRecord.FRAME); - writeFrameId(nextFrameId); - writeObjectId(methodName); - writeObjectId(methodSignature); - writeObjectId(sourceFileName); - writeInt(classSerialNum); - writeInt(lineNumber); - endTopLevelRecord(); - } - - private String getSourceFileName(FrameInfoQueryResult frame) { - String sourceFileName = frame.getSourceFileName(); - if (sourceFileName == null || sourceFileName.isEmpty()) { - sourceFileName = "Unknown Source"; - } - return sourceFileName; - } - - private int getLineNumber(FrameInfoQueryResult frame) { - if (frame.isNativeMethod()) { - return LINE_NUM_NATIVE_METHOD; - } - return frame.getSourceLineNumber(); - } + public static HeapDumpWriter singleton() { + return ImageSingletons.lookup(HeapDumpWriter.class); } - private class DumpObjectsVisitor implements ObjectVisitor { - private GrowableWordArray largeObjects; - - @Platforms(Platform.HOSTED_ONLY.class) - DumpObjectsVisitor() { - } - - @SuppressWarnings("hiding") - public void initialize(GrowableWordArray largeObjects) { - this.largeObjects = largeObjects; - } - - @Override - @RestrictHeapAccess(access = NO_ALLOCATION, reason = "Heap dumping must not allocate.") - public boolean visitObject(Object obj) { - if (isLarge(obj)) { - boolean added = GrowableWordArrayAccess.add(largeObjects, Word.objectToUntrackedPointer(obj)); - if (!added) { - Log.log().string("Failed to add an element to the large object list. Heap dump will be incomplete.").newline(); - } - } else { - writeObject(obj); - } - return true; - } - - private boolean isLarge(Object obj) { - return getObjectSize(obj).aboveThan(LARGE_OBJECT_THRESHOLD); - } - - private UnsignedWord getObjectSize(Object obj) { - int layoutEncoding = KnownIntrinsics.readHub(obj).getLayoutEncoding(); - if (LayoutEncoding.isArray(layoutEncoding)) { - int elementSize; - if (LayoutEncoding.isPrimitiveArray(layoutEncoding)) { - elementSize = LayoutEncoding.getArrayIndexScale(layoutEncoding); - } else { - elementSize = wordSize(); - } - int length = ArrayLengthNode.arrayLength(obj); - return WordFactory.unsigned(length).multiply(elementSize); - } else { - ClassInfo classInfo = HeapDumpMetadata.getClassInfo(obj.getClass()); - return WordFactory.unsigned(classInfo.getInstanceFieldsDumpSize()); - } - } - } - - private class CodeMetadataVisitor implements RuntimeCodeCache.CodeInfoVisitor, ObjectReferenceVisitor { - @Platforms(Platform.HOSTED_ONLY.class) - CodeMetadataVisitor() { - } - - @Override - public boolean visitCode(CodeInfo info) { - RuntimeCodeInfoAccess.walkObjectFields(info, this); - return true; - } - - @Override - @RestrictHeapAccess(access = NO_ALLOCATION, reason = "Heap dumping must not allocate.") - public boolean visitObjectReference(Pointer objRef, boolean compressed, Object holderObject) { - Object obj = ReferenceAccess.singleton().readObjectAt(objRef, compressed); - if (obj != null) { - markAsJniGlobalGCRoot(obj); - } - return true; - } - } - - private class ThreadLocalsVisitor implements ObjectReferenceVisitor { - private int threadSerialNum; - - @Platforms(Platform.HOSTED_ONLY.class) - ThreadLocalsVisitor() { - } - - @SuppressWarnings("hiding") - public void initialize(int threadSerialNum) { - this.threadSerialNum = threadSerialNum; - } - - @Override - @RestrictHeapAccess(access = NO_ALLOCATION, reason = "Heap dumping must not allocate.") - public boolean visitObjectReference(Pointer objRef, boolean compressed, Object holderObject) { - Object obj = ReferenceAccess.singleton().readObjectAt(objRef, compressed); - if (obj != null) { - markThreadLocalAsGCRoot(obj, threadSerialNum); - } - return true; - } - - private void markThreadLocalAsGCRoot(Object obj, int threadSerialNum) { - int recordSize = 1 + wordSize() + 4 + 4; - startSubRecord(HProfSubRecord.GC_ROOT_JNI_LOCAL, recordSize); - writeObjectId(obj); - writeInt(threadSerialNum); - writeInt(-1); // empty stack - endSubRecord(recordSize); - } - } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/HeapDumpWriterImpl.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/HeapDumpWriterImpl.java new file mode 100644 index 000000000000..0d8cb6f85de6 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/HeapDumpWriterImpl.java @@ -0,0 +1,1761 @@ +/* + * Copyright (c) 2022, 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package com.oracle.svm.core.heapdump; + +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.lang.management.ManagementFactory; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.graalvm.nativeimage.CurrentIsolate; +import org.graalvm.nativeimage.ImageSingletons; +import org.graalvm.nativeimage.IsolateThread; +import org.graalvm.word.Pointer; +import org.graalvm.word.WordBase; + +import com.oracle.svm.core.StaticFieldsSupport; +import com.oracle.svm.core.code.FrameInfoQueryResult; +import com.oracle.svm.core.config.ConfigurationValues; +import com.oracle.svm.core.heap.Heap; +import com.oracle.svm.core.heap.ObjectVisitor; +import com.oracle.svm.core.heap.ReferenceAccess; +import com.oracle.svm.core.heap.VMOperationInfos; +import com.oracle.svm.core.hub.DynamicHub; +import com.oracle.svm.core.stack.JavaStackFrameVisitor; +import com.oracle.svm.core.stack.JavaStackWalker; +import com.oracle.svm.core.thread.JavaVMOperation; +import com.oracle.svm.core.thread.PlatformThreads; +import com.oracle.svm.core.thread.VMOperation; +import com.oracle.svm.core.thread.VMThreads; +import com.oracle.svm.core.util.VMError; + +/* + * Legacy implementation, only used by other legacy code (see GR-44538). + * + * This class writes Java heap in hprof binary format. The class is heavily + * influenced by 'HeapHprofBinWriter' implementation. + */ + +/* hprof binary format originally published at: +* +* +* +* header "JAVA PROFILE 1.0.1" or "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. +* u4 high word +* u4 low word number of milliseconds since 0:00 GMT, 1/1/70 +* [record]* a sequence of records. +* +*/ + +/* +* +* Record format: +* +* u1 a TAG denoting the type of the record +* u4 number of *microseconds* since the time stamp in the +* header. (wraps around in a little more than an hour) +* u4 number of bytes *remaining* in the record. Note that +* this number excludes the tag and the length field itself. +* [u1]* BODY of the record (a sequence of bytes) +*/ + +/* +* The following TAGs are supported: +* +* TAG BODY notes +*---------------------------------------------------------- +* HPROF_UTF8 a UTF8-encoded name +* +* id name ID +* [u1]* UTF8 characters (no trailing zero) +* +* HPROF_LOAD_CLASS a newly loaded class +* +* u4 class serial number (> 0) +* id class object ID +* u4 stack trace serial number +* id class name ID +* +* HPROF_UNLOAD_CLASS an unloading class +* +* u4 class serial_number +* +* HPROF_FRAME a Java stack frame +* +* id stack frame ID +* id method name ID +* id method signature ID +* id source file name ID +* u4 class serial number +* i4 line number. >0: normal +* -1: unknown +* -2: compiled method +* -3: native method +* +* HPROF_TRACE a Java stack trace +* +* u4 stack trace serial number +* u4 thread serial number +* u4 number of frames +* [id]* stack frame IDs +* +* +* HPROF_ALLOC_SITES a set of heap allocation sites, obtained after GC +* +* u2 flags 0x0001: incremental vs. complete +* 0x0002: sorted by allocation vs. live +* 0x0004: whether to force a GC +* u4 cutoff ratio +* u4 total live bytes +* u4 total live instances +* u8 total bytes allocated +* u8 total instances allocated +* u4 number of sites that follow +* [u1 is_array: 0: normal object +* 2: object array +* 4: boolean array +* 5: char array +* 6: float array +* 7: double array +* 8: byte array +* 9: short array +* 10: int array +* 11: long array +* u4 class serial number (may be zero during startup) +* u4 stack trace serial number +* u4 number of bytes alive +* u4 number of instances alive +* u4 number of bytes allocated +* u4]* number of instance allocated +* +* HPROF_START_THREAD a newly started thread. +* +* u4 thread serial number (> 0) +* id thread object ID +* u4 stack trace serial number +* id thread name ID +* id thread group name ID +* id thread group parent name ID +* +* HPROF_END_THREAD a terminating thread. +* +* u4 thread serial number +* +* HPROF_HEAP_SUMMARY heap summary +* +* u4 total live bytes +* u4 total live instances +* u8 total bytes allocated +* u8 total instances allocated +* +* HPROF_HEAP_DUMP denote a heap dump +* +* [heap dump sub-records]* +* +* There are four kinds of heap dump sub-records: +* +* u1 sub-record type +* +* HPROF_GC_ROOT_UNKNOWN unknown root +* +* id object ID +* +* HPROF_GC_ROOT_THREAD_OBJ thread object +* +* id thread object ID (may be 0 for a +* thread newly attached through JNI) +* u4 thread sequence number +* u4 stack trace sequence number +* +* HPROF_GC_ROOT_JNI_GLOBAL JNI global ref root +* +* id object ID +* id JNI global ref ID +* +* HPROF_GC_ROOT_JNI_LOCAL JNI local ref +* +* id object ID +* u4 thread serial number +* u4 frame # in stack trace (-1 for empty) +* +* HPROF_GC_ROOT_JAVA_FRAME Java stack frame +* +* id object ID +* u4 thread serial number +* u4 frame # in stack trace (-1 for empty) +* +* HPROF_GC_ROOT_NATIVE_STACK Native stack +* +* id object ID +* u4 thread serial number +* +* HPROF_GC_ROOT_STICKY_CLASS System class +* +* id object ID +* +* HPROF_GC_ROOT_THREAD_BLOCK Reference from thread block +* +* id object ID +* u4 thread serial number +* +* HPROF_GC_ROOT_MONITOR_USED Busy monitor +* +* id object ID +* +* HPROF_GC_CLASS_DUMP dump of a class object +* +* id class object ID +* u4 stack trace serial number +* id super class object ID +* id class loader object ID +* id signers object ID +* id protection domain object ID +* id reserved +* id reserved +* +* u4 instance size (in bytes) +* +* u2 size of constant pool +* [u2, constant pool index, +* ty, type +* 2: object +* 4: boolean +* 5: char +* 6: float +* 7: double +* 8: byte +* 9: short +* 10: int +* 11: long +* vl]* and value +* +* u2 number of static fields +* [id, static field name, +* ty, type, +* vl]* and value +* +* u2 number of inst. fields (not inc. super) +* [id, instance field name, +* ty]* type +* +* HPROF_GC_INSTANCE_DUMP dump of a normal object +* +* id object ID +* u4 stack trace serial number +* id class object ID +* u4 number of bytes that follow +* [vl]* instance field values (class, followed +* by super, super's super ...) +* +* HPROF_GC_OBJ_ARRAY_DUMP dump of an object array +* +* id array object ID +* u4 stack trace serial number +* u4 number of elements +* id array class ID +* [id]* elements +* +* HPROF_GC_PRIM_ARRAY_DUMP dump of a primitive array +* +* id array object ID +* u4 stack trace serial number +* u4 number of elements +* u1 element type +* 4: boolean array +* 5: char array +* 6: float array +* 7: double array +* 8: byte array +* 9: short array +* 10: int array +* 11: long array +* [u1]* elements +* +* HPROF_CPU_SAMPLES a set of sample traces of running threads +* +* u4 total number of samples +* u4 # of traces +* [u4 # of samples +* u4]* stack trace serial number +* +* HPROF_CONTROL_SETTINGS the settings of on/off switches +* +* u4 0x00000001: alloc traces on/off +* 0x00000002: cpu sampling on/off +* 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: +* +* HPROF_HEAP_DUMP_SEGMENT denote a heap dump segment +* +* [heap dump sub-records]* +* The same sub-record types allowed by HPROF_HEAP_DUMP +* +* HPROF_HEAP_DUMP_END denotes the end of a heap dump +* +*/ +public class HeapDumpWriterImpl extends HeapDumpWriter { + + /** + * 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 * 1024 * 1024 * 1024; + + /** + * The approximate size of a heap segment. Used to calculate when to create a new segment. + */ + private static final long HPROF_SEGMENTED_HEAP_DUMP_SEGMENT_SIZE = 1L * 1024 * 1024 * 1024; + + /** + * The approximate size of a heap segment for no seek case. Used to calculate when to create a + * new segment. + */ + private static final int HPROF_NOSEEK_HEAP_DUMP_SEGMENT_SIZE = 1 * 1024 * 1024; + + /** 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. */ + private static final int HPROF_UTF8 = 0x01; + private static final int HPROF_LOAD_CLASS = 0x02; + /* private static final int HPROF_UNLOAD_CLASS = 0x03; */ + private static final int HPROF_FRAME = 0x04; + private static final int HPROF_TRACE = 0x05; + /* private static final int HPROF_ALLOC_SITES = 0x06; */ + /* private static final int HPROF_HEAP_SUMMARY = 0x07; */ + /* private static final int HPROF_START_THREAD = 0x0A; */ + /* private static final int HPROF_END_THREAD = 0x0B; */ + private static final int HPROF_HEAP_DUMP = 0x0C; + /* private static final int HPROF_CPU_SAMPLES = 0x0D; */ + /* private static final int HPROF_CONTROL_SETTINGS = 0x0E; */ + + /* 1.0.2 record types. */ + private static final int HPROF_HEAP_DUMP_SEGMENT = 0x1C; + private static final int HPROF_HEAP_DUMP_END = 0x2C; + + /* Heap dump constants */ + /* Constants in enum HprofGcTag. */ + private static final int HPROF_GC_ROOT_UNKNOWN = 0xFF; + private static final int HPROF_GC_ROOT_JNI_GLOBAL = 0x01; + /* private static final int HPROF_GC_ROOT_JNI_LOCAL = 0x02; */ + /* private static final int HPROF_GC_ROOT_JAVA_FRAME = 0x03; */ + /* private static final int HPROF_GC_ROOT_NATIVE_STACK = 0x04; */ + private static final int HPROF_GC_ROOT_STICKY_CLASS = 0x05; + /* private static final int HPROF_GC_ROOT_THREAD_BLOCK = 0x06; */ + /* private static final int HPROF_GC_ROOT_MONITOR_USED = 0x07; */ + private static final int HPROF_GC_ROOT_THREAD_OBJ = 0x08; + private static final int HPROF_GC_CLASS_DUMP = 0x20; + private static final int HPROF_GC_INSTANCE_DUMP = 0x21; + private static final int HPROF_GC_OBJ_ARRAY_DUMP = 0x22; + private static final int HPROF_GC_PRIM_ARRAY_DUMP = 0x23; + + /* Constants in enum HprofType. */ + private static final int HPROF_NORMAL_OBJECT = 2; + private static final int HPROF_BOOLEAN = 4; + private static final int HPROF_CHAR = 5; + private static final int HPROF_FLOAT = 6; + private static final int HPROF_DOUBLE = 7; + private static final int HPROF_BYTE = 8; + private static final int HPROF_SHORT = 9; + private static final int HPROF_INT = 10; + private static final int HPROF_LONG = 11; + + /* Java type codes. */ + private static final char JVM_SIGNATURE_BOOLEAN = 'Z'; + private static final char JVM_SIGNATURE_CHAR = 'C'; + private static final char JVM_SIGNATURE_BYTE = 'B'; + private static final char JVM_SIGNATURE_SHORT = 'S'; + private static final char JVM_SIGNATURE_INT = 'I'; + private static final char JVM_SIGNATURE_LONG = 'J'; + private static final char JVM_SIGNATURE_FLOAT = 'F'; + private static final char JVM_SIGNATURE_DOUBLE = 'D'; + private static final char JVM_SIGNATURE_ARRAY = '['; + private static final char JVM_SIGNATURE_CLASS = 'L'; + + /* + * We don't have allocation site info. We write a dummy stack trace with this id. + */ + private static final int DUMMY_STACK_TRACE_ID = 1; + /* private static final int EMPTY_FRAME_DEPTH = -1; */ + + private static final Field[] ZERO_FIELD_ARR = new Field[0]; + + /** Pre-allocated exceptions, for throwing from code that must not allocate. */ + private static final RuntimeException heapSegmentSizeOverflowException = new RuntimeException("Heap segment size overflow."); + + private AllocationFreeDataOutputStream out; + private HeapDumpUtils heapDumpUtils; + private Map> fieldsMap; + private ClassToClassDataMap classDataCache; + + /* Added for hprof file format 1.0.2 support. */ + private boolean useSegmentedHeapDump; + private long currentSegmentStart; + private long segmentSize; + + @Override + public void writeHeapTo(AllocationFreeOutputStream dataOutputStream, boolean gcBefore) throws IOException { + initialize(true, HPROF_NOSEEK_HEAP_DUMP_SEGMENT_SIZE); + + WriterOperation writerOperation = new WriterOperation(dataOutputStream, gcBefore, HPROF_NOSEEK_HEAP_DUMP_SEGMENT_SIZE); + writerOperation.enqueue(); + IOException operationException = writerOperation.getException(); + if (operationException != null) { + throw operationException; + } + + /* + * Close the data stream. Needs to be done outside of the VMOperation because it uses + * synchronization. + */ + dataOutputStream.close(); + } + + @Override + public void writeHeapTo(FileOutputStream fileOutputStream, boolean gcBefore) throws IOException { + initialize(ManagementFactory.getMemoryMXBean().getHeapMemoryUsage().getUsed() > HPROF_SEGMENTED_HEAP_DUMP_THRESHOLD, HPROF_SEGMENTED_HEAP_DUMP_SEGMENT_SIZE); + + WriterOperation writerOperation = new WriterOperation(fileOutputStream, gcBefore); + writerOperation.enqueue(); + IOException operationException = writerOperation.getException(); + if (operationException != null) { + throw operationException; + } + + /* + * Close the file stream. Needs to be done outside of the VMOperation because it uses + * synchronization. + */ + fileOutputStream.close(); + } + + @SuppressWarnings("hiding") + private void initialize(boolean useSegmentedHeapDump, long segmentSize) { + this.currentSegmentStart = 0L; + this.useSegmentedHeapDump = useSegmentedHeapDump; + this.segmentSize = segmentSize; + } + + /* This method runs as part of a VMOperation. */ + private void writeTo(AllocationFreeDataOutputStream outputStream, boolean gcBefore) throws IOException { + /* If requested, clean up the heap. */ + if (gcBefore) { + System.gc(); + } + + out = outputStream; + heapDumpUtils = HeapDumpUtils.getHeapDumpUtils(); + + /* hprof bin format header. */ + writeFileHeader(); + + /* Dummy stack trace. */ + writeDummyTrace(); + + List> classList = Heap.getHeap().getLoadedClasses(); + + /* hprof UTF-8 symbols section. */ + writeClassNames(classList); + + byte[] fieldsMapData = heapDumpUtils.getFieldsMap(); + if (fieldsMapData.length == 0) { + throw new IOException("Empty fieldsMap"); + } + fieldsMap = createFieldsMap(fieldsMapData); + + /* HPROF_LOAD_CLASS records for all classes. */ + writeClasses(classList); + + /* write HPROF_FRAME and HPROF_TRACE records */ + dumpStackTraces(); + + /* Write CLASS_DUMP records. */ + writeClassDumpRecords(classList); + + /* Write HEAP_DUMP record. */ + writeInstanceDumpRecords(classList); + + /* get current position to calculate length. */ + long dumpEnd = out.position(); + + /* Calculate length of heap data. */ + long dumpLenLong = (dumpEnd - currentSegmentStart - 4L); + /* Fill in final length. */ + fillInHeapRecordLength(dumpLenLong); + + if (useSegmentedHeapDump) { + /* Write heap segment-end record. */ + out.writeByte((byte) HPROF_HEAP_DUMP_END); + out.writeInt(0); + out.writeInt(0); + } + + /* Flush buffer stream and throw fields away. */ + out.flush(); + out = null; + heapDumpUtils = null; + fieldsMap = null; + classDataCache = null; + } + + private void writeHeapRecordPrologue() throws IOException { + if (currentSegmentStart == 0) { + out.flush(); + /* Write heap data header, depending on heap size use segmented heap format. */ + out.writeByte((byte) (useSegmentedHeapDump ? HPROF_HEAP_DUMP_SEGMENT + : HPROF_HEAP_DUMP)); + out.writeInt(0); + + /* + * Remember position of dump length, we will fixup length later: hprof format requires + * length. + */ + currentSegmentStart = out.position(); + + /* Write dummy length of 0 and we'll fix it later. */ + out.writeInt(0); + } + } + + private void writeHeapRecordEpilogue() throws IOException { + writeHeapRecordEpilogue(0); + } + + private void writeHeapRecordEpilogue(long dumpLenSize) throws IOException { + if (useSegmentedHeapDump) { + /* get current position (plus dumpLenLong) to calculate length. */ + long dumpEnd = out.position() + dumpLenSize; + + /* Calculate length of heap data. */ + long dumpLenLong = dumpEnd - currentSegmentStart - 4L; + if (dumpLenLong >= segmentSize) { + fillInHeapRecordLength(dumpLenLong); + out.flush(); + currentSegmentStart = 0; + } + } + } + + private void fillInHeapRecordLength(long dumpLenLong) throws IOException { + /* Check length boundary, overflow of 4GB could happen but is _very_ unlikely. */ + if (dumpLenLong >= (4L * 1024 * 1024 * 1024)) { + throw heapSegmentSizeOverflowException; + } + + /* Save the current position. */ + long currentPosition = out.position(); + + /* Seek the position to write length. */ + out.position(currentSegmentStart); + + int dumpLen = (int) dumpLenLong; + + /* Write length as integer. */ + out.writeInt(dumpLen); + + /* Reset to previous current position. */ + out.position(currentPosition); + } + + private void writeClassDumpRecords(List> classList) throws IOException { + for (Class cls : classList) { + writeHeapRecordPrologue(); + writeClassDumpRecord(cls); + writeHeapRecordEpilogue(); + } + } + + private void writeInstanceDumpRecords(List> classList) throws IOException { + final StacksSlotsVisitorImpl stackVisitor = new StacksSlotsVisitorImpl(); + final CollectedHeapVisitorImpl collectedVisitor = new CollectedHeapVisitorImpl(); + final ImageHeapVisitorImpl imageVisitor = new ImageHeapVisitorImpl(); + IOException visitorException; + + heapDumpUtils.walkStacks(stackVisitor); + visitorException = stackVisitor.getException(); + if (visitorException != null) { + throw visitorException; + } + + heapDumpUtils.walkHeapObjects(imageVisitor, collectedVisitor); + visitorException = collectedVisitor.getException(); + if (visitorException != null) { + throw visitorException; + } + visitorException = imageVisitor.getException(); + if (visitorException != null) { + throw visitorException; + } + /* Write root sticky class. */ + writeStickyClasses(classList); + + /* write JavaThreads */ + writeJavaThreads(); + } + + private void writeStickyClasses(List> classList) throws IOException { + for (Class cls : classList) { + writeHeapRecordPrologue(); + out.writeByte((byte) HPROF_GC_ROOT_STICKY_CLASS); + writeObjectID(cls); + writeHeapRecordEpilogue(); + } + } + + private void writeImageGCRoot(Object obj) throws IOException { + if (!(obj instanceof Class)) { + writeHeapRecordPrologue(); + out.writeByte((byte) HPROF_GC_ROOT_JNI_GLOBAL); + writeObjectID(obj); + writeObjectID(null); + writeHeapRecordEpilogue(); + } + } + + private void writeUnknownGCRoot(Object obj) throws IOException { + if (obj != null) { + writeHeapRecordPrologue(); + out.writeByte((byte) HPROF_GC_ROOT_UNKNOWN); + writeObjectID(obj); + writeHeapRecordEpilogue(); + } + } + + private void writeJavaThreads() throws IOException { + int threadSerialNum = 1; // Note that the thread serial number range is 1-to-N + + for (IsolateThread vmThread = VMThreads.firstThread(); vmThread.isNonNull(); vmThread = VMThreads.nextThread(vmThread)) { + if (vmThread == CurrentIsolate.getCurrentThread()) { + /* Skip itself */ + continue; + } + + Thread jt = PlatformThreads.fromVMThread(vmThread); + writeJavaThread(jt, threadSerialNum++); + } + } + + private void writeJavaThread(Thread jt, int threadSerialNum) throws IOException { + writeHeapRecordPrologue(); + out.writeByte((byte) HPROF_GC_ROOT_THREAD_OBJ); + writeObjectID(jt); + out.writeInt(threadSerialNum); // thread serial number + out.writeInt(threadSerialNum + DUMMY_STACK_TRACE_ID); // stack trace serial number + writeHeapRecordEpilogue(); + } + + private void writeClass(Class clazz) throws IOException { + /* + * All ordinary Class objects are covered by writeClassDumpRecords and they are in + * classDataCache. + */ + if (classDataCache.get(clazz) == null) { + /* Unknown class. */ + writeInstance(clazz); + } + } + + private void writeClassDumpRecord(Class cls) throws IOException { + out.writeByte((byte) HPROF_GC_CLASS_DUMP); + writeObjectID(cls); + out.writeInt(DUMMY_STACK_TRACE_ID); + writeObjectID(cls.getSuperclass()); + + if (!isArray(cls)) { + writeObjectID(cls.getClassLoader()); + writeObjectID(null); /* Signers. */ + writeObjectID(null); /* Protection domain. */ + writeObjectID(null); /* Reserved field 1. */ + writeObjectID(null); /* Reserved field 2. */ + out.writeInt(heapDumpUtils.instanceSizeOf(cls)); + + /* Ignore constant pool output number of cp entries as zero. */ + out.writeShort((short) 0); + + List declaredFields = getImmediateFields(cls); + int staticFields = 0; + int instanceFields = 0; + for (int i = 0; i < declaredFields.size(); i++) { + Field field = declaredFields.get(i); + if (field.isStatic()) { + staticFields++; + } else { + instanceFields++; + } + } + + /* Dump static field descriptors. */ + writeFieldDescriptors(true, staticFields, declaredFields); + + /* Dump instance field descriptors. */ + writeFieldDescriptors(false, instanceFields, declaredFields); + } else { + /* Array. */ + Class baseClass = getBaseClass(cls); + writeObjectID(baseClass.getClassLoader()); + writeObjectID(null); + writeObjectID(null); + /* Two reserved id fields. */ + writeObjectID(null); + writeObjectID(null); + /* Write zero instance size: instance size is variable for arrays. */ + out.writeInt(0); + /* No constant pool for array klasses. */ + out.writeShort((short) 0); + /* No static fields for array klasses. */ + out.writeShort((short) 0); + /* No instance fields for array klasses. */ + out.writeShort((short) 0); + } + } + + private void dumpStackTraces() throws IOException { + // write a HPROF_TRACE record without any frames to be referenced as object alloc sites + writeHeader(HPROF_TRACE, 3 * 4); + out.writeInt(DUMMY_STACK_TRACE_ID); + out.writeInt(0); // thread number + out.writeInt(0); // frame count + + int frameSerialNum = 0; + int numThreads = 0; + Set names = new HashSet<>(); + for (IsolateThread vmThread = VMThreads.firstThread(); vmThread.isNonNull(); vmThread = VMThreads.nextThread(vmThread)) { + if (vmThread == CurrentIsolate.getCurrentThread()) { + /* Skip itself */ + continue; + } + + final List stack = new ArrayList<>(); + + // dump thread stack trace + JavaStackFrameVisitor visitor = new JavaStackFrameVisitor() { + @Override + public boolean visitFrame(FrameInfoQueryResult frameInfo) { + if (frameInfo.getSourceClass() != null) { + stack.add(frameInfo); + } + return true; + } + }; + JavaStackWalker.walkThread(vmThread, visitor); + numThreads++; + + // write HPROF_FRAME records for this thread's stack trace + int depth = stack.size(); + int threadFrameStart = frameSerialNum; + for (int j = 0; j < depth; j++) { + FrameInfoQueryResult frame = stack.get(j); + ClassData cd = classDataCache.get(frame.getSourceClass()); + int classSerialNum = cd.serialNum; + + // the class serial number starts from 1 + assert classSerialNum > 0 : "class not found"; + dumpStackFrame(++frameSerialNum, classSerialNum, frame, names); + } + + // write HPROF_TRACE record for one thread + writeHeader(HPROF_TRACE, 3 * 4 + depth * getObjIDSize()); + int stackSerialNum = numThreads + DUMMY_STACK_TRACE_ID; + out.writeInt(stackSerialNum); // stack trace serial number + out.writeInt(numThreads); // thread serial number + out.writeInt(depth); // frame count + for (int j = 1; j <= depth; j++) { + writeObjectAddress(threadFrameStart + j); + } + } + names = null; + } + + private void dumpStackFrame(int frameSN, int classSN, FrameInfoQueryResult frame, Set names) throws IOException { + int lineNumber; + if (frame.isNativeMethod()) { + lineNumber = -3; // native frame + } else { + lineNumber = frame.getSourceLineNumber(); + } + // First dump UTF8 if needed + String method = frame.getSourceMethodName(); + String source = frame.getSourceFileName(); + if (method == null || method.isEmpty()) { + method = ""; + } + if (source == null || source.isEmpty()) { + source = "Unknown Source"; + } + writeName(method, names); // method's name + writeName("", names); // method's signature + writeName(source, names); // source file name + // Then write FRAME descriptor + writeHeader(HPROF_FRAME, 4 * getObjIDSize() + 2 * 4); + writeObjectAddress(frameSN); // frame serial number + writeSymbolID(method); // method's name + writeSymbolID(""); // method's signature + writeSymbolID(source); // source file name + out.writeInt(classSN); // class serial number + out.writeInt(lineNumber); // line number + } + + private void writeName(String name, Set names) throws IOException { + if (names.add(name)) { + writeSymbol(name); + } + } + + private void writeHeapInstance(Object obj) throws IOException { + writeHeapRecordPrologue(); + if (obj instanceof Class) { + writeClass((Class) obj); + writeHeapRecordEpilogue(); + } else if (heapDumpUtils.isJavaPrimitiveArray(obj)) { + writePrimitiveArray(obj); + } else if (isArray(obj.getClass())) { + writeObjectArray((Object[]) obj); + } else { + writeInstance(obj); + writeHeapRecordEpilogue(); + } + } + + private List getImmediateFields(Class cls) { + String clsName = cls.getName(); + List fields = fieldsMap.get(clsName); + if (fields == null) { + return Collections.emptyList(); + } + return fields; + } + + private void writeObjectArray(Object[] array) throws IOException { + out.writeByte((byte) HPROF_GC_OBJ_ARRAY_DUMP); + writeObjectID(array); + out.writeInt(DUMMY_STACK_TRACE_ID); + out.writeInt(array.length); + writeObjectID(array.getClass()); + writeHeapRecordEpilogue(array.length * getObjIDSize()); + for (Object o : array) { + writeObjectID(o); + } + } + + private void writePrimitiveArray(Object pArray) throws IOException { + out.writeByte((byte) HPROF_GC_PRIM_ARRAY_DUMP); + writeObjectID(pArray); + out.writeInt(DUMMY_STACK_TRACE_ID); + /* These are ordered by expected frequency. */ + if (pArray instanceof char[]) { + writeCharArray((char[]) pArray); + } else if (pArray instanceof byte[]) { + writeByteArray((byte[]) pArray); + } else if (pArray instanceof int[]) { + writeIntArray((int[]) pArray); + } else if (pArray instanceof long[]) { + writeLongArray((long[]) pArray); + } else if (pArray instanceof boolean[]) { + writeBooleanArray((boolean[]) pArray); + } else if (pArray instanceof short[]) { + writeShortArray((short[]) pArray); + } else if (pArray instanceof double[]) { + writeDoubleArray((double[]) pArray); + } else if (pArray instanceof float[]) { + writeFloatArray((float[]) pArray); + } else { + throw VMError.shouldNotReachHere(pArray.getClass().getName()); + } + } + + private void writeBooleanArray(boolean[] array) throws IOException { + out.writeInt(array.length); + out.writeByte((byte) HPROF_BOOLEAN); + writeHeapRecordEpilogue(array.length * 1); + for (boolean b : array) { + out.writeBoolean(b); + } + } + + private void writeByteArray(byte[] array) throws IOException { + out.writeInt(array.length); + out.writeByte((byte) HPROF_BYTE); + writeHeapRecordEpilogue(array.length * 1); + for (byte b : array) { + out.writeByte(b); + } + } + + private void writeShortArray(short[] array) throws IOException { + out.writeInt(array.length); + out.writeByte((byte) HPROF_SHORT); + writeHeapRecordEpilogue(array.length * 2); + for (short s : array) { + out.writeShort(s); + } + } + + private void writeIntArray(int[] array) throws IOException { + out.writeInt(array.length); + out.writeByte((byte) HPROF_INT); + writeHeapRecordEpilogue(array.length * 4); + for (int i : array) { + out.writeInt(i); + } + } + + private void writeLongArray(long[] array) throws IOException { + out.writeInt(array.length); + out.writeByte((byte) HPROF_LONG); + writeHeapRecordEpilogue(array.length * 8); + for (long l : array) { + out.writeLong(l); + } + } + + private void writeCharArray(char[] array) throws IOException { + out.writeInt(array.length); + out.writeByte((byte) HPROF_CHAR); + writeHeapRecordEpilogue(array.length * 2); + for (char c : array) { + out.writeChar(c); + } + } + + private void writeFloatArray(float[] array) throws IOException { + out.writeInt(array.length); + out.writeByte((byte) HPROF_FLOAT); + writeHeapRecordEpilogue(array.length * 4); + for (float f : array) { + out.writeFloat(f); + } + } + + private void writeDoubleArray(double[] array) throws IOException { + out.writeInt(array.length); + out.writeByte((byte) HPROF_DOUBLE); + writeHeapRecordEpilogue(array.length * 8); + for (double d : array) { + out.writeDouble(d); + } + } + + private void writeInstance(Object instance) throws IOException { + out.writeByte((byte) HPROF_GC_INSTANCE_DUMP); + writeObjectID(instance); + out.writeInt(DUMMY_STACK_TRACE_ID); + Class cls = instance.getClass(); + writeObjectID(cls); + + final ClassData cd = classDataCache.get(cls); + out.writeInt(cd.instSize); + final Pointer objRef = heapDumpUtils.objectToPointer(instance); + for (int i = 0; i < cd.fields.length; i++) { + writeField(cd.fields[i], objRef); + } + } + + private void writeFieldDescriptors(boolean isStatic, int size, List fields) throws IOException { + /* cls == null for instance fields. */ + out.writeShort((short) size); + for (int i = 0; i < fields.size(); i++) { + Field field = fields.get(i); + if (isStatic == field.isStatic()) { + writeSymbolIDFromField(field); + char typeCode = field.getStorageSignature(); + int kind = signatureToHprofKind(typeCode); + out.writeByte((byte) kind); + if (field.isStatic()) { + /* Static field. */ + Object staticData; + char javaSignature = field.getStorageSignature(); + if (javaSignature == JVM_SIGNATURE_CLASS || javaSignature == JVM_SIGNATURE_ARRAY) { + staticData = StaticFieldsSupport.getStaticObjectFields(); + } else { + staticData = StaticFieldsSupport.getStaticPrimitiveFields(); + } + writeField(field, heapDumpUtils.objectToPointer(staticData)); + } + } + } + } + + private static int signatureToHprofKind(char ch) { + switch (ch) { + case JVM_SIGNATURE_CLASS: + case JVM_SIGNATURE_ARRAY: + return HPROF_NORMAL_OBJECT; + case JVM_SIGNATURE_BOOLEAN: + return HPROF_BOOLEAN; + case JVM_SIGNATURE_CHAR: + return HPROF_CHAR; + case JVM_SIGNATURE_FLOAT: + return HPROF_FLOAT; + case JVM_SIGNATURE_DOUBLE: + return HPROF_DOUBLE; + case JVM_SIGNATURE_BYTE: + return HPROF_BYTE; + case JVM_SIGNATURE_SHORT: + return HPROF_SHORT; + case JVM_SIGNATURE_INT: + return HPROF_INT; + case JVM_SIGNATURE_LONG: + return HPROF_LONG; + default: + throw new RuntimeException("should not reach here"); + } + } + + private void writeField(Field field, Pointer p) throws IOException { + char storageSignature = field.getStorageSignature(); + int location = field.getLocation(); + + switch (storageSignature) { + case JVM_SIGNATURE_BOOLEAN: + out.writeByte(p.readByte(location)); + break; + case JVM_SIGNATURE_CHAR: + out.writeChar(p.readChar(location)); + break; + case JVM_SIGNATURE_BYTE: + out.writeByte(p.readByte(location)); + break; + case JVM_SIGNATURE_SHORT: + out.writeShort(p.readShort(location)); + break; + case JVM_SIGNATURE_INT: + out.writeInt(p.readInt(location)); + break; + case JVM_SIGNATURE_LONG: + out.writeLong(p.readLong(location)); + break; + case JVM_SIGNATURE_FLOAT: + out.writeFloat(p.readFloat(location)); + break; + case JVM_SIGNATURE_DOUBLE: + out.writeDouble(p.readDouble(location)); + break; + case JVM_SIGNATURE_CLASS: + case JVM_SIGNATURE_ARRAY: + writeObjectID(ReferenceAccess.singleton().readObjectAt(p.add(location), true)); + break; + default: + throw VMError.shouldNotReachHere("HeapDumpWriter.writeField: storageSignature"); + } + } + + private void writeHeader(int tag, int len) throws IOException { + out.writeByte((byte) tag); + out.writeInt(0); /* current ticks. */ + out.writeInt(len); + } + + private void writeDummyTrace() throws IOException { + writeHeader(HPROF_TRACE, 3 * 4); + out.writeInt(DUMMY_STACK_TRACE_ID); + out.writeInt(0); + out.writeInt(0); + } + + private void writeSymbolFromField(byte[] data, Field field) throws IOException { + writeHeader(HPROF_UTF8, field.getNameLength() + getObjIDSize()); + writeSymbolIDFromField(field); + out.write(data, field.getNameStartOffset(), field.getNameLength()); + } + + private void writeSymbol(String clsName) throws IOException { + byte[] buf = clsName.getBytes(StandardCharsets.UTF_8); + writeHeader(HPROF_UTF8, buf.length + getObjIDSize()); + writeSymbolID(clsName); + out.write(buf); + } + + private void writeClassNames(List> classList) throws IOException { + /* hprof UTF-8 symbols section. */ + for (Class cls : classList) { + writeSymbol(cls.getName()); + } + } + + private void writeClasses(List> classList) throws IOException { + int serialNum = 1; + /* + * Build a temporary map from Class to ClassData while I can allocate, but turn it into a + * map between arrays for later use. + */ + Map, ClassData> classDataMap = new HashMap<>(); + List fields = new ArrayList<>(); + for (Class cls : classList) { + writeHeader(HPROF_LOAD_CLASS, 2 * (getObjIDSize() + 4)); + out.writeInt(serialNum); + writeObjectID(cls); + out.writeInt(DUMMY_STACK_TRACE_ID); + writeSymbolID(cls.getName()); + assert fields.isEmpty(); + addInstanceFieldsTo(fields, cls); + int instSize = getSizeForFields(fields); + classDataMap.put(cls, new ClassData(serialNum, instSize, fields.toArray(ZERO_FIELD_ARR))); + fields.clear(); + serialNum++; + } + classDataCache = new ClassToClassDataMap(classDataMap); + } + + /** 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.writeByte((byte) '\0'); + + /* Write identifier size. we use pointers as identifiers. */ + out.writeInt(getObjIDSize()); + + /* Time stamp: file creation time. */ + out.writeLong(System.currentTimeMillis()); + } + + /** Writes unique ID for an object. */ + private void writeObjectID(Object obj) throws IOException { + if (obj != null) { + WordBase ptr = ReferenceAccess.singleton().getCompressedRepresentation(obj); + writeObjectAddress(ptr.rawValue()); + } else { + writeObjectAddress(0L); + } + } + + private void writeSymbolID(String clsName) throws IOException { + writeObjectID(clsName); + } + + private void writeSymbolIDFromField(Field field) throws IOException { + writeObjectID(field); + } + + private void writeObjectAddress(long address) throws IOException { + if (getObjIDSize() == 4) { + out.writeInt((int) address); + } else { + out.writeLong(address); + } + } + + /** Get all declared as well as inherited (directly/indirectly) fields. */ + private void addInstanceFieldsTo(List res, final Class cls) { + Class clazz = cls; + while (clazz != null) { + List curFields = getImmediateFields(clazz); + for (int i = 0; i < curFields.size(); i++) { + Field f = curFields.get(i); + + if (!f.isStatic()) { + res.add(f); + } + } + clazz = clazz.getSuperclass(); + } + } + + /** + * Get size in bytes (in stream) required for given fields. Note that this is not the same as + * object size in heap. The size in heap will include size of padding/alignment bytes as well. + */ + private static int getSizeForFields(List fields) { + int size = 0; + for (int i = 0; i < fields.size(); i++) { + Field field = fields.get(i); + char typeCode = field.getStorageSignature(); + switch (typeCode) { + case JVM_SIGNATURE_BOOLEAN: + case JVM_SIGNATURE_BYTE: + size++; + break; + case JVM_SIGNATURE_CHAR: + case JVM_SIGNATURE_SHORT: + size += 2; + break; + case JVM_SIGNATURE_INT: + case JVM_SIGNATURE_FLOAT: + size += 4; + break; + case JVM_SIGNATURE_CLASS: + case JVM_SIGNATURE_ARRAY: + size += getObjIDSize(); + break; + case JVM_SIGNATURE_LONG: + case JVM_SIGNATURE_DOUBLE: + size += 8; + break; + default: + throw new RuntimeException("should not reach here"); + } + } + return size; + } + + private static int getObjIDSize() { + return ConfigurationValues.getObjectLayout().getReferenceSize(); + } + + private static boolean isArray(Class cls) { + return cls.getName().startsWith("["); + } + + private static Class getBaseClass(final Class array) { + Class arr = array; + while (isArray(arr)) { + arr = arr.getComponentType(); + } + return arr; + } + + private Map> createFieldsMap(byte[] data) throws IOException { + int offset = 0; + Map> fldMap = new HashMap<>(); + while (offset < data.length) { + List fields; + String className = readString(data, offset); + offset += className.length() + 1; + + if (data[offset] == 0 && data[offset + 1] == 0) { + /* No fields. */ + fields = Collections.emptyList(); + offset += 2; + } else { + fields = new ArrayList<>(); + offset = readFields(false, data, offset, fields); + offset++; + offset = readFields(true, data, offset, fields); + offset++; + } + fldMap.put(className, fields); + } + return fldMap; + } + + private int readFields(boolean isStatic, byte[] data, int dataOffset, List fields) throws IOException { + int offset = dataOffset; + while (data[offset] != 0) { + /* Read field. */ + int stringStart = offset; + int stringLength = readStringLength(data, offset); + offset += stringLength + 1; + char javaSig = (char) data[offset++]; + char storageSig = (char) data[offset++]; + int location = readInt(data, offset); + offset += 4; + Field fieldDef = new Field(stringStart, stringLength, javaSig, storageSig, isStatic, location); + writeSymbolFromField(data, fieldDef); + fields.add(fieldDef); + } + return offset; + } + + private static int readInt(final byte[] data, final int st) { + int start = st; + int ch1 = data[start++] & 0xFF; + int ch2 = data[start++] & 0xFF; + int ch3 = data[start++] & 0xFF; + int ch4 = data[start++] & 0xFF; + return ((ch1 << 24) + (ch2 << 16) + (ch3 << 8) + (ch4 << 0)); + } + + private static String readString(byte[] data, int start) { + int len = readStringLength(data, start); + + return new String(data, start, len, StandardCharsets.UTF_8); + } + + /** + * Returns size of the null-terminated string. + * + * @param data byte[] array that is the source of string. + * @param start the initial offset to data array. + * @return the number of characters (bytes) in a null-terminated character sequence, without + * including the null-terminating character. + */ + private static int readStringLength(byte[] data, int start) { + int offset = start; + + while (data[offset] != 0) { + offset++; + } + return offset - start; + } + + private static class ClassData { + + int serialNum; + int instSize; + Field[] fields; + + ClassData(int serialNum, int instSize, Field[] fields) { + this.serialNum = serialNum; + this.instSize = instSize; + this.fields = fields; + } + } + + private static final class Field { + + private final int nameStart; + private final int nameLength; + private final char javaSig; + private final char storageSig; + private final boolean isStatic; + private final int location; + + private Field(final int ss, final int sl, final char jsig, final char ssig, + final boolean s, int loc) { + nameStart = ss; + nameLength = sl; + javaSig = jsig == 'A' ? JVM_SIGNATURE_CLASS : jsig; + storageSig = ssig == 'A' ? JVM_SIGNATURE_CLASS : ssig; + isStatic = s; + location = loc; + } + + private boolean isStatic() { + return isStatic; + } + + @SuppressWarnings({"unused"}) + private char getJavaSignature() { + return javaSig; + } + + private char getStorageSignature() { + return storageSig; + } + + private int getNameStartOffset() { + return nameStart; + } + + private int getNameLength() { + return nameLength; + } + + private int getLocation() { + return location; + } + } + + private class CollectedHeapVisitorImpl implements ObjectVisitor { + + private IOException exception; + + @Override + public boolean visitObject(Object obj) { + Object asObject = obj; + try { + writeHeapInstance(asObject); + } catch (IOException ex) { + /* Remember exception and abort VM operation. */ + exception = ex; + return false; + } + return true; + } + + private IOException getException() { + return exception; + } + } + + private class ImageHeapVisitorImpl implements ObjectVisitor { + + private IOException exception; + + @Override + public boolean visitObject(Object obj) { + Object asObject = obj; + try { + writeHeapInstance(asObject); + writeImageGCRoot(asObject); + } catch (IOException ex) { + /* Remember exception and abort VM operation. */ + exception = ex; + return false; + } + return true; + } + + private IOException getException() { + return exception; + } + } + + private class StacksSlotsVisitorImpl extends HeapDumpUtils.StacksSlotsVisitor { + + private IOException exception; + + @Override + public boolean visitObjectReference(Pointer objRef, boolean compressed, Object holderObject) { + try { + /* Get the referent of the reference. */ + final Object obj = ReferenceAccess.singleton().readObjectAt(objRef, compressed); + writeUnknownGCRoot(obj); + return true; + } catch (IOException ex) { + /* Remember exception. */ + exception = ex; + return false; + } + } + + private IOException getException() { + return exception; + } + } + + /** + * Abstract Allocation-free output stream created from FileOutputStream. This is the base class + * used by HeapDumpWriteImpl class. + */ + public abstract static class AllocationFreeFileOutputStream extends OutputStream { + + // constructor + public abstract AllocationFreeFileOutputStream newStreamFor(FileOutputStream fileOutputStream) throws IOException; + + @Override + public abstract void write(int b) throws IOException; + + @Override + public abstract void write(byte[] b, int offset, int length) throws IOException; + + @Override + public abstract void close() throws IOException; + + @Override + public void flush() throws IOException { + } + + /** Read the current position in a file descriptor. */ + protected abstract long position() throws IOException; + + /** Set the current position in a file descriptor. */ + protected abstract long position(long offset) throws IOException; + } + + /** + * Implementation of allocation-free output stream, which delegates to + * AllocationFreeOutputStream interface. + */ + private final class AllocationFreeFileOutputStreamWrapper extends AllocationFreeFileOutputStream { + + private final AllocationFreeOutputStream out; + private long position; + + private AllocationFreeFileOutputStreamWrapper(AllocationFreeOutputStream outputStream) { + out = outputStream; + position = 0; + } + + @Override + public AllocationFreeFileOutputStream newStreamFor(FileOutputStream fileOutputStream) throws IOException { + throw VMError.shouldNotReachHere(); + } + + @Override + public void write(int b) throws IOException { + out.write(b); + position++; + } + + @Override + public void write(byte[] b, int offset, int length) throws IOException { + out.write(b, offset, length); + position += length; + } + + @Override + public void close() throws IOException { + out.close(); + } + + @Override + public void flush() throws IOException { + out.flush(); + } + + @Override + protected long position() throws IOException { + return position; + } + + @Override + protected long position(long offset) throws IOException { + throw VMError.shouldNotReachHere(); + } + } + + private static final class AllocationFreeDataOutputStream { + + private final AllocationFreeBufferedOutputStream out; + + private AllocationFreeDataOutputStream(AllocationFreeBufferedOutputStream o) { + out = o; + } + + private void writeBoolean(boolean v) throws IOException { + out.write(v ? 1 : 0); + } + + private void writeByte(int v) throws IOException { + out.write(v); + } + + private void writeChar(int v) throws IOException { + out.write((v >>> 8) & 0xFF); + out.write((v >>> 0) & 0xFF); + } + + private void writeShort(int v) throws IOException { + out.write((v >>> 8) & 0xFF); + out.write((v >>> 0) & 0xFF); + } + + private void writeInt(int v) throws IOException { + out.write((v >>> 24) & 0xFF); + out.write((v >>> 16) & 0xFF); + out.write((v >>> 8) & 0xFF); + out.write((v >>> 0) & 0xFF); + } + + private void writeFloat(float v) throws IOException { + writeInt(Float.floatToIntBits(v)); + } + + private void writeLong(long v) throws IOException { + out.write((byte) (v >>> 56)); + out.write((byte) (v >>> 48)); + out.write((byte) (v >>> 40)); + out.write((byte) (v >>> 32)); + out.write((byte) (v >>> 24)); + out.write((byte) (v >>> 16)); + out.write((byte) (v >>> 8)); + out.write((byte) (v >>> 0)); + } + + private void writeDouble(double v) throws IOException { + writeLong(Double.doubleToLongBits(v)); + } + + private void flush() throws IOException { + out.flush(); + } + + private void write(byte[] buf) throws IOException { + out.write(buf, 0, buf.length); + } + + private void write(byte[] buf, int off, int len) throws IOException { + out.write(buf, off, len); + } + + private void writeBytes(String s) throws IOException { + int len = s.length(); + for (int i = 0; i < len; i++) { + out.write((byte) s.charAt(i)); + } + } + + private long position() throws IOException { + return out.position(); + } + + private void position(long pos) throws IOException { + out.position(pos); + } + } + + private static final class AllocationFreeBufferedOutputStream { + + private byte[] buf; + // current index in buf array + private int position; + // size of valid data in buf array + private int size; + private AllocationFreeFileOutputStream out; + + private AllocationFreeBufferedOutputStream(AllocationFreeFileOutputStream out) { + this(out, 8192); + } + + private AllocationFreeBufferedOutputStream(AllocationFreeFileOutputStream out, int size) { + this.out = out; + if (size <= 0) { + throw new IllegalArgumentException("Buffer size <= 0"); + } + buf = new byte[size]; + } + + private void flushBuffer() throws IOException { + if (size > 0) { + out.write(buf, 0, size); + position = 0; + size = 0; + } + } + + private void write(int b) throws IOException { + if (position >= buf.length) { + flushBuffer(); + } + buf[position++] = (byte) b; + setSize(); + } + + public void write(byte[] b, int off, int len) throws IOException { + if (len >= buf.length) { + flushBuffer(); + out.write(b, off, len); + return; + } + if (len > buf.length - position) { + flushBuffer(); + } + System.arraycopy(b, off, buf, position, len); + position += len; + setSize(); + } + + void flush() throws IOException { + flushBuffer(); + out.flush(); + } + + private long position() throws IOException { + return out.position() + position; + } + + private void position(long pos) throws IOException { + long currentFlushPos = out.position(); + long newCount = pos - currentFlushPos; + + if (newCount >= 0 && newCount <= size) { + position = (int) newCount; + } else { + flush(); + out.position(pos); + } + } + + private void setSize() { + if (position > size) { + size = position; + } + } + } + + private class WriterOperation extends JavaVMOperation { + + private final AllocationFreeDataOutputStream dataOutput; + private final boolean gcBefore; + private IOException exception; + + WriterOperation(FileOutputStream fileOutputStream, boolean gcBefore) throws IOException { + super(VMOperationInfos.get(WriterOperation.class, "Write heap dump", VMOperation.SystemEffect.SAFEPOINT)); + /* open file stream and create buffered data output stream. */ + AllocationFreeFileOutputStream fos = ImageSingletons.lookup(AllocationFreeFileOutputStream.class).newStreamFor(fileOutputStream); + dataOutput = new AllocationFreeDataOutputStream(new AllocationFreeBufferedOutputStream(fos)); + this.gcBefore = gcBefore; + } + + WriterOperation(AllocationFreeOutputStream outputStream, boolean gcBefore, int bufferSize) { + super(VMOperationInfos.get(WriterOperation.class, "Write heap dump", VMOperation.SystemEffect.SAFEPOINT)); + /* open file stream and create buffered data output stream. */ + AllocationFreeFileOutputStream fos = new AllocationFreeFileOutputStreamWrapper(outputStream); + dataOutput = new AllocationFreeDataOutputStream(new AllocationFreeBufferedOutputStream(fos, bufferSize + 32768)); + this.gcBefore = gcBefore; + } + + @Override + protected void operate() { + try { + writeTo(dataOutput, gcBefore); + } catch (IOException ex) { + exception = ex; + } + } + + private IOException getException() { + return exception; + } + + } + + /** A map from Class to ClassData. */ + private static class ClassToClassDataMap { + + private final ClassData[] classDataArray; + + ClassToClassDataMap(Map, ClassData> map) { + /* Find the maximum typeID among the classes. */ + int maxTypeID = 0; + for (Class key : map.keySet()) { + maxTypeID = Integer.max(maxTypeID, typeIDFromClass(key)); + } + /* Make up an array large enough to be indexed by typeID. */ + classDataArray = new ClassData[maxTypeID + 1]; + /* Fill in the array. */ + for (Class key : map.keySet()) { + classDataArray[typeIDFromClass(key)] = map.get(key); + } + } + + /** Use typeID to find the Class. */ + ClassData get(Class clazz) { + int id = typeIDFromClass(clazz); + if (id >= classDataArray.length) { + return null; // class not loaded, there can be no instances + } + return classDataArray[id]; + } + + /** Look up the typeID of a Class from the DynamicHub. */ + private static int typeIDFromClass(Class clazz) { + return DynamicHub.fromClass(clazz).getTypeID(); + } + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/os/BufferedFileOperationSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/os/BufferedFileOperationSupport.java index 244b6f0a2ab9..0e99a69c14b1 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/os/BufferedFileOperationSupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/os/BufferedFileOperationSupport.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 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 @@ -47,7 +47,6 @@ import com.oracle.svm.core.hub.DynamicHub; import com.oracle.svm.core.hub.LayoutEncoding; import com.oracle.svm.core.jdk.JavaLangSubstitutions; -import com.oracle.svm.core.os.AbstractRawFileOperationSupport.RawFileOperationSupportHolder; import com.oracle.svm.core.os.BufferedFileOperationSupport.BufferedFileOperationSupportHolder; import com.oracle.svm.core.os.RawFileOperationSupport.RawFileDescriptor; import com.oracle.svm.core.snippets.KnownIntrinsics; @@ -448,7 +447,7 @@ static BufferedFileOperationSupportHolder singleton() { class BufferedFileOperationFeature implements InternalFeature { @Override public void beforeAnalysis(BeforeAnalysisAccess access) { - if (ImageSingletons.contains(RawFileOperationSupportHolder.class)) { + if (RawFileOperationSupport.isPresent()) { ImageSingletons.add(BufferedFileOperationSupportHolder.class, new BufferedFileOperationSupportHolder()); } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/os/RawFileOperationSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/os/RawFileOperationSupport.java index d37efe523e85..b0d4aa0b720d 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/os/RawFileOperationSupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/os/RawFileOperationSupport.java @@ -27,6 +27,7 @@ import java.io.File; import org.graalvm.compiler.api.replacements.Fold; +import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.word.Pointer; import org.graalvm.word.UnsignedWord; import org.graalvm.word.WordBase; @@ -39,6 +40,11 @@ * that it can be used from uninterruptible code. */ public interface RawFileOperationSupport { + @Fold + static boolean isPresent() { + return ImageSingletons.contains(RawFileOperationSupportHolder.class); + } + /** * Returns a {@link RawFileOperationSupport} singleton that uses little endian byte order. */ diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/HeapDumpFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/HeapDumpFeature.java index 89d3bbdce9cc..83d815b8fc36 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/HeapDumpFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/HeapDumpFeature.java @@ -40,10 +40,14 @@ import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature; import com.oracle.svm.core.feature.InternalFeature; -import com.oracle.svm.core.heapdump.HProfType; -import com.oracle.svm.core.heapdump.HeapDumpSupportImpl; +import com.oracle.svm.core.heap.dump.HProfType; +import com.oracle.svm.core.heap.dump.HeapDumpSupportImpl; +import com.oracle.svm.core.heap.dump.HeapDumpWriter; +import com.oracle.svm.core.heapdump.HeapDumpUtils; +import com.oracle.svm.core.heapdump.HeapDumpWriterImpl; import com.oracle.svm.core.meta.SharedField; import com.oracle.svm.core.meta.SharedType; +import com.oracle.svm.core.os.RawFileOperationSupport; import com.oracle.svm.core.util.ByteArrayReader; import com.oracle.svm.core.util.VMError; import com.oracle.svm.hosted.FeatureImpl.AfterCompilationAccessImpl; @@ -54,8 +58,8 @@ * Heap dumping on Native Image needs some extra metadata about all the classes and fields that are * present in the image. The necessary information is encoded as binary data at image build time * (see below). When the heap dumping is triggered at run-time, the metadata is decoded on the fly - * (see {@link com.oracle.svm.core.heapdump.HeapDumpMetadata}) and used for writing the heap dump - * (see {@link com.oracle.svm.core.heapdump.HeapDumpWriter}). + * (see {@link com.oracle.svm.core.heap.dump.HeapDumpMetadata}) and used for writing the heap dump + * (see {@link HeapDumpWriter}). */ @AutomaticallyRegisteredFeature public class HeapDumpFeature implements InternalFeature { @@ -70,18 +74,36 @@ public boolean isInConfiguration(IsInConfigurationAccess access) { } @Override - public void afterRegistration(AfterRegistrationAccess access) { - ImageSingletons.add(HeapDumpSupport.class, new HeapDumpSupportImpl()); + public void beforeAnalysis(BeforeAnalysisAccess access) { + if (useLegacyImplementation()) { + ImageSingletons.add(HeapDumpSupport.class, new com.oracle.svm.core.heapdump.HeapDumpSupportImpl()); + ImageSingletons.add(HeapDumpUtils.class, new HeapDumpUtils()); + ImageSingletons.add(com.oracle.svm.core.heapdump.HeapDumpWriter.class, new HeapDumpWriterImpl()); + } else { + ImageSingletons.add(HeapDumpSupport.class, new HeapDumpSupportImpl()); + } + } + + public static boolean useLegacyImplementation() { + /* See GR-44538. */ + return !RawFileOperationSupport.isPresent() && !Platform.includedIn(Platform.WINDOWS.class); } @Override public void afterCompilation(Feature.AfterCompilationAccess access) { AfterCompilationAccessImpl accessImpl = (AfterCompilationAccessImpl) access; - byte[] metadata = computeMetadata(accessImpl.getTypes()); + if (useLegacyImplementation()) { + byte[] fieldMap = HeapDumpHostedUtils.dumpFieldsMap(accessImpl.getTypes()); - HeapDumpSupportImpl support = (HeapDumpSupportImpl) ImageSingletons.lookup(HeapDumpSupport.class); - support.setMetadata(metadata); - access.registerAsImmutable(metadata); + HeapDumpUtils.getHeapDumpUtils().setFieldsMap(fieldMap); + access.registerAsImmutable(fieldMap); + } else { + byte[] metadata = computeMetadata(accessImpl.getTypes()); + + HeapDumpSupportImpl support = (HeapDumpSupportImpl) ImageSingletons.lookup(HeapDumpSupport.class); + support.setMetadata(metadata); + access.registerAsImmutable(metadata); + } } /** diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/HeapDumpFieldsMapFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/HeapDumpFieldsMapFeature.java new file mode 100644 index 000000000000..32f4f7320eac --- /dev/null +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/HeapDumpFieldsMapFeature.java @@ -0,0 +1,141 @@ +/* + * Copyright (c) 2022, 2022, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package com.oracle.svm.hosted.heap; + +import java.io.UnsupportedEncodingException; +import java.util.Collection; + +import org.graalvm.compiler.core.common.util.TypeConversion; +import org.graalvm.compiler.core.common.util.UnsafeArrayTypeWriter; +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; + +import com.oracle.svm.core.meta.SharedField; +import com.oracle.svm.core.meta.SharedType; +import com.oracle.svm.core.util.ByteArrayReader; +import com.oracle.svm.core.util.VMError; + +import jdk.vm.ci.meta.ResolvedJavaField; + +/** Legacy implementation, only used by other legacy code (see GR-44538). */ +class HeapDumpHostedUtils { + + @Platforms(Platform.HOSTED_ONLY.class) + public static byte[] dumpFieldsMap(Collection types) { + UnsafeArrayTypeWriter writeBuffer = UnsafeArrayTypeWriter.create(ByteArrayReader.supportsUnalignedMemoryAccess()); + + writeFieldsInfo(writeBuffer, types); + int length = TypeConversion.asS4(writeBuffer.getBytesWritten()); + return writeBuffer.toArray(new byte[length]); + } + + @Platforms(Platform.HOSTED_ONLY.class) + public static void writeFieldsInfo(UnsafeArrayTypeWriter writeBuffer, Collection types) { + for (SharedType type : types) { + /* I am only interested in instance types. */ + if (type.isInstanceClass()) { + /* Get the direct fields of the class. */ + final ResolvedJavaField[] fields = type.getInstanceFields(false); + /* Get the static fields of the class. */ + final ResolvedJavaField[] sfields = type.getStaticFields(); + /* I am only interested in classes with some fields. */ + if (fields.length == 0 && sfields.length == 0) { + continue; + } + /* Write the class name */ + writeString(writeBuffer, type.toClassName()); + + /* Write each direct field and offset. */ + for (ResolvedJavaField resolvedJavaField : inHotSpotFieldOrder(fields)) { + if (resolvedJavaField instanceof SharedField) { + final SharedField field = (SharedField) resolvedJavaField; + + writeField(field, writeBuffer); + } + } + writeBuffer.putU1(0); + /* Write each static field and offset. */ + for (ResolvedJavaField resolvedJavaField : inHotSpotFieldOrder(sfields)) { + if (resolvedJavaField instanceof SharedField) { + final SharedField field = (SharedField) resolvedJavaField; + if (!field.isWritten()) { + /* I am only interested in fields that are not constants. */ + continue; + } + if (!field.isAccessed()) { + /* I am only interested in fields that are used. */ + continue; + } + writeField(field, writeBuffer); + } + } + writeBuffer.putU1(0); + } + } + } + + @Platforms(Platform.HOSTED_ONLY.class) + private static void writeField(final SharedField field, UnsafeArrayTypeWriter writeBuffer) { + final int location = field.getLocation(); + /* I am only interested in fields that have locations. */ + if (location < 0) { + return; + } + writeString(writeBuffer, field.getName()); + writeBuffer.putU1(field.getJavaKind().getTypeChar()); + writeBuffer.putU1(field.getStorageKind().getTypeChar()); + writeBuffer.putU1((location >>> 24) & 0xFF); + writeBuffer.putU1((location >>> 16) & 0xFF); + writeBuffer.putU1((location >>> 8) & 0xFF); + writeBuffer.putU1((location >>> 0) & 0xFF); + } + + /* + * Write fields in the same order as in HotSpot heap dump. This is the reverse order of what SVM + * hands out. See also GR-6758. + */ + @Platforms(Platform.HOSTED_ONLY.class) + private static ResolvedJavaField[] inHotSpotFieldOrder(ResolvedJavaField[] fields) { + ResolvedJavaField[] reversed = new ResolvedJavaField[fields.length]; + + for (int i = 0; i < fields.length; i++) { + reversed[fields.length - 1 - i] = fields[i]; + } + return reversed; + } + + @Platforms(Platform.HOSTED_ONLY.class) + private static void writeString(UnsafeArrayTypeWriter writeBuffer, String name) { + try { + byte[] buf = name.getBytes("UTF-8"); + for (byte b : buf) { + writeBuffer.putU1(b); + } + writeBuffer.putU1(0); + } catch (UnsupportedEncodingException ex) { + VMError.shouldNotReachHere(ex); + } + } +} From dd0b4d4e0280b9bdb7296c7244b32c2a47bd086d Mon Sep 17 00:00:00 2001 From: Christian Haeubl Date: Sat, 4 Mar 2023 14:37:45 +0100 Subject: [PATCH 5/5] Review feedback. --- .../svm/core/heap/dump/HeapDumpMetadata.java | 126 ++++++-- .../core/heap/dump/HeapDumpSupportImpl.java | 9 +- .../svm/core/heap/dump/HeapDumpWriter.java | 306 ++++++++++++++++-- .../com/oracle/svm/core/hub/DynamicHub.java | 2 +- .../svm/hosted/heap/HeapDumpFeature.java | 73 ++--- ...pFeature.java => HeapDumpHostedUtils.java} | 0 6 files changed, 397 insertions(+), 119 deletions(-) rename substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/{HeapDumpFieldsMapFeature.java => HeapDumpHostedUtils.java} (100%) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/dump/HeapDumpMetadata.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/dump/HeapDumpMetadata.java index 8a78133716e4..32fef73016e2 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/dump/HeapDumpMetadata.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/dump/HeapDumpMetadata.java @@ -24,7 +24,10 @@ */ package com.oracle.svm.core.heap.dump; +import org.graalvm.compiler.api.replacements.Fold; import org.graalvm.nativeimage.ImageSingletons; +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; import org.graalvm.nativeimage.StackValue; import org.graalvm.nativeimage.c.struct.RawField; import org.graalvm.nativeimage.c.struct.RawPointerTo; @@ -41,6 +44,7 @@ import com.oracle.svm.core.c.struct.PinnedObjectField; import com.oracle.svm.core.heap.Heap; import com.oracle.svm.core.heap.ObjectVisitor; +import com.oracle.svm.core.heap.UnknownObjectField; import com.oracle.svm.core.hub.DynamicHub; import com.oracle.svm.core.util.coder.ByteStream; import com.oracle.svm.core.util.coder.ByteStreamAccess; @@ -49,21 +53,75 @@ /** * Provides access to the encoded heap dump metadata that was prepared at image build-time. + * + *
+ * |----------------------------|
+ * | metadata byte[]            |
+ * |----------------------------|
+ * | s4 totalFieldCount         |
+ * | s4 classCount              |
+ * | s4 fieldNameCount          |
+ * | uv maxTypeId               |
+ * | (class information)*       |
+ * | (field names)*             |
+ * |----------------------------|
+ *
+ * |----------------------------|
+ * | information per class      |
+ * |----------------------------|
+ * | uv typeId                  |
+ * | uv instanceFieldCount      |
+ * | uv staticFieldCount        |
+ * | (instance field)*          |
+ * | (static field)*            |
+ * |----------------------------|
+ *
+ * |----------------------------|
+ * | information per field      |
+ * |----------------------------|
+ * | u1 type             |
+ * | uv fieldNameIndex          |
+ * | uv location                |
+ * |----------------------------|
+ *
+ * |----------------------------|
+ * | information per field name |
+ * |----------------------------|
+ * | uv lengthInBytes           |
+ * | (s1 utf8 character)*       |
+ * |----------------------------|
+ * 
*/ public class HeapDumpMetadata { - private static final ComputeHubDataVisitor COMPUTE_HUB_DATA_VISITOR = new ComputeHubDataVisitor(); + private final ComputeHubDataVisitor computeHubDataVisitor; + @UnknownObjectField(types = {byte[].class}) private byte[] data; + + private int fieldNameCount; + private int classInfoCount; + private ClassInfo classInfos; + private FieldInfoPointer fieldInfoTable; + private FieldNamePointer fieldNameTable; + + @Platforms(Platform.HOSTED_ONLY.class) + public HeapDumpMetadata() { + computeHubDataVisitor = new ComputeHubDataVisitor(); + } + + @Fold + public static HeapDumpMetadata singleton() { + return ImageSingletons.lookup(HeapDumpMetadata.class); + } - private static int fieldNameCount; - private static int classInfoCount; - private static ClassInfo classInfos; - private static FieldInfoPointer fieldInfoTable; - private static FieldNamePointer fieldNameTable; + @Platforms(Platform.HOSTED_ONLY.class) + public void setData(byte[] value) { + this.data = value; + } - public static boolean initialize(byte[] metadata) { + public boolean initialize() { assert classInfos.isNull() && fieldInfoTable.isNull() && fieldNameTable.isNull(); - Pointer start = NonmovableArrays.getArrayBase(NonmovableArrays.fromImageHeap(metadata)); - Pointer end = start.add(metadata.length); + Pointer start = NonmovableArrays.getArrayBase(NonmovableArrays.fromImageHeap(data)); + Pointer end = start.add(data.length); ByteStream stream = StackValue.get(ByteStream.class); ByteStreamAccess.initialize(stream, start); @@ -138,12 +196,12 @@ public static boolean initialize(byte[] metadata) { assert stream.getPosition().equal(end); /* Store the DynamicHubs in their corresponding ClassInfo structs. */ - COMPUTE_HUB_DATA_VISITOR.initialize(); - Heap.getHeap().walkImageHeapObjects(COMPUTE_HUB_DATA_VISITOR); + computeHubDataVisitor.initialize(); + Heap.getHeap().walkImageHeapObjects(computeHubDataVisitor); /* Compute the size of the instance fields per class. */ for (int i = 0; i < classInfoCount; i++) { - ClassInfo classInfo = HeapDumpMetadata.getClassInfo(i); + ClassInfo classInfo = getClassInfo(i); if (ClassInfoAccess.isValid(classInfo)) { computeInstanceFieldsDumpSize(classInfo); } @@ -151,8 +209,10 @@ public static boolean initialize(byte[] metadata) { return true; } - /** Must always be called, regardless if {@link #initialize} returned true or false. */ - public static void teardown() { + /** + * Must always be called, regardless if {@link #initialize} returned true or false. + */ + public void teardown() { ImageSingletons.lookup(UnmanagedMemorySupport.class).free(classInfos); classInfos = WordFactory.nullPointer(); @@ -163,33 +223,33 @@ public static void teardown() { fieldNameTable = WordFactory.nullPointer(); } - public static int getClassInfoCount() { + public int getClassInfoCount() { return classInfoCount; } - public static ClassInfo getClassInfo(Class clazz) { + public ClassInfo getClassInfo(Class clazz) { if (clazz == null) { return WordFactory.nullPointer(); } return getClassInfo(DynamicHub.fromClass(clazz)); } - public static ClassInfo getClassInfo(DynamicHub hub) { + public ClassInfo getClassInfo(DynamicHub hub) { if (hub == null) { return WordFactory.nullPointer(); } return getClassInfo(hub.getTypeID()); } - public static ClassInfo getClassInfo(int typeId) { + public ClassInfo getClassInfo(int typeId) { return classInfos.addressOf(typeId); } - public static int getFieldNameCount() { + public int getFieldNameCount() { return fieldNameCount; } - public static FieldName getFieldName(int index) { + public FieldName getFieldName(int index) { return fieldNameTable.addressOf(index).read(); } @@ -199,7 +259,7 @@ public static FieldName getFieldName(int index) { * size in the heap. The size in the heap will include the object header, padding/alignment, and * the size of object references may be different. */ - private static int computeInstanceFieldsDumpSize(ClassInfo classInfo) { + private int computeInstanceFieldsDumpSize(ClassInfo classInfo) { /* Check if this class was already processed earlier. */ if (classInfo.getInstanceFieldsDumpSize() != -1) { return classInfo.getInstanceFieldsDumpSize(); @@ -211,7 +271,7 @@ private static int computeInstanceFieldsDumpSize(ClassInfo classInfo) { /* Add the size of all inherited fields. */ DynamicHub superHub = classInfo.getHub().getSuperHub(); if (superHub != null) { - result += computeInstanceFieldsDumpSize(HeapDumpMetadata.getClassInfo(superHub)); + result += computeInstanceFieldsDumpSize(getClassInfo(superHub)); } classInfo.setInstanceFieldsDumpSize(result); return result; @@ -288,7 +348,9 @@ static boolean isValid(ClassInfo classInfo) { } } - /** Data structure has a variable size. */ + /** + * Data structure has a variable size. + */ @RawStructure public interface FieldInfo extends PointerBase { // u1 type @@ -303,7 +365,7 @@ static HProfType getType(FieldInfo field) { static FieldName getFieldName(FieldInfo field) { int fieldNameIndex = Pack200Coder.readUVAsInt(getFieldNameIndexAddress(field)); - return HeapDumpMetadata.getFieldName(fieldNameIndex); + return HeapDumpMetadata.singleton().getFieldName(fieldNameIndex); } static int getLocation(FieldInfo field) { @@ -332,7 +394,9 @@ public interface FieldInfoPointer extends PointerBase { FieldInfo read(); } - /** Data structure has a variable size. */ + /** + * Data structure has a variable size. + */ @RawStructure public interface FieldName extends PointerBase { // uv lengthInBytes @@ -369,13 +433,11 @@ public void initialize() { @Override public boolean visitObject(Object o) { if (o instanceof DynamicHub hub) { - ClassInfo classInfo = HeapDumpMetadata.getClassInfo(hub.getTypeID()); - if (classInfo.getHub() == null) { - /* Initialize the relevant data for all classes that don't declare any fields. */ - classInfo.setHub(hub); - classInfo.setSerialNum(++classSerialNum); - classInfo.setInstanceFieldsDumpSize(-1); - } + ClassInfo classInfo = HeapDumpMetadata.singleton().getClassInfo(hub.getTypeID()); + assert classInfo.getHub() == null; + classInfo.setHub(hub); + classInfo.setSerialNum(++classSerialNum); + classInfo.setInstanceFieldsDumpSize(-1); } return true; } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/dump/HeapDumpSupportImpl.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/dump/HeapDumpSupportImpl.java index c71bd2f7e61e..33201d0cc54d 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/dump/HeapDumpSupportImpl.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/dump/HeapDumpSupportImpl.java @@ -56,16 +56,11 @@ public class HeapDumpSupportImpl implements HeapDumpSupport { private final HeapDumpOperation heapDumpOperation; @Platforms(Platform.HOSTED_ONLY.class) - public HeapDumpSupportImpl() { - this.writer = new HeapDumpWriter(); + public HeapDumpSupportImpl(HeapDumpMetadata metadata) { + this.writer = new HeapDumpWriter(metadata); this.heapDumpOperation = new HeapDumpOperation(); } - @Platforms(Platform.HOSTED_ONLY.class) - public void setMetadata(byte[] value) { - this.writer.setMetadata(value); - } - @Override public void dumpHeap(String filename, boolean gcBefore) throws IOException { RawFileDescriptor fd = getFileSupport().create(filename, FileCreationMode.CREATE_OR_REPLACE, RawFileOperationSupport.FileAccessMode.READ_WRITE); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/dump/HeapDumpWriter.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/dump/HeapDumpWriter.java index d88310af18e6..fed71c167c92 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/dump/HeapDumpWriter.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/dump/HeapDumpWriter.java @@ -67,7 +67,6 @@ import com.oracle.svm.core.heap.ReferenceAccess; import com.oracle.svm.core.heap.ReferenceMapIndex; import com.oracle.svm.core.heap.RestrictHeapAccess; -import com.oracle.svm.core.heap.UnknownObjectField; import com.oracle.svm.core.heap.dump.HeapDumpMetadata.ClassInfo; import com.oracle.svm.core.heap.dump.HeapDumpMetadata.ClassInfoAccess; import com.oracle.svm.core.heap.dump.HeapDumpMetadata.FieldInfo; @@ -125,6 +124,269 @@ *
  • The size of individual records is limited to {@link #MAX_UNSIGNED_INT}. So, very large arrays * that have a larger size than {@link #MAX_UNSIGNED_INT} need to be truncated.
  • * + * + * HPROF file format in detail (mostly copied from {@code heapDumper.cpp}): + * + *
    + * HPROF binary format:
    + *
    + * 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.
    + * u4         high word
    + * u4         low word    number of milliseconds since 0:00 GMT, 1/1/70
    + * [record]*  a sequence of records.
    + *
    + *
    + * Record format:
    + *
    + * u1         a TAG denoting the type of the record
    + * u4         number of *microseconds* since the time stamp in the
    + *            header. (wraps around in a little more than an hour)
    + * u4         number of bytes *remaining* in the record. Note that
    + *            this number excludes the tag and the length field itself.
    + * [u1]*      BODY of the record (a sequence of bytes)
    + *
    + *
    + * The following TAGs are supported:
    + *
    + * TAG           BODY       notes
    + *----------------------------------------------------------
    + * HPROF_UTF8               a UTF8-encoded name
    + *
    + *               id         name ID
    + *               [u1]*      UTF8 characters (no trailing zero)
    + *
    + * HPROF_LOAD_CLASS         a newly loaded class
    + *
    + *                u4        class serial number (> 0)
    + *                id        class object ID
    + *                u4        stack trace serial number
    + *                id        class name ID
    + *
    + * HPROF_UNLOAD_CLASS       an unloading class
    + *
    + *                u4        class serial_number
    + *
    + * HPROF_FRAME              a Java stack frame
    + *
    + *                id        stack frame ID
    + *                id        method name ID
    + *                id        method signature ID
    + *                id        source file name ID
    + *                u4        class serial number
    + *                i4        line number. >0: normal
    + *                                       -1: unknown
    + *                                       -2: compiled method
    + *                                       -3: native method
    + *
    + * HPROF_TRACE              a Java stack trace
    + *
    + *               u4         stack trace serial number
    + *               u4         thread serial number
    + *               u4         number of frames
    + *               [id]*      stack frame IDs
    + *
    + *
    + * HPROF_ALLOC_SITES        a set of heap allocation sites, obtained after GC
    + *
    + *               u2         flags 0x0001: incremental vs. complete
    + *                                0x0002: sorted by allocation vs. live
    + *                                0x0004: whether to force a GC
    + *               u4         cutoff ratio
    + *               u4         total live bytes
    + *               u4         total live instances
    + *               u8         total bytes allocated
    + *               u8         total instances allocated
    + *               u4         number of sites that follow
    + *               [u1        is_array: 0:  normal object
    + *                                    2:  object array
    + *                                    4:  boolean array
    + *                                    5:  char array
    + *                                    6:  float array
    + *                                    7:  double array
    + *                                    8:  byte array
    + *                                    9:  short array
    + *                                    10: int array
    + *                                    11: long array
    + *                u4        class serial number (may be zero during startup)
    + *                u4        stack trace serial number
    + *                u4        number of bytes alive
    + *                u4        number of instances alive
    + *                u4        number of bytes allocated
    + *                u4]*      number of instance allocated
    + *
    + * HPROF_START_THREAD       a newly started thread.
    + *
    + *               u4         thread serial number (> 0)
    + *               id         thread object ID
    + *               u4         stack trace serial number
    + *               id         thread name ID
    + *               id         thread group name ID
    + *               id         thread group parent name ID
    + *
    + * HPROF_END_THREAD         a terminating thread.
    + *
    + *               u4         thread serial number
    + *
    + * HPROF_HEAP_SUMMARY       heap summary
    + *
    + *               u4         total live bytes
    + *               u4         total live instances
    + *               u8         total bytes allocated
    + *               u8         total instances allocated
    + *
    + * HPROF_HEAP_DUMP          denote a heap dump
    + *
    + *               [heap dump sub-records]*
    + *
    + *                          There are four kinds of heap dump sub-records:
    + *
    + *               u1         sub-record type
    + *
    + *               HPROF_GC_ROOT_UNKNOWN         unknown root
    + *
    + *                          id         object ID
    + *
    + *               HPROF_GC_ROOT_THREAD_OBJ      thread object
    + *
    + *                          id         thread object ID  (may be 0 for a
    + *                                     thread newly attached through JNI)
    + *                          u4         thread sequence number
    + *                          u4         stack trace sequence number
    + *
    + *               HPROF_GC_ROOT_JNI_GLOBAL      JNI global ref root
    + *
    + *                          id         object ID
    + *                          id         JNI global ref ID
    + *
    + *               HPROF_GC_ROOT_JNI_LOCAL       JNI local ref
    + *
    + *                          id         object ID
    + *                          u4         thread serial number
    + *                          u4         frame # in stack trace (-1 for empty)
    + *
    + *               HPROF_GC_ROOT_JAVA_FRAME      Java stack frame
    + *
    + *                          id         object ID
    + *                          u4         thread serial number
    + *                          u4         frame # in stack trace (-1 for empty)
    + *
    + *               HPROF_GC_ROOT_NATIVE_STACK    Native stack
    + *
    + *                          id         object ID
    + *                          u4         thread serial number
    + *
    + *               HPROF_GC_ROOT_STICKY_CLASS    System class
    + *
    + *                          id         object ID
    + *
    + *               HPROF_GC_ROOT_THREAD_BLOCK    Reference from thread block
    + *
    + *                          id         object ID
    + *                          u4         thread serial number
    + *
    + *               HPROF_GC_ROOT_MONITOR_USED    Busy monitor
    + *
    + *                          id         object ID
    + *
    + *               HPROF_GC_CLASS_DUMP           dump of a class object
    + *
    + *                          id         class object ID
    + *                          u4         stack trace serial number
    + *                          id         super class object ID
    + *                          id         class loader object ID
    + *                          id         signers object ID
    + *                          id         protection domain object ID
    + *                          id         reserved
    + *                          id         reserved
    + *
    + *                          u4         instance size (in bytes)
    + *
    + *                          u2         size of constant pool
    + *                          [u2,       constant pool index,
    + *                           ty,       type
    + *                                     2:  object
    + *                                     4:  boolean
    + *                                     5:  char
    + *                                     6:  float
    + *                                     7:  double
    + *                                     8:  byte
    + *                                     9:  short
    + *                                     10: int
    + *                                     11: long
    + *                           vl]*      and value
    + *
    + *                          u2         number of static fields
    + *                          [id,       static field name,
    + *                           ty,       type,
    + *                           vl]*      and value
    + *
    + *                          u2         number of inst. fields (not inc. super)
    + *                          [id,       instance field name,
    + *                           ty]*      type
    + *
    + *               HPROF_GC_INSTANCE_DUMP        dump of a normal object
    + *
    + *                          id         object ID
    + *                          u4         stack trace serial number
    + *                          id         class object ID
    + *                          u4         number of bytes that follow
    + *                          [vl]*      instance field values (class, followed
    + *                                     by super, super's super ...)
    + *
    + *               HPROF_GC_OBJ_ARRAY_DUMP       dump of an object array
    + *
    + *                          id         array object ID
    + *                          u4         stack trace serial number
    + *                          u4         number of elements
    + *                          id         array class ID
    + *                          [id]*      elements
    + *
    + *               HPROF_GC_PRIM_ARRAY_DUMP      dump of a primitive array
    + *
    + *                          id         array object ID
    + *                          u4         stack trace serial number
    + *                          u4         number of elements
    + *                          u1         element type
    + *                                     4:  boolean array
    + *                                     5:  char array
    + *                                     6:  float array
    + *                                     7:  double array
    + *                                     8:  byte array
    + *                                     9:  short array
    + *                                     10: int array
    + *                                     11: long array
    + *                          [u1]*      elements
    + *
    + * HPROF_CPU_SAMPLES        a set of sample traces of running threads
    + *
    + *                u4        total number of samples
    + *                u4        # of traces
    + *               [u4        # of samples
    + *                u4]*      stack trace serial number
    + *
    + * HPROF_CONTROL_SETTINGS   the settings of on/off switches
    + *
    + *                u4        0x00000001: alloc traces on/off
    + *                          0x00000002: cpu sampling on/off
    + *                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:
    + *
    + * HPROF_HEAP_DUMP_SEGMENT  denote a heap dump segment
    + *
    + *               [heap dump sub-records]*
    + *               The same sub-record types allowed by HPROF_HEAP_DUMP
    + *
    + * HPROF_HEAP_DUMP_END      denotes the end of a heap dump
    + * 
    */ public class HeapDumpWriter { private static final long MAX_UNSIGNED_INT = (1L << 32) - 1; @@ -138,7 +400,7 @@ public class HeapDumpWriter { private final DumpObjectsVisitor dumpObjectsVisitor = new DumpObjectsVisitor(); private final CodeMetadataVisitor codeMetadataVisitor = new CodeMetadataVisitor(); private final ThreadLocalsVisitor threadLocalsVisitor = new ThreadLocalsVisitor(); - @UnknownObjectField(types = {byte[].class}) private byte[] metadata; + private final HeapDumpMetadata metadata; private BufferedFile f; private long topLevelRecordBegin = -1; @@ -146,12 +408,8 @@ public class HeapDumpWriter { private boolean error; @Platforms(Platform.HOSTED_ONLY.class) - public HeapDumpWriter() { - } - - @Platforms(Platform.HOSTED_ONLY.class) - public void setMetadata(byte[] value) { - this.metadata = value; + public HeapDumpWriter(HeapDumpMetadata metadata) { + this.metadata = metadata; } public boolean dumpHeap(RawFileDescriptor fd) { @@ -189,11 +447,11 @@ private boolean initialize(RawFileDescriptor fd) { if (f.isNull()) { return false; } - return HeapDumpMetadata.initialize(metadata); + return metadata.initialize(); } private void teardown() { - HeapDumpMetadata.teardown(); + metadata.teardown(); assert f.isNull() || error || file().getUnflushedDataSize(f) == 0; file().free(f); @@ -282,8 +540,8 @@ private void endSubRecord(long recordSize) { } private void writeClassNames() { - for (int i = 0; i < HeapDumpMetadata.getClassInfoCount(); i++) { - ClassInfo classInfo = HeapDumpMetadata.getClassInfo(i); + for (int i = 0; i < metadata.getClassInfoCount(); i++) { + ClassInfo classInfo = metadata.getClassInfo(i); if (ClassInfoAccess.isValid(classInfo)) { writeSymbol(classInfo.getHub().getName()); } @@ -305,15 +563,15 @@ private void writeSymbol(FieldName fieldName) { } private void writeFieldNames() { - for (int i = 0; i < HeapDumpMetadata.getFieldNameCount(); i++) { - FieldName fieldName = HeapDumpMetadata.getFieldName(i); + for (int i = 0; i < metadata.getFieldNameCount(); i++) { + FieldName fieldName = metadata.getFieldName(i); writeSymbol(fieldName); } } private void writeLoadedClasses() { - for (int i = 0; i < HeapDumpMetadata.getClassInfoCount(); i++) { - ClassInfo classInfo = HeapDumpMetadata.getClassInfo(i); + for (int i = 0; i < metadata.getClassInfoCount(); i++) { + ClassInfo classInfo = metadata.getClassInfo(i); if (ClassInfoAccess.isValid(classInfo)) { DynamicHub hub = classInfo.getHub(); if (hub.isLoaded()) { @@ -373,8 +631,8 @@ private void writeDummyStackTrace() { } private void writeClasses() { - for (int i = 0; i < HeapDumpMetadata.getClassInfoCount(); i++) { - ClassInfo classInfo = HeapDumpMetadata.getClassInfo(i); + for (int i = 0; i < metadata.getClassInfoCount(); i++) { + ClassInfo classInfo = metadata.getClassInfo(i); if (ClassInfoAccess.isValid(classInfo)) { if (classInfo.getHub().isLoaded()) { writeClassDumpRecord(classInfo); @@ -489,8 +747,8 @@ private void writeJNIGlobals() { } private void writeStickyClasses() { - for (int i = 0; i < HeapDumpMetadata.getClassInfoCount(); i++) { - ClassInfo classInfo = HeapDumpMetadata.getClassInfo(i); + for (int i = 0; i < metadata.getClassInfoCount(); i++) { + ClassInfo classInfo = metadata.getClassInfo(i); if (ClassInfoAccess.isValid(classInfo)) { int recordSize = 1 + wordSize(); startSubRecord(HProfSubRecord.GC_ROOT_STICKY_CLASS, recordSize); @@ -612,7 +870,7 @@ private void markAsJniGlobalGCRoot(Object obj) { } private void writeInstance(Object obj) { - ClassInfo classInfo = HeapDumpMetadata.getClassInfo(obj.getClass()); + ClassInfo classInfo = metadata.getClassInfo(obj.getClass()); int instanceFieldsSize = classInfo.getInstanceFieldsDumpSize(); int recordSize = 1 + wordSize() + 4 + wordSize() + 4 + instanceFieldsSize; @@ -630,7 +888,7 @@ private void writeInstance(Object obj) { FieldInfo field = instanceFields.addressOf(i).read(); writeFieldData(obj, field); } - classInfo = HeapDumpMetadata.getClassInfo(classInfo.getHub().getSuperHub()); + classInfo = metadata.getClassInfo(classInfo.getHub().getSuperHub()); } while (classInfo.isNonNull()); endSubRecord(recordSize); @@ -1016,7 +1274,7 @@ private void visitFrame(FrameInfoQueryResult frame) { writeSymbol(sourceFileName); /* Write the FRAME record. */ - ClassInfo classInfo = HeapDumpMetadata.getClassInfo(frame.getSourceClass()); + ClassInfo classInfo = metadata.getClassInfo(frame.getSourceClass()); int lineNumber = getLineNumber(frame); writeFrame(classInfo.getSerialNum(), lineNumber, methodName, methodSignature, sourceFileName); } @@ -1093,7 +1351,7 @@ private UnsignedWord getObjectSize(Object obj) { int length = ArrayLengthNode.arrayLength(obj); return WordFactory.unsigned(length).multiply(elementSize); } else { - ClassInfo classInfo = HeapDumpMetadata.getClassInfo(obj.getClass()); + ClassInfo classInfo = metadata.getClassInfo(obj.getClass()); return WordFactory.unsigned(classInfo.getInstanceFieldsDumpSize()); } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHub.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHub.java index a7727d7a16a1..f319d4a66d00 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHub.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHub.java @@ -770,7 +770,7 @@ public InputStream getResourceAsStream(String resourceName) { @Substitute public ClassLoader getClassLoader() { - return classLoader; + return companion.getClassLoader(); } @KeepOriginal diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/HeapDumpFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/HeapDumpFeature.java index 83d815b8fc36..7b19650c7f32 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/HeapDumpFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/HeapDumpFeature.java @@ -41,6 +41,7 @@ import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature; import com.oracle.svm.core.feature.InternalFeature; import com.oracle.svm.core.heap.dump.HProfType; +import com.oracle.svm.core.heap.dump.HeapDumpMetadata; import com.oracle.svm.core.heap.dump.HeapDumpSupportImpl; import com.oracle.svm.core.heap.dump.HeapDumpWriter; import com.oracle.svm.core.heapdump.HeapDumpUtils; @@ -57,9 +58,9 @@ /** * Heap dumping on Native Image needs some extra metadata about all the classes and fields that are * present in the image. The necessary information is encoded as binary data at image build time - * (see below). When the heap dumping is triggered at run-time, the metadata is decoded on the fly - * (see {@link com.oracle.svm.core.heap.dump.HeapDumpMetadata}) and used for writing the heap dump - * (see {@link HeapDumpWriter}). + * (see {@link #encodeMetadata}}). When the heap dumping is triggered at run-time, the metadata is + * decoded on the fly (see {@link com.oracle.svm.core.heap.dump.HeapDumpMetadata}) and used for + * writing the heap dump (see {@link HeapDumpWriter}). */ @AutomaticallyRegisteredFeature public class HeapDumpFeature implements InternalFeature { @@ -80,13 +81,15 @@ public void beforeAnalysis(BeforeAnalysisAccess access) { ImageSingletons.add(HeapDumpUtils.class, new HeapDumpUtils()); ImageSingletons.add(com.oracle.svm.core.heapdump.HeapDumpWriter.class, new HeapDumpWriterImpl()); } else { - ImageSingletons.add(HeapDumpSupport.class, new HeapDumpSupportImpl()); + HeapDumpMetadata metadata = new HeapDumpMetadata(); + ImageSingletons.add(HeapDumpMetadata.class, metadata); + ImageSingletons.add(HeapDumpSupport.class, new HeapDumpSupportImpl(metadata)); } } public static boolean useLegacyImplementation() { /* See GR-44538. */ - return !RawFileOperationSupport.isPresent() && !Platform.includedIn(Platform.WINDOWS.class); + return !RawFileOperationSupport.isPresent(); } @Override @@ -98,68 +101,28 @@ public void afterCompilation(Feature.AfterCompilationAccess access) { HeapDumpUtils.getHeapDumpUtils().setFieldsMap(fieldMap); access.registerAsImmutable(fieldMap); } else { - byte[] metadata = computeMetadata(accessImpl.getTypes()); - - HeapDumpSupportImpl support = (HeapDumpSupportImpl) ImageSingletons.lookup(HeapDumpSupport.class); - support.setMetadata(metadata); + byte[] metadata = encodeMetadata(accessImpl.getTypes()); + HeapDumpMetadata.singleton().setData(metadata); access.registerAsImmutable(metadata); } } /** - * This method writes the metadata that is needed for heap dumping into one large byte[]. The - * format is as follows: - * - *
    -     * |----------------------------|
    -     * | metadata byte[]            |
    -     * |----------------------------|
    -     * | s4 totalFieldCount         |
    -     * | s4 classCount              |
    -     * | s4 fieldNameCount          |
    -     * | uv maxTypeId               |
    -     * | (class information)*       |
    -     * | (field names)*             |
    -     * |----------------------------|
    -     *
    -     * |----------------------------|
    -     * | information per class      |
    -     * |----------------------------|
    -     * | uv typeId                  |
    -     * | uv instanceFieldCount      |
    -     * | uv staticFieldCount        |
    -     * | (instance field)*          |
    -     * | (static field)*            |
    -     * |----------------------------|
    -     *
    -     * |----------------------------|
    -     * | information per field      |
    -     * |----------------------------|
    -     * | u1 type             |
    -     * | uv fieldNameIndex          |
    -     * | uv location                |
    -     * |----------------------------|
    -     *
    -     * |----------------------------|
    -     * | information per field name |
    -     * |----------------------------|
    -     * | uv lengthInBytes           |
    -     * | (s1 utf8 character)*       |
    -     * |----------------------------|
    -     * 
    + * This method writes the metadata that is needed for heap dumping into one large byte[] (see + * {@link com.oracle.svm.core.heap.dump.HeapDumpMetadata} for more details). */ - private static byte[] computeMetadata(Collection types) { + private static byte[] encodeMetadata(Collection types) { int maxTypeId = types.stream().mapToInt(t -> t.getHub().getTypeID()).max().orElse(0); assert maxTypeId > 0; UnsafeArrayTypeWriter output = UnsafeArrayTypeWriter.create(ByteArrayReader.supportsUnalignedMemoryAccess()); - writeMetadata(output, types, maxTypeId); + encodeMetadata(output, types, maxTypeId); int length = TypeConversion.asS4(output.getBytesWritten()); return output.toArray(new byte[length]); } - private static void writeMetadata(UnsafeArrayTypeWriter output, Collection types, int maxTypeId) { + private static void encodeMetadata(UnsafeArrayTypeWriter output, Collection types, int maxTypeId) { /* Write header. */ long totalFieldCountOffset = output.getBytesWritten(); output.putS4(0); // total field count (patched later on) @@ -191,12 +154,12 @@ private static void writeMetadata(UnsafeArrayTypeWriter output, Collection collectFields(ResolvedJavaField[] input) { return result; } - private static void writeField(SharedField field, UnsafeArrayTypeWriter output, EconomicMap fieldNames) { + private static void encodeField(SharedField field, UnsafeArrayTypeWriter output, EconomicMap fieldNames) { int location = field.getLocation(); assert location >= 0; output.putU1(getType(field).ordinal()); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/HeapDumpFieldsMapFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/HeapDumpHostedUtils.java similarity index 100% rename from substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/HeapDumpFieldsMapFeature.java rename to substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/HeapDumpHostedUtils.java