Skip to content

Commit 67c8ac5

Browse files
committed
[GR-67508] Improve reporting on included resources and error when the image heap gets too large.
PullRequest: graal/21453
2 parents 205b4d0 + 630371a commit 67c8ac5

File tree

11 files changed

+60
-60
lines changed

11 files changed

+60
-60
lines changed

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Resources.java

Lines changed: 12 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848

4949
import org.graalvm.collections.EconomicMap;
5050
import org.graalvm.collections.MapCursor;
51+
import org.graalvm.collections.UnmodifiableEconomicMap;
5152
import org.graalvm.nativeimage.ImageInfo;
5253
import org.graalvm.nativeimage.ImageSingletons;
5354
import org.graalvm.nativeimage.Platform;
@@ -218,9 +219,10 @@ public String getModuleName() {
218219
}
219220

220221
/**
221-
* The object used to mark a resource as reachable according to the metadata. It can be obtained
222-
* when accessing the {@link Resources#resources} map, and it means that even though the
223-
* resource was correctly specified in the configuration, accessing it will return null.
222+
* A resource marked with the NEGATIVE_QUERY_MARKER is a resource included in the image
223+
* according to the resource configuration, but it does not actually exist. Trying to access it
224+
* at runtime will return {@code null} and not throw a
225+
* {@link com.oracle.svm.core.jdk.resources.MissingResourceRegistrationError}.
224226
*/
225227
public static final ResourceStorageEntryBase NEGATIVE_QUERY_MARKER = new ResourceStorageEntryBase();
226228

@@ -230,7 +232,7 @@ public String getModuleName() {
230232
* correctly specified in the configuration, but we do not want to throw directly (for example
231233
* when we try to check all the modules for a resource).
232234
*/
233-
private static final ResourceStorageEntryBase MISSING_METADATA_MARKER = new ResourceStorageEntryBase();
235+
public static final ResourceStorageEntryBase MISSING_METADATA_MARKER = new ResourceStorageEntryBase();
234236

235237
/**
236238
* Embedding a resource into an image is counted as a modification. Since all resources are
@@ -275,23 +277,8 @@ public void forEachResource(BiConsumer<ModuleResourceKey, ConditionalRuntimeValu
275277
}
276278

277279
@Platforms(Platform.HOSTED_ONLY.class)
278-
public ConditionalRuntimeValue<ResourceStorageEntryBase> getResource(ModuleResourceKey storageKey) {
279-
return resources.get(storageKey);
280-
}
281-
282-
@Platforms(Platform.HOSTED_ONLY.class)
283-
public Iterable<ConditionalRuntimeValue<ResourceStorageEntryBase>> resources() {
284-
return resources.getValues();
285-
}
286-
287-
@Platforms(Platform.HOSTED_ONLY.class)
288-
public Iterable<ModuleResourceKey> resourceKeys() {
289-
return resources.getKeys();
290-
}
291-
292-
@Platforms(Platform.HOSTED_ONLY.class)
293-
public int count() {
294-
return resources.size();
280+
public UnmodifiableEconomicMap<ModuleResourceKey, ConditionalRuntimeValue<ResourceStorageEntryBase>> resources() {
281+
return resources;
295282
}
296283

297284
public static long getLastModifiedTime() {
@@ -642,8 +629,8 @@ public static InputStream createInputStream(Module module, String resourceName)
642629
} else if (entry == null) {
643630
return null;
644631
}
645-
List<byte[]> data = entry.getData();
646-
return data.isEmpty() ? null : new ByteArrayInputStream(data.get(0));
632+
byte[][] data = entry.getData();
633+
return data.length == 0 ? null : new ByteArrayInputStream(data[0]);
647634
}
648635

649636
private static ResourceStorageEntryBase findResourceForInputStream(Module module, String resourceName) {
@@ -717,8 +704,7 @@ private static void addURLEntries(List<URL> resourcesURLs, ResourceStorageEntry
717704
if (entry == null) {
718705
return;
719706
}
720-
int numberOfResources = entry.getData().size();
721-
for (int index = 0; index < numberOfResources; index++) {
707+
for (int index = 0; index < entry.getData().length; index++) {
722708
resourcesURLs.add(createURL(module, canonicalResourceName, index));
723709
}
724710
}
@@ -828,7 +814,7 @@ public void afterCompilation(AfterCompilationAccess access) {
828814
* of lazily initialized fields. Only the byte[] arrays themselves can be safely made
829815
* read-only.
830816
*/
831-
for (ConditionalRuntimeValue<ResourceStorageEntryBase> entry : Resources.currentLayer().resources()) {
817+
for (ConditionalRuntimeValue<ResourceStorageEntryBase> entry : Resources.currentLayer().resources().getValues()) {
832818
var unconditionalEntry = entry.getValueUnconditionally();
833819
if (unconditionalEntry.hasData()) {
834820
for (byte[] resource : unconditionalEntry.getData()) {

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/resources/NativeImageResourceFileSystemUtil.java

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -40,23 +40,14 @@ public static byte[] getBytes(String resourceName, boolean readOnly) {
4040
if (entry == null) {
4141
return new byte[0];
4242
}
43-
byte[] bytes = ((ResourceStorageEntry) entry).getData().get(0);
43+
byte[] bytes = ((ResourceStorageEntry) entry).getData()[0];
4444
if (readOnly) {
4545
return bytes;
4646
} else {
4747
return Arrays.copyOf(bytes, bytes.length);
4848
}
4949
}
5050

51-
public static int getSize(String resourceName) {
52-
Object entry = Resources.getAtRuntime(resourceName);
53-
if (entry == null) {
54-
return 0;
55-
} else {
56-
return ((ResourceStorageEntry) entry).getData().get(0).length;
57-
}
58-
}
59-
6051
public static String toRegexPattern(String globPattern) {
6152
return Target_jdk_nio_zipfs_ZipUtils.toRegexPattern(globPattern);
6253
}

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/resources/ResourceStorageEntry.java

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,7 @@
2525

2626
package com.oracle.svm.core.jdk.resources;
2727

28-
import java.util.ArrayList;
29-
import java.util.List;
28+
import java.util.Arrays;
3029

3130
import org.graalvm.nativeimage.Platform;
3231
import org.graalvm.nativeimage.Platforms;
@@ -36,14 +35,16 @@
3635

3736
public final class ResourceStorageEntry extends ResourceStorageEntryBase {
3837

38+
private static final byte[][] EMPTY_DATA = new byte[0][];
39+
3940
private final boolean isDirectory;
4041
private final boolean fromJar;
41-
private List<byte[]> data;
42+
private byte[][] data;
4243

4344
public ResourceStorageEntry(boolean isDirectory, boolean fromJar) {
4445
this.isDirectory = isDirectory;
4546
this.fromJar = fromJar;
46-
this.data = List.of();
47+
this.data = EMPTY_DATA;
4748
}
4849

4950
@Override
@@ -57,18 +58,17 @@ public boolean isFromJar() {
5758
}
5859

5960
@Override
60-
public List<byte[]> getData() {
61+
public byte[][] getData() {
6162
return data;
6263
}
6364

6465
@Platforms(Platform.HOSTED_ONLY.class)
6566
@Override
6667
public void addData(byte[] datum) {
67-
List<byte[]> newData = new ArrayList<>(data.size() + 1);
68-
newData.addAll(data);
69-
newData.add(datum);
68+
byte[][] newData = Arrays.copyOf(data, data.length + 1);
69+
newData[data.length] = datum;
7070
/* Always use a compact, immutable data structure in the image heap. */
71-
data = List.copyOf(newData);
71+
data = newData;
7272
}
7373

7474
/**
@@ -81,7 +81,7 @@ public void addData(byte[] datum) {
8181
public void replaceData(byte[]... replacementData) {
8282
VMError.guarantee(BuildPhaseProvider.isAnalysisFinished(), "Replacing data of a resource entry before analysis finished. Register standard resource instead.");
8383
VMError.guarantee(!BuildPhaseProvider.isCompilationFinished(), "Trying to replace data of a resource entry after compilation finished.");
84-
this.data = List.of(replacementData);
84+
this.data = replacementData;
8585
}
8686

8787
@Override

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/resources/ResourceStorageEntryBase.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,6 @@
2525

2626
package com.oracle.svm.core.jdk.resources;
2727

28-
import java.util.List;
29-
3028
import org.graalvm.nativeimage.Platform;
3129
import org.graalvm.nativeimage.Platforms;
3230

@@ -41,7 +39,7 @@ public boolean isFromJar() {
4139
throw VMError.shouldNotReachHere("This should only be called on entries with data.");
4240
}
4341

44-
public List<byte[]> getData() {
42+
public byte[][] getData() {
4543
throw VMError.shouldNotReachHere("This should only be called on entries with data.");
4644
}
4745

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/resources/ResourceURLConnection.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ public void connect() {
7777
Object entry = Resources.getAtRuntime(module, resourceName, false);
7878
if (entry != null) {
7979
ResourceStorageEntry resourceStorageEntry = (ResourceStorageEntry) entry;
80-
List<byte[]> bytes = resourceStorageEntry.getData();
80+
byte[][] bytes = resourceStorageEntry.getData();
8181
isDirectory = resourceStorageEntry.isDirectory();
8282
String urlRef = url.getRef();
8383
int index = 0;
@@ -88,12 +88,12 @@ public void connect() {
8888
throw new IllegalArgumentException("URL anchor '#" + urlRef + "' not allowed in " + JavaNetSubstitutions.RESOURCE_PROTOCOL + " URL");
8989
}
9090
}
91-
if (index < bytes.size()) {
92-
this.data = bytes.get(index);
91+
if (index < bytes.length) {
92+
this.data = bytes[index];
9393
} else {
9494
// This will happen only in case that we are creating one URL with the second URL as
9595
// a context.
96-
this.data = bytes.get(0);
96+
this.data = bytes[0];
9797
}
9898
} else {
9999
this.data = null;

substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/EmbeddedResourceExporter.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ public static List<ResourceReportEntry> getResourceReportEntryList(ConcurrentHas
120120
List<EmbeddedResourceExporter.SourceSizePair> sources = new ArrayList<>();
121121
for (int i = 0; i < registeredEntrySources.size(); i++) {
122122
SourceAndOrigin sourceAndOrigin = registeredEntrySources.get(i);
123-
int size = storageEntry.getData().get(i).length;
123+
int size = storageEntry.getData()[i].length;
124124
sources.add(new SourceSizePair(sourceAndOrigin.source(), sourceAndOrigin.origin(), size));
125125
}
126126

substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/HeapBreakdownProvider.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ protected void calculate(BeforeImageWriteAccessImpl access, boolean resourcesAre
168168
if (!ImageLayerBuildingSupport.buildingExtensionLayer() && resourcesAreReachable) {
169169
/* Extract byte[] for resources. */
170170
int resourcesByteArrayCount = 0;
171-
for (ConditionalRuntimeValue<ResourceStorageEntryBase> resourceList : Resources.currentLayer().resources()) {
171+
for (ConditionalRuntimeValue<ResourceStorageEntryBase> resourceList : Resources.currentLayer().resources().getValues()) {
172172
if (resourceList.getValueUnconditionally().hasData()) {
173173
for (byte[] resource : resourceList.getValueUnconditionally().getData()) {
174174
resourcesByteArraySize += objectLayout.getArraySize(JavaKind.Byte, resource.length, true);

substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ProgressReporter.java

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@
5252
import java.util.stream.Collectors;
5353
import java.util.stream.Stream;
5454

55+
import com.oracle.svm.core.configure.ConditionalRuntimeValue;
56+
import com.oracle.svm.core.jdk.resources.ResourceStorageEntryBase;
5557
import org.graalvm.nativeimage.ImageSingletons;
5658
import org.graalvm.nativeimage.hosted.Feature;
5759
import org.graalvm.nativeimage.impl.ImageSingletonsSupport;
@@ -506,10 +508,22 @@ private void printAnalysisStatistics(AnalysisUniverse universe, Collection<Strin
506508
String stubsFormat = "%,9d downcalls and %,d upcalls ";
507509
recordJsonMetric(AnalysisResults.FOREIGN_DOWNCALLS, (numForeignDowncalls >= 0 ? numForeignDowncalls : UNAVAILABLE_METRIC));
508510
recordJsonMetric(AnalysisResults.FOREIGN_UPCALLS, (numForeignUpcalls >= 0 ? numForeignUpcalls : UNAVAILABLE_METRIC));
509-
if (numForeignDowncalls >= 0 || numForeignUpcalls >= 0) {
511+
if (numForeignDowncalls > 0 || numForeignUpcalls > 0) {
510512
l().a(stubsFormat, numForeignDowncalls, numForeignUpcalls)
511513
.doclink("registered for foreign access", "#glossary-foreign-downcall-and-upcall-registrations").println();
512514
}
515+
int resourceCount = Resources.currentLayer().resources().size();
516+
long totalResourceSize = 0;
517+
for (ConditionalRuntimeValue<ResourceStorageEntryBase> value : Resources.currentLayer().resources().getValues()) {
518+
if (value.getValueUnconditionally().hasData()) {
519+
for (byte[] bytes : value.getValueUnconditionally().getData()) {
520+
totalResourceSize += bytes.length;
521+
}
522+
}
523+
}
524+
if (resourceCount > 0) {
525+
l().a("%,9d %s registered with %s total size", resourceCount, resourceCount == 1 ? "resource access" : "resource accesses", ByteFormattingUtil.bytesToHuman(totalResourceSize)).println();
526+
}
513527
int numLibraries = libraries.size();
514528
if (numLibraries > 0) {
515529
TreeSet<String> sortedLibraries = new TreeSet<>(libraries);
@@ -578,10 +592,15 @@ public void printCreationEnd(int imageFileSize, int heapObjectCount, long imageH
578592
String format = "%9s (%5.2f%%) for ";
579593
l().a(format, ByteFormattingUtil.bytesToHuman(codeAreaSize), Utils.toPercentage(codeAreaSize, imageFileSize))
580594
.doclink("code area", "#glossary-code-area").a(":%,10d compilation units", numCompilations).println();
581-
int numResources = Resources.currentLayer().count();
595+
int numResources = 0;
596+
for (ConditionalRuntimeValue<ResourceStorageEntryBase> entry : Resources.currentLayer().resources().getValues()) {
597+
if (entry.getValueUnconditionally() != Resources.NEGATIVE_QUERY_MARKER && entry.getValueUnconditionally() != Resources.MISSING_METADATA_MARKER) {
598+
numResources++;
599+
}
600+
}
582601
recordJsonMetric(ImageDetailKey.IMAGE_HEAP_RESOURCE_COUNT, numResources);
583602
l().a(format, ByteFormattingUtil.bytesToHuman(imageHeapSize), Utils.toPercentage(imageHeapSize, imageFileSize))
584-
.doclink("image heap", "#glossary-image-heap").a(":%,9d objects and %,d resources", heapObjectCount, numResources).println();
603+
.doclink("image heap", "#glossary-image-heap").a(":%,9d objects and %,d resource%s", heapObjectCount, numResources, numResources == 1 ? "" : "s").println();
585604
long otherBytes = imageFileSize - codeAreaSize - imageHeapSize;
586605
if (debugInfoSize > 0) {
587606
recordJsonMetric(ImageDetailKey.DEBUG_INFO_SIZE, debugInfoSize); // Optional metric

substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ResourcesFeature.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,7 @@ public void addGlob(ConfigurationCondition condition, String module, String glob
200200

201201
@Override
202202
public void addCondition(ConfigurationCondition condition, Module module, String resourcePath) {
203-
var conditionalResource = Resources.currentLayer().getResource(createStorageKey(module, resourcePath));
203+
var conditionalResource = Resources.currentLayer().resources().get(createStorageKey(module, resourcePath));
204204
if (conditionalResource != null) {
205205
classInitializationSupport.addForTypeReachedTracking(condition.getType());
206206
conditionalResource.getConditions().addCondition(condition);

substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImage.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
import java.util.Set;
5151
import java.util.stream.Collectors;
5252

53+
import com.oracle.svm.hosted.ByteFormattingUtil;
5354
import org.graalvm.collections.Pair;
5455
import org.graalvm.nativeimage.ImageSingletons;
5556
import org.graalvm.nativeimage.c.CHeader;
@@ -503,6 +504,11 @@ public void build(String imageName, DebugContext debug) {
503504
*/
504505
boolean padImageHeap = !SpawnIsolates.getValue() || MremapImageHeap.getValue();
505506
long paddedImageHeapSize = padImageHeap ? NumUtil.roundUp(imageHeapSize, alignment) : imageHeapSize;
507+
508+
VMError.guarantee(NumUtil.isInt(paddedImageHeapSize),
509+
"The size of the image heap is %s and therefore too large. It must be smaller than %s. This can happen when very large resource files are included in the image or a build time initialized class creates a large cache.",
510+
ByteFormattingUtil.bytesToHuman(paddedImageHeapSize),
511+
ByteFormattingUtil.bytesToHuman(Integer.MAX_VALUE));
506512
RelocatableBuffer heapSectionBuffer = new RelocatableBuffer(paddedImageHeapSize, objectFile.getByteOrder());
507513
ProgbitsSectionImpl heapSectionImpl = new BasicProgbitsSectionImpl(heapSectionBuffer.getBackingArray());
508514
// Note: On isolate startup the read only part of the heap will be set up as such.

0 commit comments

Comments
 (0)