diff --git a/docs/reference-manual/native-image/BuildOutput.md b/docs/reference-manual/native-image/BuildOutput.md index ebdd1f181d5f..0cfabc2840c2 100644 --- a/docs/reference-manual/native-image/BuildOutput.md +++ b/docs/reference-manual/native-image/BuildOutput.md @@ -30,7 +30,7 @@ GraalVM Native Image: Generating 'helloworld' (executable)... Garbage collector: Serial GC (max heap size: 80% of RAM) -------------------------------------------------------------------------------- Build resources: - - 13.24GB of memory (42.7% of 31.00GB system memory, determined at start) + - 13.24GB of memory (42.7% of system memory, using available memory) - 16 thread(s) (100.0% of 16 available processor(s), determined at start) [2/8] Performing analysis... [****] (4.5s @ 0.54GB) 3,163 reachable types (72.5% of 4,364 total) @@ -142,12 +142,13 @@ The `NATIVE_IMAGE_OPTIONS` environment variable is designed to be used by users, #### Build Resources The memory limit and number of threads used by the build process. -More precisely, the memory limit of the Java heap, so actual memory consumption can be even higher. +More precisely, the memory limit of the Java heap, so actual memory consumption can be higher. Please check the [peak RSS](#glossary-peak-rss) reported at the end of the build to understand how much memory was actually used. -By default, the build process tries to only use free memory (to avoid memory pressure on the build machine), and never more than 32GB of memory. -If less than 8GB of memory are free, the build process falls back to use 85% of total memory. +By default, the build process uses the dedicated mode (up to 85% of system memory) in containers or CI environments (when the `$CI` environment variable is set to `true`), but never more than 32GB of memory. +Otherwise, it tries to use available memory to avoid memory pressure on developer machines (shared mode). +If less than 8GB of memory are available, the build process falls back to the dedicated mode. Therefore, consider freeing up memory if your machine is slow during a build, for example, by closing applications that you do not need. -It is possible to overwrite the default behavior, for example with `-J-XX:MaxRAMPercentage=60.0` or `-J-Xmx16g`. +It is possible to override the default behavior and set relative or absolute memory limits, for example with `-J-XX:MaxRAMPercentage=60.0` or `-J-Xmx16g`. By default, the build process uses all available processors to maximize speed, but not more than 32 threads. Use the `--parallelism` option to set the number of threads explicitly (for example, `--parallelism=4`). diff --git a/substratevm/CHANGELOG.md b/substratevm/CHANGELOG.md index 2a7457930f56..02f55aa15c99 100644 --- a/substratevm/CHANGELOG.md +++ b/substratevm/CHANGELOG.md @@ -5,6 +5,7 @@ This changelog summarizes major changes to GraalVM Native Image. ## GraalVM for JDK 25 * (GR-58668) Enabled [Whole-Program Sparse Conditional Constant Propagation (WP-SCCP)](https://github.com/oracle/graal/pull/9821) by default, improving the precision of points-to analysis in Native Image. This optimization enhances static analysis accuracy and scalability, potentially reducing the size of the final native binary. * (GR-59313) Deprecated class-level metadata extraction using `native-image-inspect` and removed option `DumpMethodsData`. Use class-level SBOMs instead by passing `--enable-sbom=class-level,export` to the `native-image` builder. The default value of option `IncludeMethodData` was changed to `false`. +* (GR-52400) The build process now uses 85% of system memory in containers and CI environments. Otherwise, it tries to only use available memory. If less than 8GB of memory are available, it falls back to 85% of system memory. The reason for the selected memory limit is now also shown in the build resources section of the build output. ## GraalVM for JDK 24 (Internal Version 24.2.0) * (GR-59717) Added `DuringSetupAccess.registerObjectReachabilityHandler` to allow registering a callback that is executed when an object of a specified type is marked as reachable during heap scanning. diff --git a/substratevm/mx.substratevm/suite.py b/substratevm/mx.substratevm/suite.py index b2df4e28b9e4..35cfb7ce092b 100644 --- a/substratevm/mx.substratevm/suite.py +++ b/substratevm/mx.substratevm/suite.py @@ -1003,6 +1003,9 @@ "java.base" : [ "jdk.internal.jimage", ], + "jdk.jfr": [ + "jdk.jfr.internal", + ], }, "checkstyle": "com.oracle.svm.hosted", "workingSets": "SVM", diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java index 54e8ae0628b3..f4346f18611f 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java @@ -738,6 +738,8 @@ public static boolean hasColorsEnabled(OptionValues values) { @Option(help = "Internal option to forward the value of " + NATIVE_IMAGE_OPTIONS_ENV_VAR)// public static final HostedOptionKey BuildOutputNativeImageOptionsEnvVarValue = new HostedOptionKey<>(null); + public static final String BUILD_MEMORY_USAGE_REASON_TEXT_PROPERTY = "svm.build.memoryUsageReasonText"; + /* * Object and array allocation options. */ diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateUtil.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateUtil.java index d7459d547695..513897f69ff7 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateUtil.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateUtil.java @@ -38,7 +38,6 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; -import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; import org.graalvm.nativeimage.c.type.CCharPointer; @@ -60,6 +59,7 @@ import jdk.graal.compiler.java.LambdaUtils; import jdk.graal.compiler.nodes.BreakpointNode; import jdk.graal.compiler.util.Digest; +import jdk.graal.compiler.word.Word; import jdk.vm.ci.meta.ResolvedJavaMethod; import jdk.vm.ci.meta.ResolvedJavaType; import jdk.vm.ci.meta.Signature; @@ -82,17 +82,17 @@ public class SubstrateUtil { public static String getArchitectureName() { String arch = System.getProperty("os.arch"); - switch (arch) { - case "x86_64": - arch = "amd64"; - break; - case "arm64": - arch = "aarch64"; - break; - } - return arch; + return switch (arch) { + case "x86_64" -> "amd64"; + case "arm64" -> "aarch64"; + default -> arch; + }; } + /* + * [GR-55515]: Accessing isTerminal() reflectively only for 21 JDK compatibility. After dropping + * JDK 21, use it directly. + */ private static final Method IS_TERMINAL_METHOD = ReflectionUtil.lookupMethod(true, Console.class, "isTerminal"); private static boolean isTTY() { @@ -111,8 +111,12 @@ private static boolean isTTY() { } } - public static boolean isRunningInCI() { - return !isTTY() || System.getenv("CI") != null; + public static boolean isNonInteractiveTerminal() { + return isCISetToTrue() || !isTTY(); + } + + public static boolean isCISetToTrue() { + return Boolean.parseBoolean(System.getenv("CI")); } /** diff --git a/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/MemoryUtil.java b/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/MemoryUtil.java index 7cae7ce04a99..f895e329a8d5 100644 --- a/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/MemoryUtil.java +++ b/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/MemoryUtil.java @@ -24,12 +24,27 @@ */ package com.oracle.svm.driver; +import java.io.BufferedReader; +import java.io.InputStreamReader; import java.lang.management.ManagementFactory; +import java.lang.reflect.Method; +import java.nio.file.Files; +import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; +import java.util.function.Function; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import com.oracle.svm.core.OS; +import com.oracle.svm.core.SubstrateOptions; +import com.oracle.svm.core.SubstrateUtil; import com.oracle.svm.core.util.ExitStatus; +import com.oracle.svm.core.util.VMError; import com.oracle.svm.driver.NativeImage.NativeImageError; +import com.oracle.svm.util.ReflectionUtil; + +import jdk.graal.compiler.serviceprovider.JavaVersionUtil; class MemoryUtil { private static final long KiB_TO_BYTES = 1024L; @@ -39,16 +54,35 @@ class MemoryUtil { /* Builder needs at least 512MiB for building a helloworld in a reasonable amount of time. */ private static final long MIN_HEAP_BYTES = 512L * MiB_TO_BYTES; - /* If free memory is below 8GiB, use 85% of total system memory (e.g., 7GiB * 85% ~ 6GiB). */ - private static final long DEDICATED_MODE_THRESHOLD = 8L * GiB_TO_BYTES; + /* Use 85% of total system memory (e.g., 7GiB * 85% ~ 6GiB) in dedicated mode. */ private static final double DEDICATED_MODE_TOTAL_MEMORY_RATIO = 0.85D; + /* If available memory is below 8GiB, fall back to dedicated mode. */ + private static final int MIN_AVAILABLE_MEMORY_THRESHOLD_GB = 8; + /* * Builder uses at most 32GB to avoid disabling compressed oops (UseCompressedOops). * Deliberately use GB (not GiB) to stay well below 32GiB when relative maximum is calculated. */ private static final long MAX_HEAP_BYTES = 32_000_000_000L; + private static final Method IS_CONTAINERIZED_METHOD; + private static final Object IS_CONTAINERIZED_RECEIVER; + + static { + IS_CONTAINERIZED_METHOD = ReflectionUtil.lookupMethod(jdk.jfr.internal.JVM.class, "isContainerized"); + if (JavaVersionUtil.JAVA_SPEC == 21) { // non-static + var jvmField = ReflectionUtil.lookupField(jdk.jfr.internal.JVM.class, "jvm"); + try { + IS_CONTAINERIZED_RECEIVER = jvmField.get(null); + } catch (IllegalAccessException e) { + throw VMError.shouldNotReachHere(e); + } + } else { + IS_CONTAINERIZED_RECEIVER = null; // static + } + } + public static List determineMemoryFlags(NativeImage.HostFlags hostFlags) { List flags = new ArrayList<>(); if (hostFlags.hasUseParallelGC()) { @@ -61,9 +95,9 @@ public static List determineMemoryFlags(NativeImage.HostFlags hostFlags) * -XX:InitialRAMPercentage or -Xms. */ if (hostFlags.hasMaxRAMPercentage()) { - flags.add("-XX:MaxRAMPercentage=" + determineReasonableMaxRAMPercentage()); + flags.addAll(determineMemoryUsageFlags(value -> "-XX:MaxRAMPercentage=" + value)); } else if (hostFlags.hasMaximumHeapSizePercent()) { - flags.add("-XX:MaximumHeapSizePercent=" + (int) determineReasonableMaxRAMPercentage()); + flags.addAll(determineMemoryUsageFlags(value -> "-XX:MaximumHeapSizePercent=" + value.intValue())); } if (hostFlags.hasGCTimeRatio()) { /* @@ -82,23 +116,39 @@ public static List determineMemoryFlags(NativeImage.HostFlags hostFlags) } /** - * Returns a percentage (0.0-100.0) to be used as a value for the -XX:MaxRAMPercentage flag of - * the builder process. Prefer free memory over total memory to reduce memory pressure on the - * host machine. Note that this method uses OperatingSystemMXBean, which is container-aware. + * Returns memory usage flags for the build process. Dedicated mode uses a fixed percentage of + * total memory and is the default in containers. Shared mode tries to use available memory to + * reduce memory pressure on the host machine. Note that this method uses OperatingSystemMXBean, + * which is container-aware. */ - private static double determineReasonableMaxRAMPercentage() { + private static List determineMemoryUsageFlags(Function toMemoryFlag) { var osBean = (com.sun.management.OperatingSystemMXBean) ManagementFactory.getOperatingSystemMXBean(); final double totalMemorySize = osBean.getTotalMemorySize(); - double reasonableMaxMemorySize = osBean.getFreeMemorySize(); + final double dedicatedMemorySize = totalMemorySize * DEDICATED_MODE_TOTAL_MEMORY_RATIO; - if (reasonableMaxMemorySize < DEDICATED_MODE_THRESHOLD) { - /* - * When free memory is low, for example in memory-constrained environments or when a - * good amount of memory is used for caching, use a fixed percentage of total memory - * rather than free memory. In containerized environments, builds are expected to run - * more or less exclusively (builder + driver + optional Gradle/Maven process). - */ - reasonableMaxMemorySize = totalMemorySize * DEDICATED_MODE_TOTAL_MEMORY_RATIO; + String memoryUsageReason = "unknown"; + final boolean isDedicatedMemoryUsage; + if (SubstrateUtil.isCISetToTrue()) { + isDedicatedMemoryUsage = true; + memoryUsageReason = "$CI set to 'true'"; + } else if (isContainerized()) { + isDedicatedMemoryUsage = true; + memoryUsageReason = "in container"; + } else { + isDedicatedMemoryUsage = false; + } + + double reasonableMaxMemorySize; + if (isDedicatedMemoryUsage) { + reasonableMaxMemorySize = dedicatedMemorySize; + } else { + reasonableMaxMemorySize = getAvailableMemorySize(); + if (reasonableMaxMemorySize >= MIN_AVAILABLE_MEMORY_THRESHOLD_GB * GiB_TO_BYTES) { + memoryUsageReason = "using available memory"; + } else { // fall back to dedicated mode + memoryUsageReason = "less than " + MIN_AVAILABLE_MEMORY_THRESHOLD_GB + "GB of memory available"; + reasonableMaxMemorySize = dedicatedMemorySize; + } } if (reasonableMaxMemorySize < MIN_HEAP_BYTES) { @@ -111,6 +161,142 @@ private static double determineReasonableMaxRAMPercentage() { /* Ensure max memory size does not exceed upper limit. */ reasonableMaxMemorySize = Math.min(reasonableMaxMemorySize, MAX_HEAP_BYTES); - return reasonableMaxMemorySize / totalMemorySize * 100; + double reasonableMaxRamPercentage = reasonableMaxMemorySize / totalMemorySize * 100; + return List.of(toMemoryFlag.apply(reasonableMaxRamPercentage), + "-D" + SubstrateOptions.BUILD_MEMORY_USAGE_REASON_TEXT_PROPERTY + "=" + memoryUsageReason); + } + + private static boolean isContainerized() { + if (!OS.LINUX.isCurrent()) { + return false; + } + /* + * [GR-55515]: Accessing isContainerized() reflectively only for 21 JDK compatibility + * (non-static vs static method). After dropping JDK 21, use it directly. + */ + try { + return (boolean) IS_CONTAINERIZED_METHOD.invoke(IS_CONTAINERIZED_RECEIVER); + } catch (ReflectiveOperationException | ClassCastException e) { + throw VMError.shouldNotReachHere(e); + } + } + + private static double getAvailableMemorySize() { + return switch (OS.getCurrent()) { + case LINUX -> getAvailableMemorySizeLinux(); + case DARWIN -> getAvailableMemorySizeDarwin(); + case WINDOWS -> getAvailableMemorySizeWindows(); + }; + } + + /** + * Returns the total amount of available memory in bytes on Linux based on + * /proc/meminfo, otherwise -1. Note that this metric is not + * container-aware (does not take cgroups into account) and may report available memory of the + * host. + * + * @see page_alloc.c#L5137 + */ + private static long getAvailableMemorySizeLinux() { + try { + String memAvailableLine = Files.readAllLines(Paths.get("/proc/meminfo")).stream().filter(l -> l.startsWith("MemAvailable")).findFirst().orElse(""); + Matcher m = Pattern.compile("^MemAvailable:\\s+(\\d+) kB").matcher(memAvailableLine); + if (m.matches()) { + return Long.parseLong(m.group(1)) * KiB_TO_BYTES; + } + } catch (Exception e) { + } + return -1; + } + + /** + * Returns the total amount of available memory in bytes on Darwin based on + * vm_stat, otherwise -1. + * + * @see vm_stat.c + */ + private static long getAvailableMemorySizeDarwin() { + try { + Process p = Runtime.getRuntime().exec(new String[]{"vm_stat"}); + try (BufferedReader reader = new BufferedReader(new InputStreamReader(p.getInputStream()))) { + String line1 = reader.readLine(); + if (line1 == null) { + return -1; + } + Matcher m1 = Pattern.compile("^Mach Virtual Memory Statistics: \\(page size of (\\d+) bytes\\)").matcher(line1); + long pageSize = -1; + if (m1.matches()) { + pageSize = Long.parseLong(m1.group(1)); + } + if (pageSize <= 0) { + return -1; + } + String line2 = reader.readLine(); + Matcher m2 = Pattern.compile("^Pages free:\\s+(\\d+).").matcher(line2); + long freePages = -1; + if (m2.matches()) { + freePages = Long.parseLong(m2.group(1)); + } + if (freePages <= 0) { + return -1; + } + String line3 = reader.readLine(); + if (!line3.startsWith("Pages active")) { + return -1; + } + String line4 = reader.readLine(); + Matcher m4 = Pattern.compile("^Pages inactive:\\s+(\\d+).").matcher(line4); + long inactivePages = -1; + if (m4.matches()) { + inactivePages = Long.parseLong(m4.group(1)); + } + if (inactivePages <= 0) { + return -1; + } + assert freePages > 0 && inactivePages > 0 && pageSize > 0; + return (freePages + inactivePages) * pageSize; + } finally { + p.waitFor(); + } + } catch (Exception e) { + } + return -1; + } + + /** + * Returns the total amount of available memory in bytes on Windows based on wmic, + * otherwise -1. + * + * @see Win32_OperatingSystem + * class + */ + private static long getAvailableMemorySizeWindows() { + try { + Process p = Runtime.getRuntime().exec(new String[]{"cmd.exe", "/c", "wmic", "OS", "get", "FreePhysicalMemory"}); + try (BufferedReader reader = new BufferedReader(new InputStreamReader(p.getInputStream()))) { + String line1 = reader.readLine(); + if (line1 == null || !line1.startsWith("FreePhysicalMemory")) { + return -1; + } + String line2 = reader.readLine(); + if (line2 == null) { + return -1; + } + String line3 = reader.readLine(); + if (line3 == null) { + return -1; + } + Matcher m = Pattern.compile("^(\\d+)\\s+").matcher(line3); + if (m.matches()) { + return Long.parseLong(m.group(1)) * KiB_TO_BYTES; + } + } + p.waitFor(); + } catch (Exception e) { + } + return -1; } } diff --git a/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/NativeImage.java b/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/NativeImage.java index 78b4eabe4a24..0abe93bbf52b 100644 --- a/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/NativeImage.java +++ b/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/NativeImage.java @@ -2504,12 +2504,12 @@ private static boolean isDumbTerm() { } private static boolean hasColorSupport() { - return !isDumbTerm() && !SubstrateUtil.isRunningInCI() && OS.getCurrent() != OS.WINDOWS && + return !isDumbTerm() && !SubstrateUtil.isNonInteractiveTerminal() && OS.getCurrent() != OS.WINDOWS && System.getenv("NO_COLOR") == null /* https://no-color.org/ */; } private static boolean hasProgressSupport(List imageBuilderArgs) { - if (isDumbTerm() || SubstrateUtil.isRunningInCI()) { + if (isDumbTerm() || SubstrateUtil.isNonInteractiveTerminal()) { return false; } 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 index 9683bdced990..67996723d9ea 100644 --- 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 @@ -25,35 +25,27 @@ 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; + private static final double BYTES_TO_KB = 1000d; + private static final double BYTES_TO_MB = 1000d * 1000d; + private static final double BYTES_TO_GB = 1000d * 1000d * 1000d; 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"; + if (bytes < BYTES_TO_KB) { + return toHuman(bytes, "B"); + } else if (bytes < BYTES_TO_MB) { + return toHuman(bytes / BYTES_TO_KB, "kB"); + } else if (bytes < BYTES_TO_GB) { + return toHuman(bytes / BYTES_TO_MB, "MB"); } else { - return String.format(format, bytesToGiB(bytes)) + "GB"; + return bytesToHumanGB(bytes); } } - static double bytesToKiB(long bytes) { - return bytes / BYTES_TO_KiB; - } - - static double bytesToGiB(long bytes) { - return bytes / BYTES_TO_GiB; + public static String bytesToHumanGB(long bytes) { + return toHuman(bytes / BYTES_TO_GB, "GB"); } - static double bytesToMiB(long bytes) { - return bytes / BYTES_TO_MiB; + private static String toHuman(double value, String unit) { + return "%.2f%s".formatted(value, unit); } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageOptions.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageOptions.java index 56b1c07019c1..cb134b30fcf1 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageOptions.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageOptions.java @@ -186,11 +186,11 @@ public static CStandards getCStandard() { } /** - * Configures the number of threads of the common pool (see driver). + * Configures the number of threads of the common pool. */ private static final String PARALLELISM_OPTION_NAME = "parallelism"; @APIOption(name = PARALLELISM_OPTION_NAME)// - @Option(help = "The maximum number of threads to use concurrently during native image generation.")// + @Option(help = "The maximum number of threads the build process is allowed to use.")// public static final HostedOptionKey NumberOfThreads = new HostedOptionKey<>(Math.max(1, Math.min(Runtime.getRuntime().availableProcessors(), 32)), key -> { int numberOfThreads = key.getValue(); if (numberOfThreads < 1) { 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 cf8debbbbd27..9355e699419d 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 @@ -107,7 +107,6 @@ import jdk.graal.compiler.util.json.JsonWriter; public class ProgressReporter implements FeatureSingleton, UnsavedSingleton { - private static final boolean IS_CI = SubstrateUtil.isRunningInCI(); private static final int CHARACTERS_PER_LINE; private static final String HEADLINE_SEPARATOR; private static final String LINE_SEPARATOR; @@ -169,7 +168,7 @@ private enum BuildStage { } static { - CHARACTERS_PER_LINE = IS_CI ? ProgressReporterCHelper.MAX_CHARACTERS_PER_LINE : ProgressReporterCHelper.getTerminalWindowColumnsClamped(); + CHARACTERS_PER_LINE = SubstrateUtil.isNonInteractiveTerminal() ? ProgressReporterCHelper.MAX_CHARACTERS_PER_LINE : ProgressReporterCHelper.getTerminalWindowColumnsClamped(); HEADLINE_SEPARATOR = Utils.stringFilledWith(CHARACTERS_PER_LINE, "="); LINE_SEPARATOR = Utils.stringFilledWith(CHARACTERS_PER_LINE, "-"); } @@ -424,14 +423,16 @@ private void printResourceInfo() { recordJsonMetric(ResourceUsageKey.MEMORY_TOTAL, totalMemorySize); List inputArguments = ManagementFactory.getRuntimeMXBean().getInputArguments(); - List maxRAMPrecentageValues = inputArguments.stream().filter(arg -> arg.startsWith("-XX:MaxRAMPercentage")).toList(); - String maxHeapSuffix = "determined at start"; - if (maxRAMPrecentageValues.size() > 1) { // The driver sets this option once - maxHeapSuffix = "set via '%s'".formatted(maxRAMPrecentageValues.get(maxRAMPrecentageValues.size() - 1)); + List maxRAMPercentageValues = inputArguments.stream().filter(arg -> arg.startsWith("-XX:MaxRAMPercentage=") || arg.startsWith("-XX:MaximumHeapSizePercent=")).toList(); + String memoryUsageReason = "unknown"; + if (maxRAMPercentageValues.size() == 1) { // The driver sets one of these options once + memoryUsageReason = System.getProperty(SubstrateOptions.BUILD_MEMORY_USAGE_REASON_TEXT_PROPERTY, "unknown"); + } else if (maxRAMPercentageValues.size() > 1) { + memoryUsageReason = "set via '%s'".formatted(maxRAMPercentageValues.getLast()); } String xmxValueOrNull = inputArguments.stream().filter(arg -> arg.startsWith("-Xmx")).reduce((first, second) -> second).orElse(null); if (xmxValueOrNull != null) { // -Xmx takes precedence over -XX:MaxRAMPercentage - maxHeapSuffix = "set via '%s'".formatted(xmxValueOrNull); + memoryUsageReason = "set via '%s'".formatted(xmxValueOrNull); } int maxNumberOfThreads = NativeImageOptions.getActualNumberOfThreads(); @@ -445,8 +446,7 @@ private void printResourceInfo() { l().printLineSeparator(); l().yellowBold().doclink("Build resources", "#glossary-build-resources").a(":").reset().println(); - l().a(" - %.2fGB of memory (%.1f%% of %.2fGB system memory, %s)", - ByteFormattingUtil.bytesToGiB(maxMemory), Utils.toPercentage(maxMemory, totalMemorySize), ByteFormattingUtil.bytesToGiB(totalMemorySize), maxHeapSuffix).println(); + l().a(" - %s of memory (%.1f%% of system memory, %s)", ByteFormattingUtil.bytesToHuman(maxMemory), Utils.toPercentage(maxMemory, totalMemorySize), memoryUsageReason).println(); l().a(" - %s thread(s) (%.1f%% of %s available processor(s), %s)", maxNumberOfThreads, Utils.toPercentage(maxNumberOfThreads, availableProcessors), availableProcessors, maxNumberOfThreadsSuffix).println(); } @@ -837,7 +837,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", ByteFormattingUtil.bytesToGiB(peakRSS)); + p.a(" | ").doclink("Peak RSS", "#glossary-peak-rss").a(": ").a(ByteFormattingUtil.bytesToHuman(peakRSS)); } recordJsonMetric(ResourceUsageKey.PEAK_RSS, (peakRSS >= 0 ? peakRSS : UNAVAILABLE_METRIC)); long processCPUTime = getOperatingSystemMXBean().getProcessCpuTime(); @@ -862,7 +862,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", ByteFormattingUtil.bytesToGiB(ProgressReporterCHelper.getPeakRSS())).println(); + l().a(" Please ensure more than %s of memory is available for Native Image", ByteFormattingUtil.bytesToHuman(ProgressReporterCHelper.getPeakRSS())).println(); l().a(" to reduce GC overhead and improve image build time.").println(); } lastGCStats = currentGCStats; @@ -898,8 +898,8 @@ private static double nanosToSeconds(double nanos) { return nanos / NANOS_TO_SECONDS; } - private static double getUsedMemory() { - return ByteFormattingUtil.bytesToGiB(Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()); + private static String getUsedMemory() { + return ByteFormattingUtil.bytesToHumanGB(Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()); } private static String stringFilledWith(int size, String fill) { @@ -1228,7 +1228,7 @@ void end(double totalTime) { a("]").reset(); } - String suffix = String.format("(%.1fs @ %.2fGB)", Utils.millisToSeconds(totalTime), Utils.getUsedMemory()); + String suffix = String.format("(%.1fs @ %s)", Utils.millisToSeconds(totalTime), Utils.getUsedMemory()); int textLength = getCurrentTextLength(); // TODO: `assert textLength > 0;` should be used here but tests do not start stages // properly (GR-35721) diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/JNIRegistrationJavaNio.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/JNIRegistrationJavaNio.java index ce89065ec271..ed27eead6ff7 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/JNIRegistrationJavaNio.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/JNIRegistrationJavaNio.java @@ -72,6 +72,7 @@ public void duringSetup(DuringSetupAccess a) { initializeAtRunTime(a, "sun.nio.ch.Net", "sun.nio.ch.SocketOptionRegistry$LazyInitialization"); initializeAtRunTime(a, "sun.nio.ch.AsynchronousSocketChannelImpl$DefaultOptionsHolder", "sun.nio.ch.AsynchronousServerSocketChannelImpl$DefaultOptionsHolder", "sun.nio.ch.DatagramChannelImpl$DefaultOptionsHolder", "sun.nio.ch.ServerSocketChannelImpl$DefaultOptionsHolder", "sun.nio.ch.SocketChannelImpl$DefaultOptionsHolder"); + initializeAtRunTime(a, "sun.nio.ch.NioSocketImpl"); /* Ensure that the interrupt signal handler is initialized at runtime. */ initializeAtRunTime(a, "sun.nio.ch.NativeThread"); initializeAtRunTime(a, "sun.nio.ch.FileDispatcherImpl", "sun.nio.ch.FileChannelImpl$Unmapper"); diff --git a/substratevm/src/com.oracle.svm.util/src/com/oracle/svm/util/ModuleSupport.java b/substratevm/src/com.oracle.svm.util/src/com/oracle/svm/util/ModuleSupport.java index 0445979610fb..01e04f6715c9 100644 --- a/substratevm/src/com.oracle.svm.util/src/com/oracle/svm/util/ModuleSupport.java +++ b/substratevm/src/com.oracle.svm.util/src/com/oracle/svm/util/ModuleSupport.java @@ -44,8 +44,8 @@ public final class ModuleSupport { public static final List nonExplicitModules = List.of(MODULE_SET_ALL_DEFAULT, MODULE_SET_ALL_SYSTEM, MODULE_SET_ALL_MODULE_PATH); public static final String ENV_VAR_USE_MODULE_SYSTEM = "USE_NATIVE_IMAGE_JAVA_PLATFORM_MODULE_SYSTEM"; - public static final String PROPERTY_IMAGE_EXPLICITLY_ADDED_MODULES = "org.graalvm.nativeimage.module.addmods"; - public static final String PROPERTY_IMAGE_EXPLICITLY_LIMITED_MODULES = "org.graalvm.nativeimage.module.limitmods"; + public static final String PROPERTY_IMAGE_EXPLICITLY_ADDED_MODULES = "svm.modulesupport.addedModules"; + public static final String PROPERTY_IMAGE_EXPLICITLY_LIMITED_MODULES = "svm.modulesupport.limitedModules"; public static final boolean modulePathBuild = isModulePathBuild(); public static final Set SYSTEM_MODULES = Set.of("org.graalvm.nativeimage.builder", "org.graalvm.nativeimage", "org.graalvm.nativeimage.base", "com.oracle.svm.svm_enterprise",