Skip to content

Commit b4ef3d3

Browse files
author
Jonathan Dowland
committedDec 19, 2022
8292083: Detected container memory limit may exceed physical machine memory
Reviewed-by: sgehwolf Backport-of: f694f8a7671002559e7d23fdb65d5e9c768f9c03
1 parent 78cdc41 commit b4ef3d3

7 files changed

+74
-36
lines changed
 

‎hotspot/src/os/linux/vm/cgroupSubsystem_linux.cpp

+27
Original file line numberDiff line numberDiff line change
@@ -543,7 +543,34 @@ jlong CgroupSubsystem::memory_limit_in_bytes() {
543543
if (!memory_limit->should_check_metric()) {
544544
return memory_limit->value();
545545
}
546+
jlong phys_mem = os::Linux::physical_memory();
547+
if (PrintContainerInfo) {
548+
tty->print_cr("total physical memory: " JLONG_FORMAT, phys_mem);
549+
}
546550
jlong mem_limit = read_memory_limit_in_bytes();
551+
552+
if (mem_limit <= 0 || mem_limit >= phys_mem) {
553+
jlong read_mem_limit = mem_limit;
554+
const char *reason;
555+
if (mem_limit >= phys_mem) {
556+
// Exceeding physical memory is treated as unlimited. Cg v1's implementation
557+
// of read_memory_limit_in_bytes() caps this at phys_mem since Cg v1 has no
558+
// value to represent 'max'. Cg v2 may return a value >= phys_mem if e.g. the
559+
// container engine was started with a memory flag exceeding it.
560+
reason = "ignored";
561+
mem_limit = -1;
562+
} else if (OSCONTAINER_ERROR == mem_limit) {
563+
reason = "failed";
564+
} else {
565+
assert(mem_limit == -1, "Expected unlimited");
566+
reason = "unlimited";
567+
}
568+
if (PrintContainerInfo) {
569+
tty->print_cr("container memory limit %s: " JLONG_FORMAT ", using host value " JLONG_FORMAT,
570+
reason, read_mem_limit, phys_mem);
571+
}
572+
}
573+
547574
// Update cached metric to avoid re-reading container settings too often
548575
memory_limit->set_value(mem_limit, OSCONTAINER_CACHE_TIMEOUT);
549576
return mem_limit;

‎hotspot/src/os/linux/vm/cgroupV1Subsystem_linux.cpp

+8-5
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
#include "runtime/globals.hpp"
3131
#include "runtime/os.hpp"
3232
#include "utilities/globalDefinitions.hpp"
33+
#include "os_linux.hpp"
3334

3435
/*
3536
* Set directory to subsystem specific files based
@@ -104,7 +105,7 @@ jlong CgroupV1Subsystem::read_memory_limit_in_bytes() {
104105
GET_CONTAINER_INFO(julong, _memory->controller(), "/memory.limit_in_bytes",
105106
"Memory Limit is: " JULONG_FORMAT, JULONG_FORMAT, memlimit);
106107

107-
if (memlimit >= _unlimited_memory) {
108+
if (memlimit >= os::Linux::physical_memory()) {
108109
if (PrintContainerInfo) {
109110
tty->print_cr("Non-Hierarchical Memory Limit is: Unlimited");
110111
}
@@ -114,7 +115,7 @@ jlong CgroupV1Subsystem::read_memory_limit_in_bytes() {
114115
const char* format = "%s " JULONG_FORMAT;
115116
GET_CONTAINER_INFO_LINE(julong, _memory->controller(), "/memory.stat", matchline,
116117
"Hierarchical Memory Limit is: " JULONG_FORMAT, format, hier_memlimit)
117-
if (hier_memlimit >= _unlimited_memory) {
118+
if (hier_memlimit >= os::Linux::physical_memory()) {
118119
if (PrintContainerInfo) {
119120
tty->print_cr("Hierarchical Memory Limit is: Unlimited");
120121
}
@@ -130,9 +131,11 @@ jlong CgroupV1Subsystem::read_memory_limit_in_bytes() {
130131
}
131132

132133
jlong CgroupV1Subsystem::memory_and_swap_limit_in_bytes() {
134+
julong host_total_memsw;
133135
GET_CONTAINER_INFO(julong, _memory->controller(), "/memory.memsw.limit_in_bytes",
134136
"Memory and Swap Limit is: " JULONG_FORMAT, JULONG_FORMAT, memswlimit);
135-
if (memswlimit >= _unlimited_memory) {
137+
host_total_memsw = os::Linux::host_swap() + os::Linux::physical_memory();
138+
if (memswlimit >= host_total_memsw) {
136139
if (PrintContainerInfo) {
137140
tty->print_cr("Non-Hierarchical Memory and Swap Limit is: Unlimited");
138141
}
@@ -142,7 +145,7 @@ jlong CgroupV1Subsystem::memory_and_swap_limit_in_bytes() {
142145
const char* format = "%s " JULONG_FORMAT;
143146
GET_CONTAINER_INFO_LINE(julong, _memory->controller(), "/memory.stat", matchline,
144147
"Hierarchical Memory and Swap Limit is : " JULONG_FORMAT, format, hier_memlimit)
145-
if (hier_memlimit >= _unlimited_memory) {
148+
if (hier_memlimit >= host_total_memsw) {
146149
if (PrintContainerInfo) {
147150
tty->print_cr("Hierarchical Memory and Swap Limit is: Unlimited");
148151
}
@@ -159,7 +162,7 @@ jlong CgroupV1Subsystem::memory_and_swap_limit_in_bytes() {
159162
jlong CgroupV1Subsystem::memory_soft_limit_in_bytes() {
160163
GET_CONTAINER_INFO(julong, _memory->controller(), "/memory.soft_limit_in_bytes",
161164
"Memory Soft Limit is: " JULONG_FORMAT, JULONG_FORMAT, memsoftlimit);
162-
if (memsoftlimit >= _unlimited_memory) {
165+
if (memsoftlimit >= os::Linux::physical_memory()) {
163166
if (PrintContainerInfo) {
164167
tty->print_cr("Memory Soft Limit is: Unlimited");
165168
}

‎hotspot/src/os/linux/vm/cgroupV1Subsystem_linux.hpp

-3
Original file line numberDiff line numberDiff line change
@@ -94,8 +94,6 @@ class CgroupV1Subsystem: public CgroupSubsystem {
9494
CachingCgroupController * cpu_controller() { return _cpu; }
9595

9696
private:
97-
julong _unlimited_memory;
98-
9997
/* controllers */
10098
CachingCgroupController* _memory;
10199
CgroupV1Controller* _cpuset;
@@ -111,7 +109,6 @@ class CgroupV1Subsystem: public CgroupSubsystem {
111109
_cpu = new CachingCgroupController(cpu);
112110
_cpuacct = cpuacct;
113111
_memory = new CachingCgroupController(memory);
114-
_unlimited_memory = (LONG_MAX / os::vm_page_size()) * os::vm_page_size();
115112
}
116113
};
117114

‎hotspot/src/os/linux/vm/osContainer_linux.cpp

-11
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,6 @@ CgroupSubsystem* cgroup_subsystem;
4242
* we are running under cgroup control.
4343
*/
4444
void OSContainer::init() {
45-
jlong mem_limit;
46-
4745
assert(!_is_initialized, "Initializing OSContainer more than once");
4846

4947
_is_initialized = true;
@@ -63,17 +61,8 @@ void OSContainer::init() {
6361
if (cgroup_subsystem == NULL) {
6462
return; // Required subsystem files not found or other error
6563
}
66-
// We need to update the amount of physical memory now that
67-
// cgroup subsystem files have been processed.
68-
if ((mem_limit = cgroup_subsystem->memory_limit_in_bytes()) > 0) {
69-
os::Linux::set_physical_memory(mem_limit);
70-
if (PrintContainerInfo) {
71-
tty->print_cr("Memory Limit is: " JLONG_FORMAT, mem_limit);
72-
}
73-
}
7464

7565
_is_containerized = true;
76-
7766
}
7867

7968
const char * OSContainer::container_type() {

‎hotspot/src/os/linux/vm/os_linux.cpp

+11-15
Original file line numberDiff line numberDiff line change
@@ -184,21 +184,14 @@ julong os::Linux::available_memory() {
184184
julong avail_mem;
185185

186186
if (OSContainer::is_containerized()) {
187-
jlong mem_limit, mem_usage;
188-
if ((mem_limit = OSContainer::memory_limit_in_bytes()) < 1) {
189-
if (PrintContainerInfo) {
190-
tty->print_cr("container memory limit %s: " JLONG_FORMAT ", using host value",
191-
mem_limit == OSCONTAINER_ERROR ? "failed" : "unlimited", mem_limit);
192-
}
193-
}
194-
187+
jlong mem_limit = OSContainer::memory_limit_in_bytes();
188+
jlong mem_usage;
195189
if (mem_limit > 0 && (mem_usage = OSContainer::memory_usage_in_bytes()) < 1) {
196190
if (PrintContainerInfo) {
197191
tty->print_cr("container memory usage failed: " JLONG_FORMAT ", using host value", mem_usage);
198192
}
199193
}
200-
201-
if (mem_limit > 0 && mem_usage > 0 ) {
194+
if (mem_limit > 0 && mem_usage > 0) {
202195
avail_mem = mem_limit > mem_usage ? (julong)mem_limit - (julong)mem_usage : 0;
203196
if (PrintContainerInfo) {
204197
tty->print_cr("available container memory: " JULONG_FORMAT, avail_mem);
@@ -225,11 +218,6 @@ julong os::physical_memory() {
225218
}
226219
return mem_limit;
227220
}
228-
229-
if (PrintContainerInfo) {
230-
tty->print_cr("container memory limit %s: " JLONG_FORMAT ", using host value",
231-
mem_limit == OSCONTAINER_ERROR ? "failed" : "unlimited", mem_limit);
232-
}
233221
}
234222

235223
phys_mem = Linux::physical_memory();
@@ -308,6 +296,14 @@ pid_t os::Linux::gettid() {
308296
}
309297
}
310298

299+
// Returns the amount of swap currently configured, in bytes.
300+
// This can change at any time.
301+
julong os::Linux::host_swap() {
302+
struct sysinfo si;
303+
sysinfo(&si);
304+
return (julong)si.totalswap;
305+
}
306+
311307
// Most versions of linux have a bug where the number of processors are
312308
// determined by looking at the /proc file system. In a chroot environment,
313309
// the system call returns 1. This causes the VM to act as if it is

‎hotspot/src/os/linux/vm/os_linux.hpp

+3-2
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,6 @@ class Linux {
8181
static const int _vm_default_page_size;
8282

8383
static julong available_memory();
84-
static julong physical_memory() { return _physical_memory; }
85-
static void set_physical_memory(julong phys_mem) { _physical_memory = phys_mem; }
8684
static int active_processor_count();
8785

8886
static void initialize_system_info();
@@ -154,6 +152,9 @@ class Linux {
154152
static intptr_t* ucontext_get_sp(ucontext_t* uc);
155153
static intptr_t* ucontext_get_fp(ucontext_t* uc);
156154

155+
static julong physical_memory() { return _physical_memory; }
156+
static julong host_swap();
157+
157158
// For Analyzer Forte AsyncGetCallTrace profiling support:
158159
//
159160
// This interface should be declared in os_linux_i486.hpp, but

‎hotspot/test/runtime/containers/docker/TestMemoryAwareness.java

+25
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424

2525
/*
2626
* @test
27+
* @bug 8146115 8292083
2728
* @summary Test JVM's memory resource awareness when running inside docker container
2829
* @library /testlibrary /testlibrary/whitebox
2930
* @build AttemptOOM sun.hotspot.WhiteBox PrintContainerInfo CheckOperatingSystemMXBean
@@ -36,6 +37,7 @@
3637
import com.oracle.java.testlibrary.DockerTestUtils;
3738
import com.oracle.java.testlibrary.OutputAnalyzer;
3839

40+
import com.oracle.java.testlibrary.Asserts;
3941

4042
public class TestMemoryAwareness {
4143
private static final String imageName = Common.imageName("memory");
@@ -72,6 +74,7 @@ public static void main(String[] args) throws Exception {
7274
"1G", Integer.toString(((int) Math.pow(2, 20)) * 1024),
7375
"1500M", Integer.toString(((int) Math.pow(2, 20)) * (1500 - 1024))
7476
);
77+
testContainerMemExceedsPhysical();
7578
} finally {
7679
DockerTestUtils.removeDockerImage(imageName);
7780
}
@@ -90,6 +93,28 @@ private static void testMemoryLimit(String valueToSet, String expectedTraceValue
9093
.shouldMatch("Memory Limit is:.*" + expectedTraceValue);
9194
}
9295

96+
// JDK-8292083
97+
// Ensure that Java ignores container memory limit values above the host's physical memory.
98+
private static void testContainerMemExceedsPhysical()
99+
throws Exception {
100+
101+
Common.logNewTestCase("container memory limit exceeds physical memory");
102+
103+
DockerRunOptions opts = Common.newOpts(imageName);
104+
105+
// first run: establish physical memory in test environment and derive
106+
// a bad value one power of ten larger
107+
String goodMem = Common.run(opts).firstMatch("total physical memory: (\\d+)", 1);
108+
Asserts.assertNotNull(goodMem, "no match for 'total physical memory' in trace output");
109+
String badMem = goodMem + "0";
110+
111+
// second run: set a container memory limit to the bad value
112+
opts = Common.newOpts(imageName)
113+
.addDockerOpts("--memory", badMem);
114+
Common.run(opts)
115+
.shouldMatch("container memory limit (ignored: " + badMem + "|unlimited: -1), using host value " + goodMem);
116+
}
117+
93118

94119
private static void testMemorySoftLimit(String valueToSet, String expectedTraceValue)
95120
throws Exception {

0 commit comments

Comments
 (0)
Please sign in to comment.