diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/image/ImageHeap.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/image/ImageHeap.java index f65ca0162524..d7f945578a81 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/image/ImageHeap.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/image/ImageHeap.java @@ -29,7 +29,7 @@ public interface ImageHeap { Collection getObjects(); - ImageHeapObject addLateToImageHeap(Object object, String reason); + ImageHeapObject addLateToImageHeap(Object object, Object reason); ImageHeapObject addFillerObject(int size); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ByteFormattingUtil.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ByteFormattingUtil.java new file mode 100644 index 000000000000..0c0abd2cb92d --- /dev/null +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ByteFormattingUtil.java @@ -0,0 +1,59 @@ +/* + * 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; + +public class ByteFormattingUtil { + private static final double BYTES_TO_KiB = 1024d; + private static final double BYTES_TO_MiB = 1024d * 1024d; + private static final double BYTES_TO_GiB = 1024d * 1024d * 1024d; + + public static String bytesToHuman(long bytes) { + return bytesToHuman("%4.2f", bytes); + } + + public static String bytesToHuman(String format, long bytes) { + if (bytes < BYTES_TO_KiB) { + return String.format(format, (double) bytes) + "B"; + } else if (bytes < BYTES_TO_MiB) { + return String.format(format, bytesToKiB(bytes)) + "KB"; + } else if (bytes < BYTES_TO_GiB) { + return String.format(format, bytesToMiB(bytes)) + "MB"; + } else { + return String.format(format, bytesToGiB(bytes)) + "GB"; + } + } + + static double bytesToKiB(long bytes) { + return bytes / BYTES_TO_KiB; + } + + static double bytesToGiB(long bytes) { + return bytes / BYTES_TO_GiB; + } + + static double bytesToMiB(long bytes) { + return bytes / BYTES_TO_MiB; + } +} diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ProgressReporter.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ProgressReporter.java index eb3737b8f62f..5f7296290547 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ProgressReporter.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ProgressReporter.java @@ -267,7 +267,7 @@ public void printInitializeEnd() { String gcName = Heap.getHeap().getGC().getName(); recordJsonMetric(GeneralInfo.GC, gcName); long maxHeapSize = SubstrateGCOptions.MaxHeapSize.getValue(); - String maxHeapValue = maxHeapSize == 0 ? "unlimited" : Utils.bytesToHuman(maxHeapSize); + String maxHeapValue = maxHeapSize == 0 ? "unlimited" : ByteFormattingUtil.bytesToHuman(maxHeapSize); l().a(" ").doclink("Garbage collector", "#glossary-gc").a(": ").a(gcName).a(" (").doclink("max heap size", "#glossary-gc-max-heap-size").a(": ").a(maxHeapValue).a(")").println(); } @@ -414,16 +414,17 @@ public void printCreationEnd(int imageFileSize, int numHeapObjects, long imageHe stagePrinter.end(imageTimer.getTotalTime() + writeTimer.getTotalTime()); creationStageEndCompleted = true; String format = "%9s (%5.2f%%) for "; - l().a(format, Utils.bytesToHuman(codeAreaSize), codeAreaSize / (double) imageFileSize * 100) + l().a(format, ByteFormattingUtil.bytesToHuman(codeAreaSize), codeAreaSize / (double) imageFileSize * 100) .doclink("code area", "#glossary-code-area").a(":%,10d compilation units", numCompilations).println(); EconomicMap, ResourceStorageEntry> resources = Resources.singleton().resources(); int numResources = resources.size(); recordJsonMetric(ImageDetailKey.IMAGE_HEAP_RESOURCE_COUNT, numResources); - l().a(format, Utils.bytesToHuman(imageHeapSize), imageHeapSize / (double) imageFileSize * 100) + l().a(format, ByteFormattingUtil.bytesToHuman(imageHeapSize), imageHeapSize / (double) imageFileSize * 100) .doclink("image heap", "#glossary-image-heap").a(":%,9d objects and %,d resources", numHeapObjects, numResources).println(); if (debugInfoSize > 0) { recordJsonMetric(ImageDetailKey.DEBUG_INFO_SIZE, debugInfoSize); // Optional metric - DirectPrinter l = l().a(format, Utils.bytesToHuman(debugInfoSize), debugInfoSize / (double) imageFileSize * 100) + DirectPrinter l = l().a(format, ByteFormattingUtil.bytesToHuman(debugInfoSize), debugInfoSize / (double) imageFileSize * 100) + .doclink("debug info", "#glossary-debug-info"); if (debugInfoTimer != null) { l.a(" generated in %.1fs", Utils.millisToSeconds(debugInfoTimer.getTotalTime())); @@ -435,9 +436,9 @@ public void printCreationEnd(int imageFileSize, int numHeapObjects, long imageHe recordJsonMetric(ImageDetailKey.TOTAL_SIZE, imageFileSize); recordJsonMetric(ImageDetailKey.CODE_AREA_SIZE, codeAreaSize); recordJsonMetric(ImageDetailKey.NUM_COMP_UNITS, numCompilations); - l().a(format, Utils.bytesToHuman(otherBytes), otherBytes / (double) imageFileSize * 100) + l().a(format, ByteFormattingUtil.bytesToHuman(otherBytes), otherBytes / (double) imageFileSize * 100) .doclink("other data", "#glossary-other-data").println(); - l().a("%9s in total", Utils.bytesToHuman(imageFileSize)).println(); + l().a("%9s in total", ByteFormattingUtil.bytesToHuman(imageFileSize)).println(); printBreakdowns(); } @@ -568,7 +569,7 @@ private void printBreakdowns() { if (packagesBySize.hasNext()) { Entry e = packagesBySize.next(); String className = Utils.truncateClassOrPackageName(e.getKey()); - codeSizePart = String.format("%9s %s", Utils.bytesToHuman(e.getValue()), className); + codeSizePart = String.format("%9s %s", ByteFormattingUtil.bytesToHuman(e.getValue()), className); printedCodeBytes += e.getValue(); printedCodeItems++; } @@ -581,7 +582,7 @@ private void printBreakdowns() { if (!className.startsWith(BREAKDOWN_BYTE_ARRAY_PREFIX)) { className = Utils.truncateClassOrPackageName(className); } - heapSizePart = String.format("%9s %s", Utils.bytesToHuman(e.getValue()), className); + heapSizePart = String.format("%9s %s", ByteFormattingUtil.bytesToHuman(e.getValue()), className); printedHeapBytes += e.getValue(); printedHeapItems++; } @@ -595,9 +596,10 @@ private void printBreakdowns() { int numHeapItems = heapBreakdown.size(); long totalCodeBytes = codeBreakdown.values().stream().collect(Collectors.summingLong(Long::longValue)); long totalHeapBytes = heapBreakdown.values().stream().collect(Collectors.summingLong(Long::longValue)); - p.l().a(String.format("%9s for %s more ", Utils.bytesToHuman(totalCodeBytes - printedCodeBytes), numCodeItems - printedCodeItems)).doclink("origins", "#glossary-code-area-origins") + + p.l().a(String.format("%9s for %s more packages", ByteFormattingUtil.bytesToHuman(totalCodeBytes - printedCodeBytes), numCodeItems - printedCodeItems)) .jumpToMiddle() - .a(String.format("%9s for %s more object types", Utils.bytesToHuman(totalHeapBytes - printedHeapBytes), numHeapItems - printedHeapItems)).flushln(); + .a(String.format("%9s for %s more object types", ByteFormattingUtil.bytesToHuman(totalHeapBytes - printedHeapBytes), numHeapItems - printedHeapItems)).flushln(); } public void printEpilog(Optional optionalImageName, Optional optionalGenerator, ImageClassLoader classLoader, Optional optionalError, @@ -702,11 +704,11 @@ private static Path reportImageBuildStatistics(String imageName, BigBang bb) { String description = "image build statistics"; if (ImageBuildStatistics.Options.ImageBuildStatisticsFile.hasBeenSet(bb.getOptions())) { final File file = new File(ImageBuildStatistics.Options.ImageBuildStatisticsFile.getValue(bb.getOptions())); - return ReportUtils.report(description, file.getAbsoluteFile().toPath(), statsReporter, false); + return com.oracle.graal.pointsto.reports.ReportUtils.report(description, file.getAbsoluteFile().toPath(), statsReporter, false); } else { - String name = "image_build_statistics_" + ReportUtils.extractImageName(imageName); + String name = "image_build_statistics_" + com.oracle.graal.pointsto.reports.ReportUtils.extractImageName(imageName); String path = SubstrateOptions.Path.getValue() + File.separatorChar + "reports"; - return ReportUtils.report(description, path, name, "json", statsReporter, false); + return com.oracle.graal.pointsto.reports.ReportUtils.report(description, path, name, "json", statsReporter, false); } } @@ -721,7 +723,7 @@ private void printResourceStatistics() { .doclink("GCs", "#glossary-garbage-collections"); long peakRSS = ProgressReporterCHelper.getPeakRSS(); if (peakRSS >= 0) { - p.a(" | ").doclink("Peak RSS", "#glossary-peak-rss").a(": ").a("%.2fGB", Utils.bytesToGiB(peakRSS)); + p.a(" | ").doclink("Peak RSS", "#glossary-peak-rss").a(": ").a("%.2fGB", ByteFormattingUtil.bytesToGiB(peakRSS)); } recordJsonMetric(ResourceUsageKey.PEAK_RSS, (peakRSS >= 0 ? peakRSS : UNAVAILABLE_METRIC)); OperatingSystemMXBean osMXBean = ManagementFactory.getOperatingSystemMXBean(); @@ -747,7 +749,7 @@ private void checkForExcessiveGarbageCollection() { .a(": %.1fs spent in %d GCs during the last stage, taking up %.2f%% of the time.", Utils.millisToSeconds(gcTimeDeltaMillis), currentGCStats.totalCount - lastGCStats.totalCount, ratio * 100) .println(); - l().a(" Please ensure more than %.2fGB of memory is available for Native Image", Utils.bytesToGiB(ProgressReporterCHelper.getPeakRSS())).println(); + l().a(" Please ensure more than %.2fGB of memory is available for Native Image", ByteFormattingUtil.bytesToGiB(ProgressReporterCHelper.getPeakRSS())).println(); l().a(" to reduce GC overhead and improve image build time.").println(); } lastGCStats = currentGCStats; @@ -770,40 +772,8 @@ private static Timer getTimer(TimerCollection.Registry type) { private static class Utils { private static final double MILLIS_TO_SECONDS = 1000d; private static final double NANOS_TO_SECONDS = 1000d * 1000d * 1000d; - private static final double BYTES_TO_KiB = 1024d; - private static final double BYTES_TO_MiB = 1024d * 1024d; - private static final double BYTES_TO_GiB = 1024d * 1024d * 1024d; - private static final Field STRING_VALUE = ReflectionUtil.lookupField(String.class, "value"); - private static String bytesToHuman(long bytes) { - return bytesToHuman("%4.2f", bytes); - } - - private static String bytesToHuman(String format, long bytes) { - if (bytes < BYTES_TO_KiB) { - return String.format(format, (double) bytes) + "B"; - } else if (bytes < BYTES_TO_MiB) { - return String.format(format, bytesToKiB(bytes)) + "KB"; - } else if (bytes < BYTES_TO_GiB) { - return String.format(format, bytesToMiB(bytes)) + "MB"; - } else { - return String.format(format, bytesToGiB(bytes)) + "GB"; - } - } - - private static double bytesToKiB(long bytes) { - return bytes / BYTES_TO_KiB; - } - - private static double bytesToGiB(long bytes) { - return bytes / BYTES_TO_GiB; - } - - private static double bytesToMiB(long bytes) { - return bytes / BYTES_TO_MiB; - } - private static double millisToSeconds(double millis) { return millis / MILLIS_TO_SECONDS; } @@ -821,7 +791,7 @@ private static int getInternalByteArrayLength(String string) { } private static double getUsedMemory() { - return bytesToGiB(Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()); + return ByteFormattingUtil.bytesToGiB(Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()); } private static String stringFilledWith(int size, String fill) { @@ -862,6 +832,10 @@ private static String truncateClassOrPackageName(String classOrPackageName) { } } + private static void resetANSIMode() { + NativeImageSystemIOWrappers.singleton().getOut().print(ANSI.RESET); + } + private static class GCStats { private final long totalCount; private final long totalTimeMillis; diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/HeapHistogram.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/HeapHistogram.java index 9143ce4fc8b0..85c36e83262f 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/HeapHistogram.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/HeapHistogram.java @@ -24,6 +24,7 @@ */ package com.oracle.svm.hosted.image; +import java.io.PrintWriter; import java.util.Arrays; import java.util.Comparator; import java.util.HashMap; @@ -39,6 +40,7 @@ public class HeapHistogram { protected static boolean PrintStrings = false; private final Map data = new HashMap<>(); + private final PrintWriter out; static class HistogramEntry { protected final HostedClass clazz; @@ -50,6 +52,14 @@ static class HistogramEntry { } } + public HeapHistogram() { + this.out = new PrintWriter(System.out); + } + + public HeapHistogram(PrintWriter out) { + this.out = out; + } + private static final Comparator SIZE_COMPARATOR = (o1, o2) -> { // Larger sizes first int result = Long.compare(o2.size, o1.size); @@ -77,18 +87,18 @@ public void add(ObjectInfo objectInfo, long size) { entry.size += size; if (PrintStrings && objectInfo.getObject() instanceof String) { - String reason = String.valueOf(objectInfo.reason); + String reason = String.valueOf(objectInfo.getMainReason()); String value = ((String) objectInfo.getObject()).replace("\n", ""); if (!reason.startsWith("com.oracle.svm.core.hub.DynamicHub")) { - System.out.format("%120s ::: %s\n", value, reason); + out.format("%120s ::: %s\n", value, reason); } } } public void printHeadings(final String title) { assert NativeImageOptions.PrintHeapHistogram.getValue(); - System.out.format("\n%s\n", title); - System.out.format(headerFormat, "Count", "Size", "Size%", "Cum%", "Class"); + out.format("%s\n", title); + out.format(headerFormat, "Count", "Size", "Size%", "Cum%", "Class"); } public void print() { @@ -101,7 +111,7 @@ public void print() { long printedSize = 0; for (HistogramEntry entry : entries) { printedSize += entry.size; - System.out.format(entryFormat, entry.count, entry.size, entry.size * 100d / totalSize, printedSize * 100d / totalSize, entry.clazz.toJavaName()); + out.format(entryFormat, entry.count, entry.size, entry.size * 100d / totalSize, printedSize * 100d / totalSize, entry.clazz.toJavaName()); } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/ImageHeapConnectedComponentsFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/ImageHeapConnectedComponentsFeature.java new file mode 100644 index 000000000000..839041d5fe6a --- /dev/null +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/ImageHeapConnectedComponentsFeature.java @@ -0,0 +1,83 @@ +/* + * 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.image; + +import com.oracle.graal.pointsto.reports.ReportUtils; +import com.oracle.svm.core.SubstrateOptions; +import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature; +import com.oracle.svm.core.feature.InternalFeature; +import com.oracle.svm.core.option.HostedOptionKey; +import com.oracle.svm.hosted.FeatureImpl; +import org.graalvm.compiler.options.Option; +import org.graalvm.compiler.options.OptionType; + +import java.io.File; +import java.io.PrintWriter; +import java.nio.file.Path; +import java.util.function.Consumer; + +@AutomaticallyRegisteredFeature +public class ImageHeapConnectedComponentsFeature implements InternalFeature { + public static class Options { + @Option(help = {"file:doc-files/PrintImageHeapConnectedComponents.md"}, type = OptionType.Debug)// + static final HostedOptionKey PrintImageHeapConnectedComponents = new HostedOptionKey<>(false); + } + + private AbstractImage image; + private NativeImageHeap heap; + + @Override + public boolean isInConfiguration(IsInConfigurationAccess access) { + return Options.PrintImageHeapConnectedComponents.getValue(); + } + + @Override + public void afterHeapLayout(AfterHeapLayoutAccess a) { + FeatureImpl.AfterHeapLayoutAccessImpl access = (FeatureImpl.AfterHeapLayoutAccessImpl) a; + this.heap = access.getHeap(); + } + + @Override + public void beforeImageWrite(BeforeImageWriteAccess access) { + this.image = ((FeatureImpl.BeforeImageWriteAccessImpl) access).getImage(); + } + + @Override + public void afterImageWrite(AfterImageWriteAccess a) { + FeatureImpl.AfterImageWriteAccessImpl access = (FeatureImpl.AfterImageWriteAccessImpl) a; + Path imagePath = access.getImagePath().getFileName(); + String imageName = imagePath != null ? imagePath.toString() : "native-image"; + ImageHeapConnectedComponentsPrinter printer = new ImageHeapConnectedComponentsPrinter(heap, access.getUniverse().getBigBang(), image, imageName); + printReport("connected_components_" + imageName, "txt", printer::printConnectedComponentsObjectHistogramReport); + printReport("summary_info_for_every_object_in_connected_components_" + imageName, "json", printer::printSummaryInfoForEveryObjectInConnectedComponents); + printReport("access_points_for_connected_components_" + imageName, "json", printer::printAccessPointsForConnectedComponents); + heap.objectReachabilityInfo.clear(); + } + + private static void printReport(String reportName, String extension, Consumer writer) { + File file = ReportUtils.reportFile(SubstrateOptions.reportsPath(), reportName, extension); + ReportUtils.report(reportName, file.toPath(), writer); + } +} diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/ImageHeapConnectedComponentsPrinter.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/ImageHeapConnectedComponentsPrinter.java new file mode 100644 index 000000000000..f776af63d82c --- /dev/null +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/ImageHeapConnectedComponentsPrinter.java @@ -0,0 +1,570 @@ +/* + * 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.image; + +import java.io.BufferedWriter; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.EnumMap; +import java.util.IdentityHashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; + +import org.graalvm.collections.EconomicMap; +import org.graalvm.collections.Pair; + +import com.oracle.graal.pointsto.BigBang; +import com.oracle.svm.core.jdk.Resources; +import com.oracle.svm.core.jdk.resources.ResourceStorageEntry; +import com.oracle.svm.core.util.VMError; +import com.oracle.svm.core.util.json.JsonWriter; +import com.oracle.svm.hosted.ByteFormattingUtil; +import com.oracle.svm.hosted.image.NativeImageHeap.HeapInclusionReason; +import com.oracle.svm.hosted.image.NativeImageHeap.ObjectInfo; +import com.oracle.svm.hosted.image.NativeImageHeap.ObjectReachabilityGroup; +import com.oracle.svm.hosted.image.NativeImageHeap.ObjectReachabilityInfo; +import com.oracle.svm.hosted.meta.HostedField; + +import jdk.vm.ci.meta.JavaConstant; +import jdk.vm.ci.meta.JavaKind; + +public class ImageHeapConnectedComponentsPrinter { + private final NativeImageHeap heap; + private final long totalHeapSizeInBytes; + private final List connectedComponents; + private final BigBang bb; + private final String imageName; + private final EnumMap groups; + + private static class GroupEntry { + final Set objects; + long sizeInBytes; + + GroupEntry() { + this.objects = Collections.newSetFromMap(new IdentityHashMap<>()); + } + + void addObject(ObjectInfo object) { + objects.add(object); + this.sizeInBytes += object.getSize(); + } + } + + public ImageHeapConnectedComponentsPrinter(NativeImageHeap heap, BigBang bigBang, AbstractImage image, String imageName) { + this.heap = heap; + this.imageName = imageName; + this.totalHeapSizeInBytes = image.getImageHeapSize(); + this.bb = bigBang; + this.groups = groupObjectsByReachability(heap); + + ObjectInfoGraph objectInfoGraph = constructGraph(groups.get(ObjectReachabilityGroup.MethodOrStaticField).objects); + this.connectedComponents = computeConnectedComponents(objectInfoGraph); + } + + private static boolean shouldIncludeObjectInTheReport(ObjectInfo objectInfo) { + return !objectInfo.getMainReason().equals(HeapInclusionReason.FillerObject); + } + + /** + * Objects can be reachable from multiple object reachability groups. When generating a report + * we `claim` objects in the order specified below. Object can only appear (i.e. be claimed) by + * exactly one group. We first remove Resources, InternedStringsTable, DynamicHubs and + * ImageCodeInfo, whatever objects are left were added because a static field references them or + * a method accesses that object as a constant. We then construct a objectInfoGraph of objects + * that belong to {@link ObjectReachabilityGroup#MethodOrStaticField} and compute the connected + * components of that objectInfoGraph. + */ + private static EnumMap groupObjectsByReachability(NativeImageHeap heap) { + ObjectReachabilityGroup[] objectReachabilityGroupOrder = { + ObjectReachabilityGroup.Resources, + ObjectReachabilityGroup.DynamicHubs, + ObjectReachabilityGroup.ImageCodeInfo, + ObjectReachabilityGroup.MethodOrStaticField + }; + + markResources(heap); + EnumMap groups = new EnumMap<>(ObjectReachabilityGroup.class); + for (ObjectReachabilityGroup group : objectReachabilityGroupOrder) { + groups.put(group, new GroupEntry()); + } + Set objects = Collections.newSetFromMap(new IdentityHashMap<>()); + heap.getObjects().stream() + .filter(ImageHeapConnectedComponentsPrinter::shouldIncludeObjectInTheReport) + .forEach(objects::add); + + /* + * The interned strings table is treated specially because we don't want all interned + * strings to be merged into a single component, that's why we remove it first and then + * compute connected components. + */ + Optional internedStringsTable = objects.stream().filter(o -> o.getMainReason().equals(HeapInclusionReason.InternedStringsTable)).findFirst(); + if (internedStringsTable.isPresent()) { + GroupEntry entry = new GroupEntry(); + entry.addObject(internedStringsTable.get()); + groups.put(ObjectReachabilityGroup.InternedStringsTable, entry); + objects.remove(internedStringsTable.get()); + } + + for (ObjectReachabilityGroup group : objectReachabilityGroupOrder) { + for (ObjectInfo object : objects) { + ObjectReachabilityInfo reachabilityInfo = heap.objectReachabilityInfo.get(object); + if (reachabilityInfo.objectReachableFrom(group)) { + groups.get(group).addObject(object); + } + } + objects.removeAll(groups.get(group).objects); + } + return groups; + } + + private static void markResources(NativeImageHeap heap) { + EconomicMap, ResourceStorageEntry> resources = Resources.singleton().resources(); + for (ResourceStorageEntry value : resources.getValues()) { + for (byte[] arr : value.getData()) { + ObjectInfo info = heap.getObjectInfo(arr); + if (info != null) { + heap.objectReachabilityInfo.get(info).addReason(HeapInclusionReason.Resource); + } + } + } + } + + private ObjectInfoGraph constructGraph(Set objects) { + ObjectInfoGraph objectInfoGraph = new ObjectInfoGraph(); + for (ObjectInfo objectInfo : objects) { + objectInfoGraph.addNode(objectInfo); + ObjectReachabilityInfo reachabilityInfo = heap.objectReachabilityInfo.get(objectInfo); + for (Object referencesToThisObject : reachabilityInfo.getAllReasons()) { + if (referencesToThisObject instanceof ObjectInfo && objects.contains(referencesToThisObject)) { + objectInfoGraph.connect((ObjectInfo) referencesToThisObject, objectInfo); + } + } + } + return objectInfoGraph; + } + + public void printAccessPointsForConnectedComponents(PrintWriter out) { + try (JsonWriter writer = new JsonWriter(new BufferedWriter(out))) { + writer.append('[').newline(); + for (Iterator connectedComponentIterator = connectedComponents.iterator(); connectedComponentIterator.hasNext();) { + ConnectedComponent connectedComponent = connectedComponentIterator.next(); + writer.append('{').newline(); + writer.quote("componentId").append(':').append(String.valueOf(connectedComponent.getId())).append(',').newline(); + + writer.quote("methods").append(":[").newline(); + for (Iterator methodIterator = getMethodAccesses(connectedComponent.getObjects()).iterator(); methodIterator.hasNext();) { + String methodAccess = methodIterator.next(); + writer.quote(methodAccess); + if (methodIterator.hasNext()) { + writer.append(',').newline(); + } + } + writer.append("],").newline(); + + writer.quote("staticFields").append(":["); + for (Iterator staticFieldIterator = getHostedFieldsAccess(connectedComponent.getObjects()).iterator(); staticFieldIterator.hasNext();) { + String hostedFieldsAccess = staticFieldIterator.next(); + writer.quote(hostedFieldsAccess); + if (staticFieldIterator.hasNext()) { + writer.append(',').newline(); + } + } + writer.append(']').newline(); + writer.append('}'); + if (connectedComponentIterator.hasNext()) { + writer.append(',').newline(); + } + } + writer.append(']').newline(); + } catch (IOException e) { + out.write("{\"Error\":\"Failed to generate the report\""); + } + } + + public void printSummaryInfoForEveryObjectInConnectedComponents(PrintWriter out) { + try (JsonWriter writer = new JsonWriter(new BufferedWriter(out))) { + writer.append('{').newline(); + writer.quote("connectedComponents").append(":[").newline(); + for (Iterator iterator = connectedComponents.iterator(); iterator.hasNext();) { + ConnectedComponent connectedComponent = iterator.next(); + writer.append('{').newline(); + writer.quote("componentId").append(':').quote(connectedComponent.getId()).append(',').newline(); + writer.quote("sizeInBytes").append(':').append(String.valueOf(connectedComponent.getSizeInBytes())).append(',').newline(); + writer.quote("objects").append(":["); + List objects = connectedComponent.getObjects(); + printObjectsToJson(writer, objects); + writer.append(']').newline(); + writer.append('}'); + if (iterator.hasNext()) { + writer.append(',').newline(); + } + } + writer.newline().append("],").newline(); + + ObjectReachabilityGroup[] groupsToPrint = { + ObjectReachabilityGroup.DynamicHubs, + ObjectReachabilityGroup.ImageCodeInfo, + ObjectReachabilityGroup.InternedStringsTable, + ObjectReachabilityGroup.Resources + }; + + for (int i = 0; i < groupsToPrint.length; i++) { + ObjectReachabilityGroup group = groupsToPrint[i]; + writer.quote(group.name).append(":{").newline(); + writer.quote("sizeInBytes").append(':').append(String.valueOf(groups.get(group).sizeInBytes)).append(',').newline(); + writer.quote("objects").append(":[").newline(); + printObjectsToJson(writer, groups.get(group).objects); + writer.append(']').newline().append('}'); + if (i != groupsToPrint.length - 1) { + writer.append(',').newline(); + } + } + writer.newline().append('}').newline(); + } catch (IOException e) { + out.write("{\"Error\":\"Failed to generate the report\""); + } + } + + public void printConnectedComponentsObjectHistogramReport(PrintWriter out) { + String title = "Native image heap connected components report"; + out.println(fillHeading(title)); + out.println(fillHeading(imageName)); + out.printf("Total Heap Size: %s\n", ByteFormattingUtil.bytesToHuman(totalHeapSizeInBytes)); + ObjectReachabilityGroup[] headerGroups = { + ObjectReachabilityGroup.ImageCodeInfo, + ObjectReachabilityGroup.DynamicHubs, + ObjectReachabilityGroup.InternedStringsTable, + ObjectReachabilityGroup.Resources, + }; + long totalHeaderGroupSize = 0; + for (ObjectReachabilityGroup headerGroup : headerGroups) { + long groupSize = groups.get(headerGroup).sizeInBytes; + out.printf("\t%s size: %s\n", headerGroup.description, ByteFormattingUtil.bytesToHuman(groupSize)); + totalHeaderGroupSize += groupSize; + } + out.printf("\tIn connected components report: %s\n", ByteFormattingUtil.bytesToHuman(totalHeapSizeInBytes - totalHeaderGroupSize)); + out.printf("Total number of objects in the heap: %d\n", this.heap.getObjects().size()); + out.printf("Number of connected components in the report %d", this.connectedComponents.size()); + for (int i = 0; i < connectedComponents.size(); i++) { + ConnectedComponent connectedComponent = connectedComponents.get(i); + float percentageOfTotalHeapSize = 100.0f * connectedComponent.getSizeInBytes() / + this.totalHeapSizeInBytes; + HeapHistogram objectHistogram = new HeapHistogram(out); + connectedComponent.getObjects().forEach(o -> objectHistogram.add(o, o.getSize())); + String headingInfo = String.format("ComponentId=%d | Size=%s | Percentage of total image heap size=%.4f%%", i, + ByteFormattingUtil.bytesToHuman(connectedComponent.getSizeInBytes()), + percentageOfTotalHeapSize); + + out.println(); + String fullHeading = fillHeading(headingInfo); + objectHistogram.printHeadings(String.format("%s\n%s", "=".repeat(fullHeading.length()), fullHeading)); + objectHistogram.print(); + + Collection roots = connectedComponent.getObjects(); + Collection methods = getMethodAccesses(roots); + Collection staticFields = getHostedFieldsAccess(roots); + int entryPointLimit = 10; + if (!staticFields.isEmpty()) { + out.printf("\nStatic fields accessing component %d:\n", i); + for (String field : staticFields.stream().limit(entryPointLimit).collect(Collectors.toList())) { + out.printf("\t%s\n", field); + } + if (staticFields.size() > entryPointLimit) { + out.printf("\t... %d more in the access_points report\n", staticFields.size() - entryPointLimit); + } + } + if (!methods.isEmpty()) { + out.printf("\nMethods accessing connected component %d:\n", i); + for (String methodName : methods.stream().limit(entryPointLimit).collect(Collectors.toList())) { + out.printf("\t%s\n", formatMethodAsLink(methodName)); + } + if (methods.size() > entryPointLimit) { + out.printf("\t... %d more in the access_points report\n", methods.size() - entryPointLimit); + } + } + } + + for (ObjectReachabilityGroup groupType : headerGroups) { + HeapHistogram objectHistogram = new HeapHistogram(out); + GroupEntry groupEntry = groups.get(groupType); + groupEntry.objects.forEach(o -> objectHistogram.add(o, o.getSize())); + float percentageOfTotalHeapSize = 100.0f * groupEntry.sizeInBytes / this.totalHeapSizeInBytes; + String headingInfo = String.format("Group=%s | Size=%s | Percentage of total image heap size=%.4f%%", groupType, + ByteFormattingUtil.bytesToHuman(groups.get(groupType).sizeInBytes), + percentageOfTotalHeapSize); + out.println(); + String fullHeading = fillHeading(headingInfo); + objectHistogram.printHeadings(String.format("%s\n%s", "=".repeat(fullHeading.length()), fullHeading)); + objectHistogram.print(); + } + } + + private static final int HEADING_WIDTH = 140; + + private static String fillHeading(String title) { + String fill = "=".repeat(Math.max(HEADING_WIDTH - title.length(), 8) / 2); + return String.format("%s %s %s%s", fill, title, fill, title.length() % 2 == 0 ? "" : "="); + } + + private Collection getMethodAccesses(Collection objects) { + List methods = new ArrayList<>(); + for (ObjectInfo object : objects) { + ObjectReachabilityInfo reachabilityInfo = heap.objectReachabilityInfo.get(object); + for (Object reason : reachabilityInfo.getAllReasons()) { + if (reason instanceof String) { + methods.add((String) reason); + } + } + } + return methods; + } + + private static String formatMethodAsLink(String method) { + int lastDot = method.lastIndexOf("."); + if (lastDot != -1) { + return method.substring(0, lastDot) + '#' + method.substring(lastDot + 1); + } else { + return method; + } + } + + private Collection getHostedFieldsAccess(Collection objects) { + List hostedFields = new ArrayList<>(); + for (ObjectInfo object : objects) { + ObjectReachabilityInfo reachabilityInfo = heap.objectReachabilityInfo.get(object); + for (Object reason : reachabilityInfo.getAllReasons()) { + if (reason instanceof HostedField) { + HostedField field = (HostedField) reason; + hostedFields.add(field.format("%H#%n")); + } + } + } + return hostedFields; + } + + private void printObjectsToJson(JsonWriter writer, Collection objects) throws IOException { + for (Iterator iterator = objects.iterator(); iterator.hasNext();) { + ObjectInfo objectInfo = iterator.next(); + writer.append('{'); + writer.quote("className").append(':').quote(objectInfo.getObject().getClass().getName()).append(','); + writer.quote("identityHashCode").append(':').quote(String.valueOf(objectInfo.getIdentityHashCode())).append(','); + writer.quote("constantValue").append(':').quote(constantAsRawString(bb, objectInfo.getConstant())).append(','); + printReasonToJson(objectInfo.getMainReason(), writer); + writer.append('}'); + if (iterator.hasNext()) { + writer.append(',').newline(); + } + } + } + + private static void printReasonToJson(Object reason, JsonWriter writer) throws IOException { + String kind = null; + String value = null; + if (reason instanceof String) { + kind = "method"; + value = reason.toString(); + } else if (reason instanceof ObjectInfo) { + ObjectInfo r = (ObjectInfo) reason; + kind = "object"; + value = String.valueOf(r.getIdentityHashCode()); + } else if (reason instanceof HostedField) { + HostedField r = (HostedField) reason; + kind = "staticField"; + value = r.format("%H#%n"); + } else if (reason instanceof HeapInclusionReason) { + kind = "svmInternal"; + value = reason.toString(); + } else { + VMError.shouldNotReachHere("Unhandled type"); + } + + writer.quote("reason").append(":{"); + writer.quote("kind").append(':').quote(kind).append(','); + writer.quote("value").append(':').quote(value); + writer.append('}'); + } + + private static Object constantAsObject(BigBang bb, JavaConstant constant) { + return bb.getSnippetReflectionProvider().asObject(Object.class, constant); + } + + private static String constantAsRawString(BigBang bb, JavaConstant constant) { + Object object = constantAsObject(bb, constant); + if (object instanceof String) { + return (String) object; + } else { + return JavaKind.Object.format(object); + } + } + + /** + * Do DFS visit of a graph from a start node and add all reachable objects into a single + * {@link ConnectedComponent}. Returns a List sorted by the size in bytes in + * descending order + */ + private static List computeConnectedComponents(ObjectInfoGraph objectInfoGraph) { + ArrayList result = new ArrayList<>(); + boolean[] visited = new boolean[objectInfoGraph.getNumberOfNodes()]; + ArrayList stack = new ArrayList<>(); + + for (ObjectInfo start : objectInfoGraph.getNodesSet()) { + if (visited[objectInfoGraph.getNodeId(start)]) { + continue; + } + ConnectedComponent currentConnectedComponent = new ConnectedComponent(); + stack.add(start); + while (!stack.isEmpty()) { + ObjectInfo currentObject = stack.remove(stack.size() - 1); + int currentNodeId = objectInfoGraph.getNodeId(currentObject); + if (visited[currentNodeId]) { + continue; + } + visited[currentNodeId] = true; + currentConnectedComponent.addObject(currentObject); + for (ObjectInfo neighbour : objectInfoGraph.getNeighbours(currentObject)) { + if (!visited[objectInfoGraph.getNodeId(neighbour)]) { + stack.add(neighbour); + } + } + } + result.add(currentConnectedComponent); + } + result.sort(Comparator.comparing(ConnectedComponent::getSizeInBytes).reversed()); + + int id = 0; + for (ConnectedComponent connectedComponent : result) { + connectedComponent.setId(id++); + } + return result; + } + + static final class ConnectedComponent { + int id = -1; + private final List objects; + private long sizeInBytes; + + ConnectedComponent() { + this.objects = new ArrayList<>(); + this.sizeInBytes = 0; + } + + public void addObject(ObjectInfo object) { + this.objects.add(object); + this.sizeInBytes += object.getSize(); + } + + public long getSizeInBytes() { + return sizeInBytes; + } + + public List getObjects() { + return objects; + } + + void setId(int id) { + this.id = id; + } + + int getId() { + return id; + } + } +} + +/* Undirected graph of objects in a native image heap */ +class ObjectInfoGraph { + protected final Map nodes = new IdentityHashMap<>(); + + private void doConnect(ObjectInfo from, ObjectInfo to) { + if (from == null || to == null) { + throw VMError.shouldNotReachHere("Trying to connect null"); + } + NodeData fromNodeData = addNode(from); + addNode(to); + fromNodeData.getNeighbours().add(to); + } + + void connect(ObjectInfo a, ObjectInfo b) { + doConnect(a, b); + doConnect(b, a); + } + + NodeData addNode(ObjectInfo a) { + if (nodes.containsKey(a)) { + return nodes.get(a); + } + return nodes.computeIfAbsent(a, objectInfo -> new NodeData(nodes.size())); + } + + Set getNodesSet() { + return nodes.keySet(); + } + + int getNodeId(ObjectInfo objectInfo) { + NodeData nodeData = nodes.get(objectInfo); + if (nodeData == null) { + return -1; + } + return nodeData.getNodeId(); + } + + Set getNeighbours(ObjectInfo a) { + NodeData nodeData = nodes.get(a); + if (nodeData == null) { + return Collections.emptySet(); + } + return nodeData.getNeighbours(); + } + + int getNumberOfNodes() { + return nodes.size(); + } + + private static final class NodeData { + private final Set neighbours; + private final int nodeId; + + private NodeData(int nodeId) { + this.neighbours = Collections.newSetFromMap(new IdentityHashMap<>()); + this.nodeId = nodeId; + } + + private Set getNeighbours() { + return neighbours; + } + + private int getNodeId() { + return nodeId; + } + } +} diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageCodeCache.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageCodeCache.java index 07cc26065324..26307d24f222 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageCodeCache.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageCodeCache.java @@ -200,7 +200,7 @@ public void addConstantsToHeap() { for (DataSection.Data data : dataSection) { if (data instanceof SubstrateDataBuilder.ObjectData) { JavaConstant constant = ((SubstrateDataBuilder.ObjectData) data).getConstant(); - addConstantToHeap(constant, "data section"); + addConstantToHeap(constant, NativeImageHeap.HeapInclusionReason.DataSection); } } for (Pair pair : getOrderedCompilations()) { diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageHeap.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageHeap.java index 056c0a95879e..32aed8a2dcdd 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageHeap.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageHeap.java @@ -35,6 +35,7 @@ import java.util.Deque; import java.util.HashMap; import java.util.IdentityHashMap; +import java.util.LinkedHashSet; import java.util.Map; import java.util.Optional; import java.util.Set; @@ -55,11 +56,13 @@ import com.oracle.graal.pointsto.util.AnalysisError; import com.oracle.svm.core.StaticFieldsSupport; import com.oracle.svm.core.SubstrateOptions; +import com.oracle.svm.core.code.ImageCodeInfo; import com.oracle.svm.core.config.ConfigurationValues; import com.oracle.svm.core.config.ObjectLayout; import com.oracle.svm.core.heap.FillerObject; import com.oracle.svm.core.heap.Heap; import com.oracle.svm.core.hub.DynamicHub; +import com.oracle.svm.core.hub.DynamicHubCompanion; import com.oracle.svm.core.hub.LayoutEncoding; import com.oracle.svm.core.image.ImageHeap; import com.oracle.svm.core.image.ImageHeapLayouter; @@ -124,6 +127,9 @@ public final class NativeImageHeap implements ImageHeap { /** Objects that are known to be immutable in the native image heap. */ private final Set knownImmutableObjects = Collections.newSetFromMap(new IdentityHashMap<>()); + /** For diagnostic purpose only. */ + Map objectReachabilityInfo = null; + public NativeImageHeap(AnalysisUniverse aUniverse, HostedUniverse universe, HostedMetaAccess metaAccess, ImageHeapLayouter heapLayouter) { this.aUniverse = aUniverse; this.universe = universe; @@ -134,6 +140,9 @@ public NativeImageHeap(AnalysisUniverse aUniverse, HostedUniverse universe, Host this.minInstanceSize = objectLayout.getMinimumInstanceObjectSize(); this.minArraySize = objectLayout.getMinimumArraySize(); + if (ImageHeapConnectedComponentsFeature.Options.PrintImageHeapConnectedComponents.getValue()) { + this.objectReachabilityInfo = new IdentityHashMap<>(); + } assert assertFillerObjectSizes(); } @@ -211,7 +220,7 @@ public void addTrailingObjects() { /* * Ensure that the hub of the String[] array (used for the interned objects) is written. */ - addObject(getMetaAccess().lookupJavaType(String[].class).getHub(), false, "internedStrings table"); + addObject(getMetaAccess().lookupJavaType(String[].class).getHub(), false, HeapInclusionReason.InternedStringsTable); /* * We are no longer allowed to add new interned strings, because that would modify the * table we are about to write. @@ -224,8 +233,7 @@ public void addTrailingObjects() { String[] imageInternedStrings = internedStrings.keySet().toArray(new String[0]); Arrays.sort(imageInternedStrings); ImageSingletons.lookup(StringInternSupport.class).setImageInternedStrings(imageInternedStrings); - - addObject(imageInternedStrings, true, "internedStrings table"); + addObject(imageInternedStrings, true, HeapInclusionReason.InternedStringsTable); // Process any objects that were transitively added to the heap. processAddObjectWorklist(); @@ -246,8 +254,8 @@ private static JavaConstant readConstantField(HostedField field, JavaConstant re } private void addStaticFields() { - addObject(StaticFieldsSupport.getStaticObjectFields(), false, "staticObjectFields"); - addObject(StaticFieldsSupport.getStaticPrimitiveFields(), false, "staticPrimitiveFields"); + addObject(StaticFieldsSupport.getStaticObjectFields(), false, HeapInclusionReason.StaticObjectFields); + addObject(StaticFieldsSupport.getStaticPrimitiveFields(), false, HeapInclusionReason.StaticPrimitiveFields); /* * We only have empty holder arrays for the static fields, so we need to add static object @@ -315,6 +323,8 @@ public void addConstant(final JavaConstant constant, boolean immutableFromParent final ObjectInfo existing = objects.get(uncompressed); if (existing == null) { addObjectToImageHeap(uncompressed, immutableFromParent, identityHashCode, reason); + } else if (objectReachabilityInfo != null) { + objectReachabilityInfo.get(existing).addReason(reason); } } @@ -369,9 +379,9 @@ public ObjectInfo addFillerObject(int size) { int elementSize = objectLayout.getArrayIndexScale(JavaKind.Int); int arrayLength = (size - minArraySize) / elementSize; assert objectLayout.getArraySize(JavaKind.Int, arrayLength) == size; - return addLateToImageHeap(new int[arrayLength], "Filler object"); + return addLateToImageHeap(new int[arrayLength], HeapInclusionReason.FillerObject); } else if (size >= minInstanceSize) { - return addLateToImageHeap(new FillerObject(), "Filler object"); + return addLateToImageHeap(new FillerObject(), HeapInclusionReason.FillerObject); } else { return null; } @@ -570,7 +580,7 @@ private static StringBuilder fillReasonStack(StringBuilder msg, Object reason) { if (reason instanceof ObjectInfo) { ObjectInfo info = (ObjectInfo) reason; msg.append(" object: ").append(info.getObject()).append(" of class: ").append(info.getObject().getClass().getTypeName()).append(System.lineSeparator()); - return fillReasonStack(msg, info.reason); + return fillReasonStack(msg, info.getMainReason()); } return msg.append(" root: ").append(reason).append(System.lineSeparator()); } @@ -613,7 +623,7 @@ private ObjectInfo addToImageHeap(JavaConstant constant, HostedClass clazz, long * objects are not processed recursively. Use this method with care. */ @Override - public ObjectInfo addLateToImageHeap(Object object, String reason) { + public ObjectInfo addLateToImageHeap(Object object, Object reason) { assert !(object instanceof DynamicHub) : "needs a different identity hashcode"; assert !(object instanceof String) : "needs String interning"; @@ -710,9 +720,9 @@ public final class ObjectInfo implements ImageHeapObject { * * This is either another ObjectInfo, saying which object refers to this object, eventually * a root object which refers to this object, or is a String explaining why this object is - * in the heap. The reason field is like a "comes from" pointer. + * in the heap, or an {@link HeapInclusionReason}, or a {@link HostedField}. */ - final Object reason; + private final Object reason; ObjectInfo(Object object, long size, HostedClass clazz, int identityHashCode, Object reason) { this(SubstrateObjectConstant.forObject(object), size, clazz, identityHashCode, reason); @@ -725,7 +735,12 @@ public final class ObjectInfo implements ImageHeapObject { this.offsetInPartition = -1L; this.size = size; this.identityHashCode = identityHashCode; + + // For diagnostic purposes only this.reason = reason; + if (objectReachabilityInfo != null) { + objectReachabilityInfo.put(this, new ObjectReachabilityInfo(this, reason)); + } } @Override @@ -811,16 +826,20 @@ int getIdentityHashCode() { return identityHashCode; } + Object getMainReason() { + return this.reason; + } + @Override public String toString() { - StringBuilder result = new StringBuilder(getObject().getClass().getName()).append(" -> "); - Object cur = reason; + StringBuilder result = new StringBuilder(getObject().getClass().getName()).append(":").append(identityHashCode).append(" -> "); + Object cur = getMainReason(); Object prev = null; boolean skipped = false; while (cur instanceof ObjectInfo) { skipped = prev != null; prev = cur; - cur = ((ObjectInfo) cur).reason; + cur = ((ObjectInfo) cur).getMainReason(); } if (skipped) { result.append("... -> "); @@ -832,6 +851,7 @@ public String toString() { } return result.toString(); } + } protected static final class Phase { @@ -874,4 +894,91 @@ private enum PhaseValue { AFTER } } + + enum HeapInclusionReason { + InternedStringsTable, + FillerObject, + StaticObjectFields, + DataSection, + StaticPrimitiveFields, + Resource, + } + + final class ObjectReachabilityInfo { + private final LinkedHashSet allReasons; + private int objectReachabilityGroup; + + ObjectReachabilityInfo(ObjectInfo info, Object firstReason) { + this.allReasons = new LinkedHashSet<>(); + this.allReasons.add(firstReason); + this.objectReachabilityGroup = ObjectReachabilityGroup.getFlagForObjectInfo(info, firstReason, objectReachabilityInfo); + } + + void addReason(Object additionalReason) { + this.allReasons.add(additionalReason); + this.objectReachabilityGroup |= ObjectReachabilityGroup.getByReason(additionalReason, objectReachabilityInfo); + } + + Set getAllReasons() { + return this.allReasons; + } + + int getObjectReachabilityGroup() { + return objectReachabilityGroup; + } + + boolean objectReachableFrom(ObjectReachabilityGroup other) { + return (this.objectReachabilityGroup & other.flag) != 0; + } + } + + /** + * For diagnostic purposes only when + * {@link ImageHeapConnectedComponentsFeature.Options#PrintImageHeapConnectedComponents} is + * enabled. + */ + enum ObjectReachabilityGroup { + Resources(1 << 1, "Resources byte arrays", "resources"), + InternedStringsTable(1 << 2, "Interned strings table", "internedStringsTable"), + DynamicHubs(1 << 3, "Class data", "classData"), + ImageCodeInfo(1 << 4, "Code metadata", "codeMetadata"), + MethodOrStaticField(1 << 5, "Connected components accessed from method or a static field", "connectedComponents"), + Other(1 << 6, "Other", "other"); + + public final int flag; + public final String description; + public final String name; + + ObjectReachabilityGroup(int flag, String description, String name) { + this.flag = flag; + this.description = description; + this.name = name; + } + + static int getFlagForObjectInfo(ObjectInfo object, Object firstReason, Map additionalReasonInfoHashMap) { + int result = 0; + if (object.getObjectClass().equals(ImageCodeInfo.class)) { + result |= ImageCodeInfo.flag; + } + if (object.getObject().getClass().equals(DynamicHub.class) || object.getObject().getClass().equals(DynamicHubCompanion.class)) { + result |= DynamicHubs.flag; + } + result |= getByReason(firstReason, additionalReasonInfoHashMap); + return result; + } + + static int getByReason(Object reason, Map additionalReasonInfoHashMap) { + if (reason.equals(HeapInclusionReason.InternedStringsTable)) { + return ObjectReachabilityGroup.InternedStringsTable.flag; + } else if (reason.equals(HeapInclusionReason.Resource)) { + return ObjectReachabilityGroup.Resources.flag; + } else if (reason instanceof String || reason instanceof HostedField) { + return ObjectReachabilityGroup.MethodOrStaticField.flag; + } else if (reason instanceof ObjectInfo) { + ObjectInfo r = (ObjectInfo) reason; + return additionalReasonInfoHashMap.get(r).getObjectReachabilityGroup(); + } + return ObjectReachabilityGroup.Other.flag; + } + } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/doc-files/PrintImageHeapConnectedComponents.md b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/doc-files/PrintImageHeapConnectedComponents.md new file mode 100644 index 000000000000..7923dadf1564 --- /dev/null +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/doc-files/PrintImageHeapConnectedComponents.md @@ -0,0 +1,99 @@ +A native-image run with the flag `-H:+PrintImageHeapConnectedComponents` will create reports that can help us debug which objects got into the native image heap and why. A component represents a set of objects in the image heap. +The reports divide components into two kinds: +* Special components: Code metadata, Class data, Interned strings table, and Resources +* Connected components: These components consist of objects that refer to one another and are not of special components kind. + +When `PrintImageHeapConnectedComponents` flag is enabled, a native-image run will generate three reports with the following names: +* connected_components_{image-name}_{timestamp}.txt +* summary_info_for_every_object_in_connected_components_{image-name}_{timestamp}.json +* access_points_for_connected_components_{image-name}_{timestamp}.json + +Connected components are listed in the body of a connected_components_{image-name}_{timestamp}.txt report sorted +in descending order by their sizes. + +Here is an example of a connected component summary info in a .connected_components_{image-name}_{timestamp}.txt report: +``` +============================================================================================================================================== +================================ ComponentId=21 | Size=3.05KiB | Percentage of total image heap size=0.0866% ================================= + Count Size Size% Cum% Class + 52 1664 53.33% 53.33% java.math.BigInteger + 37 720 23.08% 76.41% java.math.BigInteger[] + 36 576 18.46% 94.87% int[] + 1 160 5.13% 100.00% java.math.BigInteger[][] + +Static fields accessing component 21: + java.math.BigInteger#powerCache + +Methods accessing connected component 21: + java.math.BigInteger#valueOf(long) + java.math.BigInteger#shiftRightImpl(int) + java.math.BigInteger#valueOf(long) + java.math.BigInteger#pow(int) + java.math.BigInteger#pow(int) +``` +We can see that the component size is 3.05KiB and +that it takes 0.0866% of the total image heap size. +Below the connected component summary is the histogram of objects in that connected component. For example, we see +that the component has 52 BigInteger objects, 37 BigInteger[] objects, 36 int[] objects, and 1 BigInteger[][] object. +`Static fields accessing Component 21` segment contains the list of static fields referencing an object from the connected component. +Methods accessing connected components is the list of methods referencing one or more objects from the connected component. + +Sometimes too many methods and static fields reference a single component to be displayed in the +`access_points_for_connected_components_{image-name}_{timestamp}.json` report. +For that reason, the report `access_points_for_connected_components_{image-name}_{timestamp}.json` contains, for each +component, a list of method and static field names that reference objects inside a given component. + +Report `summary_info_for_every_object_in_connected_components_{image-name}_{timestamp}.json` gives a short summary info +for all objects in the connected_components_{image-name}_{timestamp}.txt report. +`summary_info_for_every_object_in_connected_components_{image-name}_{timestamp}.json` is a json file with the following +structure: +``` +{ + "connectedComponents":[ + "componentId":0, + "sizeInBytes":95920, + "objects":[ + { + "className":"[B", + "identityHashCode":"1118440229", + "constantValue":"byte[21]{40, 76, 106, 97, 118, ...}", + "reason":{ + "kind":"object", + "value":"2126035551" + } + }, + ... + ], + "classData":{ + "sizeInBytes":1245240, + "objects":[{...},...] + }, + "codeMetadata":{ + "sizeInBytes":914200, + "objects":[{...},...] + }, + "internedStringsTable":{ + "sizeInBytes":17400, + "objects":[{...},...] + } + "resources":{ + "sizeInBytes":0, + "objects":[] + } +} +``` +Field `connectedComponents` array contains for each component the following fields: + `componentId` - id from the connected_components_{image-name}_{timestamp}.txt report + `sizeInBytes` - total size of the component in bytes + `reason` - is why an object was added to the image heap. The field `kind` can have one of the following values: "object", "method", "staticField", "svmInternal". + "object" means that another object referenced the object in the image heap, and in that case, the field "value" holds the identityHashCode of that object + "method" means that the object was added because some method has a reference to it, and in that case, the field "value" holds the method name + "staticField" means that the object was added because a static field is referencing it and, in that case, the field "value" holds the name of that static field + "svmInternal" means that the object was added by the internal infrastructure of the native-image + `objects` - an array of summary info for every object in the component. `identityHashCode` is the objects unique + identifier. We can use `identityHashCode` to identify why the object was added. + In the example above we can see that the `reason` that object with an `identityHashCode` value of + 1118440229 was added because it was referenced by another object whose `identityHashCode` value is 2126035551. + +Fields `classData`, `codeMetadata`, `resources`, and 'internedStringTable' each contain an array of `objects` that are in these +special component groups and the field `sizeInBytes` which is the total size of the objects in that special group.