Skip to content

Commit 691c375

Browse files
committed
[GR-45673] Tweak GC settings of Native Image builder.
PullRequest: graal/14360
2 parents f1c1d71 + 64dbd4f commit 691c375

File tree

14 files changed

+231
-101
lines changed

14 files changed

+231
-101
lines changed

docs/reference-manual/native-image/BuildOutput.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -257,7 +257,10 @@ Increase the amount of available memory to reduce the time to build the native b
257257
#### <a name="glossary-peak-rss"></a>Peak RSS
258258
Peak [resident set size](https://en.wikipedia.org/wiki/Resident_set_size) as reported by the operating system.
259259
This value indicates the maximum amount of memory consumed by the build process.
260-
If the [GC statistics](#glossary-garbage-collection) do not show any problems, the amount of available memory of the system can be reduced to a value closer to the peak RSS.
260+
By default, the process will only use available memory, so memory that the operating system can make available without having to swap out memory used by other processes.
261+
Therefore, consider freeing up memory if builds are slow, for example, by closing applications that you do not need.
262+
Note that, by default, the build process will also not use more than 32GB if available.
263+
If the [GC statistics](#glossary-garbage-collection) do not show any problems, the amount of total memory of the system can be reduced to a value closer to the peak RSS to lower operational costs.
261264
262265
#### <a name="glossary-cpu-load"></a>CPU load
263266
The CPU time used by the process divided by the total process time.

substratevm/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ This changelog summarizes major changes to GraalVM Native Image.
44

55
## Version 23.1.0
66
* (GR-35746) Lower the default aligned chunk size from 1 MB to 512 KB for the serial and epsilon GCs, reducing memory usage and image size in many cases.
7+
* (GR-45673) Improve the memory footprint of the Native Image build process. The builder now takes available memory into account to reduce memory pressure when many other processes are running on the same machine. It also consumes less memory in many cases and is therefore also less likely to fail due to out-of-memory errors. At the same time, we have raised its memory limit from 14GB to 32GB.
78

89
## Version 23.0.0
910
* (GR-40187) Report invalid use of SVM specific classes on image class- or module-path as error. As a temporary workaround, `-H:+AllowDeprecatedBuilderClassesOnImageClasspath` allows turning the error into a warning.

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/OS.java

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ public enum OS {
3131

3232
DARWIN("Darwin", false),
3333
LINUX("Linux", true),
34-
SOLARIS("Solaris", true),
3534
WINDOWS("Windows", false);
3635

3736
/**
@@ -61,9 +60,6 @@ private static OS findCurrent() {
6160
if (name.equals("Linux")) {
6261
return LINUX;
6362
}
64-
if (name.equals("SunOS")) {
65-
return SOLARIS;
66-
}
6763
if (name.equals("Mac OS X") || name.equals("Darwin")) {
6864
return DARWIN;
6965
}
Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
/*
2+
* Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation. Oracle designates this
8+
* particular file as subject to the "Classpath" exception as provided
9+
* by Oracle in the LICENSE file that accompanied this code.
10+
*
11+
* This code is distributed in the hope that it will be useful, but WITHOUT
12+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14+
* version 2 for more details (a copy is included in the LICENSE file that
15+
* accompanied this code).
16+
*
17+
* You should have received a copy of the GNU General Public License version
18+
* 2 along with this work; if not, write to the Free Software Foundation,
19+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20+
*
21+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22+
* or visit www.oracle.com if you need additional information or have any
23+
* questions.
24+
*/
25+
package com.oracle.svm.driver;
26+
27+
import java.io.BufferedReader;
28+
import java.io.InputStreamReader;
29+
import java.lang.management.ManagementFactory;
30+
import java.nio.file.Files;
31+
import java.nio.file.Paths;
32+
import java.util.List;
33+
import java.util.regex.Matcher;
34+
import java.util.regex.Pattern;
35+
36+
import com.oracle.svm.core.OS;
37+
import com.oracle.svm.core.util.ExitStatus;
38+
import com.oracle.svm.driver.NativeImage.NativeImageError;
39+
40+
class MemoryUtil {
41+
private static final long KiB_TO_BYTES = 1024;
42+
private static final long MiB_TO_BYTES = 1024 * KiB_TO_BYTES;
43+
44+
/* Builder needs at least 512MiB for building a helloworld in a reasonable amount of time. */
45+
private static final long MIN_HEAP_BYTES = 512 * MiB_TO_BYTES;
46+
47+
/*
48+
* Builder uses at most 32GB to avoid disabling compressed oops (UseCompressedOops).
49+
* Deliberately use GB (not GiB) to stay well below 32GiB when relative maximum is calculated.
50+
*/
51+
private static final long MAX_HEAP_BYTES = 32_000_000_000L;
52+
53+
/* Use 80% of total system memory in case available memory cannot be determined. */
54+
private static final double FALLBACK_MAX_RAM_PERCENTAGE = 80.0;
55+
56+
public static List<String> determineMemoryFlags() {
57+
return List.of(
58+
/*
59+
* Use MaxRAMPercentage to allow users to overwrite max heap setting with
60+
* -XX:MaxRAMPercentage or -Xmx, and freely adjust the min heap with
61+
* -XX:InitialRAMPercentage or -Xms.
62+
*/
63+
"-XX:MaxRAMPercentage=" + determineReasonableMaxRAMPercentage(),
64+
/*
65+
* Optimize for throughput by increasing the goal of the total time for
66+
* garbage collection from 1% to 5% (N=19). This also reduces peak RSS.
67+
*/
68+
"-XX:GCTimeRatio=19", // 1/(1+N) time for GC
69+
/*
70+
* Let builder exit on first OutOfMemoryError to provide for shorter
71+
* feedback loops.
72+
*/
73+
"-XX:+ExitOnOutOfMemoryError");
74+
}
75+
76+
/**
77+
* Returns a percentage (0.0-100.0) to be used as a value for the -XX:MaxRAMPercentage flag of
78+
* the builder process. Prefer available memory over total memory to reduce memory pressure on
79+
* the host machine.
80+
*/
81+
private static double determineReasonableMaxRAMPercentage() {
82+
var osBean = (com.sun.management.OperatingSystemMXBean) ManagementFactory.getOperatingSystemMXBean();
83+
long totalMemorySize = osBean.getTotalMemorySize();
84+
long reasonableMaxMemorySize = -1;
85+
reasonableMaxMemorySize = switch (OS.getCurrent()) {
86+
case LINUX -> getAvailableMemorySizeLinux();
87+
case DARWIN -> getAvailableMemorySizeDarwin();
88+
case WINDOWS -> getAvailableMemorySizeWindows();
89+
};
90+
if (reasonableMaxMemorySize < 0 || reasonableMaxMemorySize > totalMemorySize) {
91+
return FALLBACK_MAX_RAM_PERCENTAGE;
92+
}
93+
if (reasonableMaxMemorySize < MIN_HEAP_BYTES) {
94+
throw new NativeImageError(
95+
"There is not enough memory available on the system (got %sMiB, need at least %sMiB). Consider freeing up memory if builds are slow, for example, by closing applications that you do not need."
96+
.formatted(reasonableMaxMemorySize / MiB_TO_BYTES, MIN_HEAP_BYTES / MiB_TO_BYTES),
97+
null, ExitStatus.OUT_OF_MEMORY.getValue());
98+
}
99+
reasonableMaxMemorySize = Math.min(reasonableMaxMemorySize, MAX_HEAP_BYTES);
100+
return (double) reasonableMaxMemorySize / totalMemorySize * 100;
101+
}
102+
103+
/**
104+
* Returns the total amount of available memory in bytes on Linux based on
105+
* <code>/proc/meminfo</code>, otherwise <code>-1</code>.
106+
*
107+
* @see <a href=
108+
* "https://github.com/torvalds/linux/blob/865fdb08197e657c59e74a35fa32362b12397f58/mm/page_alloc.c#L5137">page_alloc.c#L5137</a>
109+
*/
110+
private static long getAvailableMemorySizeLinux() {
111+
try {
112+
String memAvailableLine = Files.readAllLines(Paths.get("/proc/meminfo")).stream().filter(l -> l.startsWith("MemAvailable")).findFirst().orElse("");
113+
Matcher m = Pattern.compile("^MemAvailable:\\s+(\\d+) kB").matcher(memAvailableLine);
114+
if (m.matches()) {
115+
return Long.parseLong(m.group(1)) * KiB_TO_BYTES;
116+
}
117+
} catch (Exception e) {
118+
}
119+
return -1;
120+
}
121+
122+
/**
123+
* Returns the total amount of available memory in bytes on Darwin based on
124+
* <code>vm_stat</code>, otherwise <code>-1</code>.
125+
*
126+
* @see <a href=
127+
* "https://opensource.apple.com/source/system_cmds/system_cmds-496/vm_stat.tproj/vm_stat.c.auto.html">vm_stat.c</a>
128+
*/
129+
private static long getAvailableMemorySizeDarwin() {
130+
try {
131+
Process p = Runtime.getRuntime().exec(new String[]{"vm_stat"});
132+
try (BufferedReader reader = new BufferedReader(new InputStreamReader(p.getInputStream()))) {
133+
String line1 = reader.readLine();
134+
if (line1 == null) {
135+
return -1;
136+
}
137+
Matcher m1 = Pattern.compile("^Mach Virtual Memory Statistics: \\(page size of (\\d+) bytes\\)").matcher(line1);
138+
long pageSize = -1;
139+
if (m1.matches()) {
140+
pageSize = Long.parseLong(m1.group(1));
141+
}
142+
if (pageSize <= 0) {
143+
return -1;
144+
}
145+
String line2 = reader.readLine();
146+
Matcher m2 = Pattern.compile("^Pages free:\\s+(\\d+).").matcher(line2);
147+
long freePages = -1;
148+
if (m2.matches()) {
149+
freePages = Long.parseLong(m2.group(1));
150+
}
151+
if (freePages <= 0) {
152+
return -1;
153+
}
154+
String line3 = reader.readLine();
155+
if (!line3.startsWith("Pages active")) {
156+
return -1;
157+
}
158+
String line4 = reader.readLine();
159+
Matcher m4 = Pattern.compile("^Pages inactive:\\s+(\\d+).").matcher(line4);
160+
long inactivePages = -1;
161+
if (m4.matches()) {
162+
inactivePages = Long.parseLong(m4.group(1));
163+
}
164+
if (inactivePages <= 0) {
165+
return -1;
166+
}
167+
assert freePages > 0 && inactivePages > 0 && pageSize > 0;
168+
return (freePages + inactivePages) * pageSize;
169+
} finally {
170+
p.waitFor();
171+
}
172+
} catch (Exception e) {
173+
}
174+
return -1;
175+
}
176+
177+
/**
178+
* Returns the total amount of available memory in bytes on Windows based on <code>wmic</code>,
179+
* otherwise <code>-1</code>.
180+
*
181+
* @see <a href=
182+
* "https://learn.microsoft.com/en-us/windows/win32/cimwin32prov/win32-operatingsystem">Win32_OperatingSystem
183+
* class</a>
184+
*/
185+
private static long getAvailableMemorySizeWindows() {
186+
try {
187+
Process p = Runtime.getRuntime().exec(new String[]{"cmd.exe", "/c", "wmic", "OS", "get", "FreePhysicalMemory"});
188+
try (BufferedReader reader = new BufferedReader(new InputStreamReader(p.getInputStream()))) {
189+
String line1 = reader.readLine();
190+
if (line1 == null || !line1.startsWith("FreePhysicalMemory")) {
191+
return -1;
192+
}
193+
String line2 = reader.readLine();
194+
if (line2 == null) {
195+
return -1;
196+
}
197+
String line3 = reader.readLine();
198+
if (line3 == null) {
199+
return -1;
200+
}
201+
Matcher m = Pattern.compile("^(\\d+)\\s+").matcher(line3);
202+
if (m.matches()) {
203+
return Long.parseLong(m.group(1)) * KiB_TO_BYTES;
204+
}
205+
}
206+
p.waitFor();
207+
} catch (Exception e) {
208+
}
209+
return -1;
210+
}
211+
}

0 commit comments

Comments
 (0)