Skip to content

Commit a7c011c

Browse files
committed
8360651: Create OSContainer API for memory limit
1 parent d8f9b18 commit a7c011c

File tree

11 files changed

+273
-3
lines changed

11 files changed

+273
-3
lines changed

src/hotspot/os/linux/osContainer_linux.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535

3636
bool OSContainer::_is_initialized = false;
3737
bool OSContainer::_is_containerized = false;
38+
bool OSContainer::_has_memory_limit = false;
3839
CgroupSubsystem* cgroup_subsystem;
3940

4041
/* init
@@ -76,14 +77,15 @@ void OSContainer::init() {
7677
const char *reason;
7778
bool any_mem_cpu_limit_present = false;
7879
bool controllers_read_only = cgroup_subsystem->is_containerized();
80+
_has_memory_limit = cgroup_subsystem->memory_limit_in_bytes() > 0;
7981
if (controllers_read_only) {
8082
// in-container case
8183
reason = " because all controllers are mounted read-only (container case)";
8284
} else {
8385
// We can be in one of two cases:
8486
// 1.) On a physical Linux system without any limit
8587
// 2.) On a physical Linux system with a limit enforced by other means (like systemd slice)
86-
any_mem_cpu_limit_present = cgroup_subsystem->memory_limit_in_bytes() > 0 ||
88+
any_mem_cpu_limit_present = _has_memory_limit ||
8789
os::Linux::active_processor_count() != cgroup_subsystem->active_processor_count();
8890
if (any_mem_cpu_limit_present) {
8991
reason = " because either a cpu or a memory limit is present";

src/hotspot/os/linux/osContainer_linux.hpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ class OSContainer: AllStatic {
4040
private:
4141
static bool _is_initialized;
4242
static bool _is_containerized;
43+
static bool _has_memory_limit;
4344
static int _active_processor_count;
4445

4546
public:
@@ -48,6 +49,7 @@ class OSContainer: AllStatic {
4849
static void print_container_helper(outputStream* st, jlong j, const char* metrics);
4950

5051
static inline bool is_containerized();
52+
static inline bool has_memory_limit();
5153
static const char * container_type();
5254

5355
static jlong memory_limit_in_bytes();
@@ -80,4 +82,8 @@ inline bool OSContainer::is_containerized() {
8082
return _is_containerized;
8183
}
8284

85+
inline bool OSContainer::has_memory_limit() {
86+
return _has_memory_limit;
87+
}
88+
8389
#endif // OS_LINUX_OSCONTAINER_LINUX_HPP

src/hotspot/share/prims/whitebox.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2508,6 +2508,11 @@ WB_ENTRY(jboolean, WB_IsContainerized(JNIEnv* env, jobject o))
25082508
return false;
25092509
WB_END
25102510

2511+
WB_ENTRY(jboolean, WB_HasMemoryLimit(JNIEnv* env, jobject o))
2512+
LINUX_ONLY(return OSContainer::has_memory_limit();)
2513+
return false;
2514+
WB_END
2515+
25112516
// Physical memory of the host machine (including containers)
25122517
WB_ENTRY(jlong, WB_HostPhysicalMemory(JNIEnv* env, jobject o))
25132518
LINUX_ONLY(return os::Linux::physical_memory();)
@@ -2981,6 +2986,7 @@ static JNINativeMethod methods[] = {
29812986
{CC"checkLibSpecifiesNoexecstack", CC"(Ljava/lang/String;)Z",
29822987
(void*)&WB_CheckLibSpecifiesNoexecstack},
29832988
{CC"isContainerized", CC"()Z", (void*)&WB_IsContainerized },
2989+
{CC"hasMemoryLimit", CC"()Z", (void*)&WB_HasMemoryLimit },
29842990
{CC"validateCgroup",
29852991
CC"(ZLjava/lang/String;Ljava/lang/String;Ljava/lang/String;)I",
29862992
(void*)&WB_ValidateCgroup },
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/*
2+
* Copyright (c) 2025, Red Hat, Inc.
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.
8+
*
9+
* This code is distributed in the hope that it will be useful, but WITHOUT
10+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12+
* version 2 for more details (a copy is included in the LICENSE file that
13+
* accompanied this code).
14+
*
15+
* You should have received a copy of the GNU General Public License version
16+
* 2 along with this work; if not, write to the Free Software Foundation,
17+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18+
*
19+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20+
* or visit www.oracle.com if you need additional information or have any
21+
* questions.
22+
*/
23+
24+
import jdk.test.whitebox.WhiteBox;
25+
26+
/*
27+
* Checks OSContainer::has_memory_limit() and related APIs when run with
28+
* a container limit.
29+
*/
30+
public class ContainerMemory {
31+
32+
public static void main(String[] args) {
33+
if (args.length < 1) {
34+
throw new RuntimeException("Illegal number of arguments. Expected at least one argument.");
35+
}
36+
switch (args[0]) {
37+
case "hasMemoryLimit": {
38+
testHasMemoryLimit(args[0], args[1]);
39+
break;
40+
}
41+
default: {
42+
throw new RuntimeException("Unknown test argument: " + args[0]);
43+
}
44+
}
45+
}
46+
47+
private static void testHasMemoryLimit(String testCase, String strExpected) {
48+
WhiteBox wb = WhiteBox.getWhiteBox();
49+
boolean expected = Boolean.parseBoolean(strExpected);
50+
boolean actual = wb.hasMemoryLimit();
51+
if (expected != actual) {
52+
throw new RuntimeException("hasMemoryLimit test failed. Expected '" + expected + "' but got '" + actual + "'");
53+
}
54+
// PASS
55+
System.out.printf("%s=%s", testCase, strExpected);
56+
}
57+
}

test/hotspot/jtreg/containers/cgroup/TestContainerized.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@ public static void main(String[] args) throws Exception {
4343
if (wb.isContainerized()) {
4444
throw new RuntimeException("Test failed! Expected not containerized on plain Linux.");
4545
}
46+
if (wb.hasMemoryLimit()) {
47+
throw new RuntimeException("Test failed! Expected no memory limit for plain Linux.");
48+
}
4649
System.out.println("Plain linux, no limits. Passed!");
4750
}
4851
}

test/hotspot/jtreg/containers/docker/TestContainerInfo.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
* @modules java.base/jdk.internal.misc
3434
* java.management
3535
* jdk.jartool/sun.tools.jar
36-
* @build CheckContainerized jdk.test.whitebox.WhiteBox PrintContainerInfo
36+
* @build jdk.test.whitebox.WhiteBox PrintContainerInfo
3737
* @run driver jdk.test.lib.helpers.ClassFileInstaller -jar whitebox.jar jdk.test.whitebox.WhiteBox
3838
* @run driver TestContainerInfo
3939
*/
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
/*
2+
* Copyright (c) 2025, Red Hat, Inc.
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.
8+
*
9+
* This code is distributed in the hope that it will be useful, but WITHOUT
10+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12+
* version 2 for more details (a copy is included in the LICENSE file that
13+
* accompanied this code).
14+
*
15+
* You should have received a copy of the GNU General Public License version
16+
* 2 along with this work; if not, write to the Free Software Foundation,
17+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18+
*
19+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20+
* or visit www.oracle.com if you need additional information or have any
21+
* questions.
22+
*/
23+
24+
25+
/*
26+
* @test
27+
* @bug 8360651
28+
* @key cgroups
29+
* @summary Test JVM's correct detection of memory limit in a container
30+
* @requires container.support
31+
* @library /test/lib ../
32+
* @modules java.base/jdk.internal.misc
33+
* java.base/jdk.internal.platform
34+
* java.management
35+
* jdk.jartool/sun.tools.jar
36+
* @build ContainerMemory jdk.test.whitebox.WhiteBox
37+
* @run driver jdk.test.lib.helpers.ClassFileInstaller -jar whitebox.jar jdk.test.whitebox.WhiteBox
38+
* @run main/othervm -Xbootclasspath/a:whitebox.jar -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI TestContainerMemory
39+
*/
40+
import java.util.function.Consumer;
41+
import jdk.test.lib.containers.docker.Common;
42+
import jdk.test.lib.containers.docker.DockerRunOptions;
43+
import jdk.test.lib.containers.docker.DockerTestUtils;
44+
import jdk.test.whitebox.WhiteBox;
45+
46+
import static jdk.test.lib.Asserts.assertNotNull;
47+
48+
public class TestContainerMemory {
49+
private static final String imageName = Common.imageName("container-memory");
50+
private static final WhiteBox wb = WhiteBox.getWhiteBox();
51+
52+
public static void main(String[] args) throws Exception {
53+
if (!DockerTestUtils.canTestDocker()) {
54+
return;
55+
}
56+
57+
Common.prepareWhiteBox();
58+
DockerTestUtils.buildJdkContainerImage(imageName);
59+
60+
try {
61+
testWithMemoryLimit();
62+
testWithoutMemoryLimit();
63+
} finally {
64+
if (!DockerTestUtils.RETAIN_IMAGE_AFTER_TEST) {
65+
DockerTestUtils.removeDockerImage(imageName);
66+
}
67+
}
68+
}
69+
70+
71+
private static void testWithMemoryLimit() throws Exception {
72+
String memLimit = "100m"; // Any limit will do
73+
Common.logNewTestCase("testing container memory with limit: " + memLimit);
74+
75+
DockerRunOptions opts = Common.newOpts(imageName, "ContainerMemory");
76+
opts.addClassOptions("hasMemoryLimit", "true");
77+
Common.addWhiteBoxOpts(opts);
78+
Common.addTestClassPath(opts);
79+
opts.addDockerOpts("--memory", memLimit);
80+
// We are interested in the default option when run in a container, so
81+
// don't append test java options
82+
opts.appendTestJavaOptions = false;
83+
Common.run(opts)
84+
.shouldContain("hasMemoryLimit=true");
85+
}
86+
87+
private static void testWithoutMemoryLimit() throws Exception {
88+
Common.logNewTestCase("testing container without limit");
89+
90+
DockerRunOptions opts = Common.newOpts(imageName, "ContainerMemory");
91+
opts.addClassOptions("hasMemoryLimit", "false");
92+
Common.addWhiteBoxOpts(opts);
93+
Common.addTestClassPath(opts);
94+
// We are interested in the default option when run in a container, so
95+
// don't append test java options
96+
opts.appendTestJavaOptions = false;
97+
Common.run(opts)
98+
.shouldContain("hasMemoryLimit=false");
99+
}
100+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
/*
2+
* Copyright (c) 2025, Red Hat, Inc.
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.
8+
*
9+
* This code is distributed in the hope that it will be useful, but WITHOUT
10+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12+
* version 2 for more details (a copy is included in the LICENSE file that
13+
* accompanied this code).
14+
*
15+
* You should have received a copy of the GNU General Public License version
16+
* 2 along with this work; if not, write to the Free Software Foundation,
17+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18+
*
19+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20+
* or visit www.oracle.com if you need additional information or have any
21+
* questions.
22+
*/
23+
24+
import jdk.test.lib.containers.systemd.SystemdRunOptions;
25+
import jdk.test.lib.containers.systemd.SystemdTestUtils;
26+
import jdk.test.lib.process.OutputAnalyzer;
27+
import jdk.test.whitebox.WhiteBox;
28+
import jtreg.SkippedException;
29+
30+
/*
31+
* @test
32+
* @bug 8360651
33+
* @summary Verify OSContainer::has_memory_limit() in a systemd slice
34+
* @requires systemd.support
35+
* @library /test/lib ../
36+
* @modules java.base/jdk.internal.platform
37+
* @build ContainerMemory jdk.test.whitebox.WhiteBox
38+
* @run driver jdk.test.lib.helpers.ClassFileInstaller -jar whitebox.jar jdk.test.whitebox.WhiteBox
39+
* @run main/othervm -Xbootclasspath/a:whitebox.jar -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI MemoryLimitTest
40+
*/
41+
public class MemoryLimitTest {
42+
43+
private static final int MB = 1024 * 1024;
44+
private static final WhiteBox wb = WhiteBox.getWhiteBox();
45+
private static final String TEST_SLICE_NAME = MemoryLimitTest.class.getSimpleName() + "HS";
46+
47+
public static void main(String[] args) throws Exception {
48+
testHasMemoryLimit();
49+
}
50+
51+
private static void testHasMemoryLimit() throws Exception {
52+
SystemdRunOptions opts = SystemdTestUtils.newOpts("ContainerMemory");
53+
opts.addClassOptions("hasMemoryLimit", "true");
54+
opts.memoryLimit("100M");
55+
opts.sliceName(TEST_SLICE_NAME);
56+
SystemdTestUtils.addWhiteBoxOpts(opts);
57+
58+
OutputAnalyzer out = SystemdTestUtils.buildAndRunSystemdJava(opts);
59+
out.shouldHaveExitValue(0);
60+
try {
61+
out.shouldContain("hasMemoryLimit=true");
62+
} catch (RuntimeException e) {
63+
// memory delegation needs to be enabled when run as user on cg v2
64+
if (SystemdTestUtils.RUN_AS_USER) {
65+
String hint = "When run as user on cg v2 memory delegation needs to be configured!";
66+
throw new SkippedException(hint);
67+
}
68+
throw e;
69+
}
70+
}
71+
72+
}

test/lib/jdk/test/lib/containers/docker/Common.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@
3131
import java.nio.file.Files;
3232
import java.nio.file.Paths;
3333
import java.nio.file.StandardCopyOption;
34+
import java.util.ArrayList;
35+
import java.util.List;
36+
import java.util.regex.Pattern;
37+
import java.util.stream.Collectors;
3438
import jdk.test.lib.Utils;
3539
import jdk.test.lib.process.OutputAnalyzer;
3640

@@ -101,6 +105,19 @@ public static DockerRunOptions addWhiteBoxOpts(DockerRunOptions opts) {
101105
return opts;
102106
}
103107

108+
public static DockerRunOptions addTestClassPath(DockerRunOptions opts) {
109+
int num = 0;
110+
List<String> classPath = new ArrayList<>();
111+
for (String item: Utils.TEST_CLASS_PATH.split(Pattern.quote(System.getProperty("path.separator")))) {
112+
String name = String.format("/test_class_path_%d", num);
113+
opts.addDockerOpts("--volume", String.format("%s:%s", item, name));
114+
classPath.add(name);
115+
num++;
116+
}
117+
opts.addJavaOpts("-cp", classPath.stream().collect(Collectors.joining(":")));
118+
return opts;
119+
}
120+
104121

105122
// most common type of run and checks
106123
public static OutputAnalyzer run(DockerRunOptions opts) throws Exception {

test/lib/jdk/test/lib/containers/systemd/SystemdTestUtils.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,13 @@ public static SystemdRunOptions newOpts(String testClass) {
7272
return new SystemdRunOptions(testClass,
7373
"-Xlog:os+container=trace",
7474
"-cp",
75-
Utils.TEST_CLASSES);
75+
Utils.TEST_CLASS_PATH);
76+
}
77+
78+
public static SystemdRunOptions addWhiteBoxOpts(SystemdRunOptions opts) {
79+
opts.addJavaOpts("-Xbootclasspath/a:whitebox.jar",
80+
"-XX:+UnlockDiagnosticVMOptions", "-XX:+WhiteBoxAPI");
81+
return opts;
7682
}
7783

7884
/**

0 commit comments

Comments
 (0)