Skip to content

Commit f694f8a

Browse files
Jonathan Dowlandjerboaa
authored andcommitted
8292083: Detected container memory limit may exceed physical machine memory
Reviewed-by: sgehwolf, stuefe
1 parent f91943c commit f694f8a

File tree

7 files changed

+72
-28
lines changed

7 files changed

+72
-28
lines changed

src/hotspot/os/linux/cgroupSubsystem_linux.cpp

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -528,7 +528,30 @@ jlong CgroupSubsystem::memory_limit_in_bytes() {
528528
if (!memory_limit->should_check_metric()) {
529529
return memory_limit->value();
530530
}
531+
jlong phys_mem = os::Linux::physical_memory();
532+
log_trace(os, container)("total physical memory: " JLONG_FORMAT, phys_mem);
531533
jlong mem_limit = read_memory_limit_in_bytes();
534+
535+
if (mem_limit <= 0 || mem_limit >= phys_mem) {
536+
jlong read_mem_limit = mem_limit;
537+
const char *reason;
538+
if (mem_limit >= phys_mem) {
539+
// Exceeding physical memory is treated as unlimited. Cg v1's implementation
540+
// of read_memory_limit_in_bytes() caps this at phys_mem since Cg v1 has no
541+
// value to represent 'max'. Cg v2 may return a value >= phys_mem if e.g. the
542+
// container engine was started with a memory flag exceeding it.
543+
reason = "ignored";
544+
mem_limit = -1;
545+
} else if (OSCONTAINER_ERROR == mem_limit) {
546+
reason = "failed";
547+
} else {
548+
assert(mem_limit == -1, "Expected unlimited");
549+
reason = "unlimited";
550+
}
551+
log_debug(os, container)("container memory limit %s: " JLONG_FORMAT ", using host value " JLONG_FORMAT,
552+
reason, read_mem_limit, phys_mem);
553+
}
554+
532555
// Update cached metric to avoid re-reading container settings too often
533556
memory_limit->set_value(mem_limit, OSCONTAINER_CACHE_TIMEOUT);
534557
return mem_limit;

src/hotspot/os/linux/cgroupV1Subsystem_linux.cpp

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
#include "runtime/globals.hpp"
3232
#include "runtime/os.hpp"
3333
#include "utilities/globalDefinitions.hpp"
34+
#include "os_linux.hpp"
3435

3536
/*
3637
* Set directory to subsystem specific files based
@@ -91,15 +92,15 @@ jlong CgroupV1Subsystem::read_memory_limit_in_bytes() {
9192
GET_CONTAINER_INFO(julong, _memory->controller(), "/memory.limit_in_bytes",
9293
"Memory Limit is: " JULONG_FORMAT, JULONG_FORMAT, memlimit);
9394

94-
if (memlimit >= _unlimited_memory) {
95+
if (memlimit >= os::Linux::physical_memory()) {
9596
log_trace(os, container)("Non-Hierarchical Memory Limit is: Unlimited");
9697
CgroupV1MemoryController* mem_controller = reinterpret_cast<CgroupV1MemoryController*>(_memory->controller());
9798
if (mem_controller->is_hierarchical()) {
9899
const char* matchline = "hierarchical_memory_limit";
99100
const char* format = "%s " JULONG_FORMAT;
100101
GET_CONTAINER_INFO_LINE(julong, _memory->controller(), "/memory.stat", matchline,
101102
"Hierarchical Memory Limit is: " JULONG_FORMAT, format, hier_memlimit)
102-
if (hier_memlimit >= _unlimited_memory) {
103+
if (hier_memlimit >= os::Linux::physical_memory()) {
103104
log_trace(os, container)("Hierarchical Memory Limit is: Unlimited");
104105
} else {
105106
return (jlong)hier_memlimit;
@@ -113,17 +114,19 @@ jlong CgroupV1Subsystem::read_memory_limit_in_bytes() {
113114
}
114115

115116
jlong CgroupV1Subsystem::memory_and_swap_limit_in_bytes() {
117+
julong host_total_memsw;
116118
GET_CONTAINER_INFO(julong, _memory->controller(), "/memory.memsw.limit_in_bytes",
117119
"Memory and Swap Limit is: " JULONG_FORMAT, JULONG_FORMAT, memswlimit);
118-
if (memswlimit >= _unlimited_memory) {
120+
host_total_memsw = os::Linux::host_swap() + os::Linux::physical_memory();
121+
if (memswlimit >= host_total_memsw) {
119122
log_trace(os, container)("Non-Hierarchical Memory and Swap Limit is: Unlimited");
120123
CgroupV1MemoryController* mem_controller = reinterpret_cast<CgroupV1MemoryController*>(_memory->controller());
121124
if (mem_controller->is_hierarchical()) {
122125
const char* matchline = "hierarchical_memsw_limit";
123126
const char* format = "%s " JULONG_FORMAT;
124127
GET_CONTAINER_INFO_LINE(julong, _memory->controller(), "/memory.stat", matchline,
125128
"Hierarchical Memory and Swap Limit is : " JULONG_FORMAT, format, hier_memswlimit)
126-
if (hier_memswlimit >= _unlimited_memory) {
129+
if (hier_memswlimit >= host_total_memsw) {
127130
log_trace(os, container)("Hierarchical Memory and Swap Limit is: Unlimited");
128131
} else {
129132
jlong swappiness = read_mem_swappiness();
@@ -158,7 +161,7 @@ jlong CgroupV1Subsystem::read_mem_swappiness() {
158161
jlong CgroupV1Subsystem::memory_soft_limit_in_bytes() {
159162
GET_CONTAINER_INFO(julong, _memory->controller(), "/memory.soft_limit_in_bytes",
160163
"Memory Soft Limit is: " JULONG_FORMAT, JULONG_FORMAT, memsoftlimit);
161-
if (memsoftlimit >= _unlimited_memory) {
164+
if (memsoftlimit >= os::Linux::physical_memory()) {
162165
log_trace(os, container)("Memory Soft Limit is: Unlimited");
163166
return (jlong)-1;
164167
} else {
@@ -205,7 +208,7 @@ jlong CgroupV1Subsystem::kernel_memory_usage_in_bytes() {
205208
jlong CgroupV1Subsystem::kernel_memory_limit_in_bytes() {
206209
GET_CONTAINER_INFO(julong, _memory->controller(), "/memory.kmem.limit_in_bytes",
207210
"Kernel Memory Limit is: " JULONG_FORMAT, JULONG_FORMAT, kmem_limit);
208-
if (kmem_limit >= _unlimited_memory) {
211+
if (kmem_limit >= os::Linux::physical_memory()) {
209212
return (jlong)-1;
210213
}
211214
return (jlong)kmem_limit;

src/hotspot/os/linux/cgroupV1Subsystem_linux.hpp

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -104,8 +104,6 @@ class CgroupV1Subsystem: public CgroupSubsystem {
104104
CachingCgroupController * cpu_controller() { return _cpu; }
105105

106106
private:
107-
julong _unlimited_memory;
108-
109107
/* controllers */
110108
CachingCgroupController* _memory = NULL;
111109
CgroupV1Controller* _cpuset = NULL;
@@ -128,7 +126,6 @@ class CgroupV1Subsystem: public CgroupSubsystem {
128126
_cpuacct = cpuacct;
129127
_pids = pids;
130128
_memory = new CachingCgroupController(memory);
131-
_unlimited_memory = (LONG_MAX / os::vm_page_size()) * os::vm_page_size();
132129
}
133130
};
134131

src/hotspot/os/linux/osContainer_linux.cpp

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,6 @@ CgroupSubsystem* cgroup_subsystem;
4343
* we are running under cgroup control.
4444
*/
4545
void OSContainer::init() {
46-
jlong mem_limit;
47-
4846
assert(!_is_initialized, "Initializing OSContainer more than once");
4947

5048
_is_initialized = true;
@@ -60,15 +58,8 @@ void OSContainer::init() {
6058
if (cgroup_subsystem == NULL) {
6159
return; // Required subsystem files not found or other error
6260
}
63-
// We need to update the amount of physical memory now that
64-
// cgroup subsystem files have been processed.
65-
if ((mem_limit = cgroup_subsystem->memory_limit_in_bytes()) > 0) {
66-
os::Linux::set_physical_memory(mem_limit);
67-
log_info(os, container)("Memory Limit is: " JLONG_FORMAT, mem_limit);
68-
}
6961

7062
_is_containerized = true;
71-
7263
}
7364

7465
const char * OSContainer::container_type() {

src/hotspot/os/linux/os_linux.cpp

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -194,15 +194,12 @@ julong os::Linux::available_memory() {
194194
julong avail_mem;
195195

196196
if (OSContainer::is_containerized()) {
197-
jlong mem_limit, mem_usage;
198-
if ((mem_limit = OSContainer::memory_limit_in_bytes()) < 1) {
199-
log_debug(os, container)("container memory limit %s: " JLONG_FORMAT ", using host value",
200-
mem_limit == OSCONTAINER_ERROR ? "failed" : "unlimited", mem_limit);
201-
}
197+
jlong mem_limit = OSContainer::memory_limit_in_bytes();
198+
jlong mem_usage;
202199
if (mem_limit > 0 && (mem_usage = OSContainer::memory_usage_in_bytes()) < 1) {
203200
log_debug(os, container)("container memory usage failed: " JLONG_FORMAT ", using host value", mem_usage);
204201
}
205-
if (mem_limit > 0 && mem_usage > 0 ) {
202+
if (mem_limit > 0 && mem_usage > 0) {
206203
avail_mem = mem_limit > mem_usage ? (julong)mem_limit - (julong)mem_usage : 0;
207204
log_trace(os)("available container memory: " JULONG_FORMAT, avail_mem);
208205
return avail_mem;
@@ -223,8 +220,6 @@ julong os::physical_memory() {
223220
log_trace(os)("total container memory: " JLONG_FORMAT, mem_limit);
224221
return mem_limit;
225222
}
226-
log_debug(os, container)("container memory limit %s: " JLONG_FORMAT ", using host value",
227-
mem_limit == OSCONTAINER_ERROR ? "failed" : "unlimited", mem_limit);
228223
}
229224

230225
phys_mem = Linux::physical_memory();
@@ -340,6 +335,14 @@ pid_t os::Linux::gettid() {
340335
return (pid_t)rslt;
341336
}
342337

338+
// Returns the amount of swap currently configured, in bytes.
339+
// This can change at any time.
340+
julong os::Linux::host_swap() {
341+
struct sysinfo si;
342+
sysinfo(&si);
343+
return (julong)si.totalswap;
344+
}
345+
343346
// Most versions of linux have a bug where the number of processors are
344347
// determined by looking at the /proc file system. In a chroot environment,
345348
// the system call returns 1.

src/hotspot/os/linux/os_linux.hpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,6 @@ class os::Linux {
5757
static pthread_t _main_thread;
5858

5959
static julong available_memory();
60-
static julong physical_memory() { return _physical_memory; }
61-
static void set_physical_memory(julong phys_mem) { _physical_memory = phys_mem; }
6260
static int active_processor_count();
6361

6462
static void initialize_system_info();
@@ -131,6 +129,9 @@ class os::Linux {
131129
static address initial_thread_stack_bottom(void) { return _initial_thread_stack_bottom; }
132130
static uintptr_t initial_thread_stack_size(void) { return _initial_thread_stack_size; }
133131

132+
static julong physical_memory() { return _physical_memory; }
133+
static julong host_swap();
134+
134135
static intptr_t* ucontext_get_sp(const ucontext_t* uc);
135136
static intptr_t* ucontext_get_fp(const ucontext_t* uc);
136137

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

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424

2525
/*
2626
* @test
27+
* @bug 8146115 8292083
2728
* @key cgroups
2829
* @summary Test JVM's memory resource awareness when running inside docker container
2930
* @requires docker.support
@@ -41,6 +42,8 @@
4142
import jdk.test.lib.containers.docker.DockerTestUtils;
4243
import jdk.test.lib.process.OutputAnalyzer;
4344

45+
import static jdk.test.lib.Asserts.assertNotNull;
46+
4447
public class TestMemoryAwareness {
4548
private static final String imageName = Common.imageName("memory");
4649

@@ -76,6 +79,7 @@ public static void main(String[] args) throws Exception {
7679
"1G", Integer.toString(((int) Math.pow(2, 20)) * 1024),
7780
"1500M", Integer.toString(((int) Math.pow(2, 20)) * (1500 - 1024))
7881
);
82+
testContainerMemExceedsPhysical();
7983
} finally {
8084
if (!DockerTestUtils.RETAIN_IMAGE_AFTER_TEST) {
8185
DockerTestUtils.removeDockerImage(imageName);
@@ -96,6 +100,28 @@ private static void testMemoryLimit(String valueToSet, String expectedTraceValue
96100
.shouldMatch("Memory Limit is:.*" + expectedTraceValue);
97101
}
98102

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

100126
private static void testMemorySoftLimit(String valueToSet, String expectedTraceValue)
101127
throws Exception {

0 commit comments

Comments
 (0)