Skip to content

Commit e66fd86

Browse files
author
Jonathan Dowland
committed
8292083: Detected container memory limit may exceed physical machine memory
Reviewed-by: sgehwolf Backport-of: f694f8a7671002559e7d23fdb65d5e9c768f9c03
1 parent 282efe5 commit e66fd86

File tree

7 files changed

+71
-27
lines changed

7 files changed

+71
-27
lines changed

src/hotspot/os/linux/cgroupSubsystem_linux.cpp

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -555,7 +555,30 @@ jlong CgroupSubsystem::memory_limit_in_bytes() {
555555
if (!memory_limit->should_check_metric()) {
556556
return memory_limit->value();
557557
}
558+
jlong phys_mem = os::Linux::physical_memory();
559+
log_trace(os, container)("total physical memory: " JLONG_FORMAT, phys_mem);
558560
jlong mem_limit = read_memory_limit_in_bytes();
561+
562+
if (mem_limit <= 0 || mem_limit >= phys_mem) {
563+
jlong read_mem_limit = mem_limit;
564+
const char *reason;
565+
if (mem_limit >= phys_mem) {
566+
// Exceeding physical memory is treated as unlimited. Cg v1's implementation
567+
// of read_memory_limit_in_bytes() caps this at phys_mem since Cg v1 has no
568+
// value to represent 'max'. Cg v2 may return a value >= phys_mem if e.g. the
569+
// container engine was started with a memory flag exceeding it.
570+
reason = "ignored";
571+
mem_limit = -1;
572+
} else if (OSCONTAINER_ERROR == mem_limit) {
573+
reason = "failed";
574+
} else {
575+
assert(mem_limit == -1, "Expected unlimited");
576+
reason = "unlimited";
577+
}
578+
log_debug(os, container)("container memory limit %s: " JLONG_FORMAT ", using host value " JLONG_FORMAT,
579+
reason, read_mem_limit, phys_mem);
580+
}
581+
559582
// Update cached metric to avoid re-reading container settings too often
560583
memory_limit->set_value(mem_limit, OSCONTAINER_CACHE_TIMEOUT);
561584
return mem_limit;

src/hotspot/os/linux/cgroupV1Subsystem_linux.cpp

Lines changed: 8 additions & 5 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
@@ -105,15 +106,15 @@ jlong CgroupV1Subsystem::read_memory_limit_in_bytes() {
105106
GET_CONTAINER_INFO(julong, _memory->controller(), "/memory.limit_in_bytes",
106107
"Memory Limit is: " JULONG_FORMAT, JULONG_FORMAT, memlimit);
107108

108-
if (memlimit >= _unlimited_memory) {
109+
if (memlimit >= os::Linux::physical_memory()) {
109110
log_trace(os, container)("Non-Hierarchical Memory Limit is: Unlimited");
110111
CgroupV1MemoryController* mem_controller = reinterpret_cast<CgroupV1MemoryController*>(_memory->controller());
111112
if (mem_controller->is_hierarchical()) {
112113
const char* matchline = "hierarchical_memory_limit";
113114
const char* format = "%s " JULONG_FORMAT;
114115
GET_CONTAINER_INFO_LINE(julong, _memory->controller(), "/memory.stat", matchline,
115116
"Hierarchical Memory Limit is: " JULONG_FORMAT, format, hier_memlimit)
116-
if (hier_memlimit >= _unlimited_memory) {
117+
if (hier_memlimit >= os::Linux::physical_memory()) {
117118
log_trace(os, container)("Hierarchical Memory Limit is: Unlimited");
118119
} else {
119120
return (jlong)hier_memlimit;
@@ -127,17 +128,19 @@ jlong CgroupV1Subsystem::read_memory_limit_in_bytes() {
127128
}
128129

129130
jlong CgroupV1Subsystem::memory_and_swap_limit_in_bytes() {
131+
julong host_total_memsw;
130132
GET_CONTAINER_INFO(julong, _memory->controller(), "/memory.memsw.limit_in_bytes",
131133
"Memory and Swap Limit is: " JULONG_FORMAT, JULONG_FORMAT, memswlimit);
132-
if (memswlimit >= _unlimited_memory) {
134+
host_total_memsw = os::Linux::host_swap() + os::Linux::physical_memory();
135+
if (memswlimit >= host_total_memsw) {
133136
log_trace(os, container)("Non-Hierarchical Memory and Swap Limit is: Unlimited");
134137
CgroupV1MemoryController* mem_controller = reinterpret_cast<CgroupV1MemoryController*>(_memory->controller());
135138
if (mem_controller->is_hierarchical()) {
136139
const char* matchline = "hierarchical_memsw_limit";
137140
const char* format = "%s " JULONG_FORMAT;
138141
GET_CONTAINER_INFO_LINE(julong, _memory->controller(), "/memory.stat", matchline,
139142
"Hierarchical Memory and Swap Limit is : " JULONG_FORMAT, format, hier_memswlimit)
140-
if (hier_memswlimit >= _unlimited_memory) {
143+
if (hier_memswlimit >= host_total_memsw) {
141144
log_trace(os, container)("Hierarchical Memory and Swap Limit is: Unlimited");
142145
} else {
143146
jlong swappiness = read_mem_swappiness();
@@ -172,7 +175,7 @@ jlong CgroupV1Subsystem::read_mem_swappiness() {
172175
jlong CgroupV1Subsystem::memory_soft_limit_in_bytes() {
173176
GET_CONTAINER_INFO(julong, _memory->controller(), "/memory.soft_limit_in_bytes",
174177
"Memory Soft Limit is: " JULONG_FORMAT, JULONG_FORMAT, memsoftlimit);
175-
if (memsoftlimit >= _unlimited_memory) {
178+
if (memsoftlimit >= os::Linux::physical_memory()) {
176179
log_trace(os, container)("Memory Soft Limit is: Unlimited");
177180
return (jlong)-1;
178181
} else {

src/hotspot/os/linux/cgroupV1Subsystem_linux.hpp

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

9999
private:
100-
julong _unlimited_memory;
101-
102100
/* controllers */
103101
CachingCgroupController* _memory = NULL;
104102
CgroupV1Controller* _cpuset = NULL;
@@ -121,7 +119,6 @@ class CgroupV1Subsystem: public CgroupSubsystem {
121119
_cpuacct = cpuacct;
122120
_pids = pids;
123121
_memory = new CachingCgroupController(memory);
124-
_unlimited_memory = (LONG_MAX / os::vm_page_size()) * os::vm_page_size();
125122
}
126123
};
127124

src/hotspot/os/linux/osContainer_linux.cpp

Lines changed: 0 additions & 9 deletions
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;
@@ -59,15 +57,8 @@ void OSContainer::init() {
5957
if (cgroup_subsystem == NULL) {
6058
return; // Required subsystem files not found or other error
6159
}
62-
// We need to update the amount of physical memory now that
63-
// cgroup subsystem files have been processed.
64-
if ((mem_limit = cgroup_subsystem->memory_limit_in_bytes()) > 0) {
65-
os::Linux::set_physical_memory(mem_limit);
66-
log_info(os, container)("Memory Limit is: " JLONG_FORMAT, mem_limit);
67-
}
6860

6961
_is_containerized = true;
70-
7162
}
7263

7364
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
@@ -195,15 +195,12 @@ julong os::Linux::available_memory() {
195195
julong avail_mem;
196196

197197
if (OSContainer::is_containerized()) {
198-
jlong mem_limit, mem_usage;
199-
if ((mem_limit = OSContainer::memory_limit_in_bytes()) < 1) {
200-
log_debug(os, container)("container memory limit %s: " JLONG_FORMAT ", using host value",
201-
mem_limit == OSCONTAINER_ERROR ? "failed" : "unlimited", mem_limit);
202-
}
198+
jlong mem_limit = OSContainer::memory_limit_in_bytes();
199+
jlong mem_usage;
203200
if (mem_limit > 0 && (mem_usage = OSContainer::memory_usage_in_bytes()) < 1) {
204201
log_debug(os, container)("container memory usage failed: " JLONG_FORMAT ", using host value", mem_usage);
205202
}
206-
if (mem_limit > 0 && mem_usage > 0 ) {
203+
if (mem_limit > 0 && mem_usage > 0) {
207204
avail_mem = mem_limit > mem_usage ? (julong)mem_limit - (julong)mem_usage : 0;
208205
log_trace(os)("available container memory: " JULONG_FORMAT, avail_mem);
209206
return avail_mem;
@@ -224,8 +221,6 @@ julong os::physical_memory() {
224221
log_trace(os)("total container memory: " JLONG_FORMAT, mem_limit);
225222
return mem_limit;
226223
}
227-
log_debug(os, container)("container memory limit %s: " JLONG_FORMAT ", using host value",
228-
mem_limit == OSCONTAINER_ERROR ? "failed" : "unlimited", mem_limit);
229224
}
230225

231226
phys_mem = Linux::physical_memory();
@@ -354,6 +349,14 @@ pid_t os::Linux::gettid() {
354349
return (pid_t)rslt;
355350
}
356351

352+
// Returns the amount of swap currently configured, in bytes.
353+
// This can change at any time.
354+
julong os::Linux::host_swap() {
355+
struct sysinfo si;
356+
sysinfo(&si);
357+
return (julong)si.totalswap;
358+
}
359+
357360
// Most versions of linux have a bug where the number of processors are
358361
// determined by looking at the /proc file system. In a chroot environment,
359362
// 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
@@ -59,8 +59,6 @@ class Linux {
5959
static int _page_size;
6060

6161
static julong available_memory();
62-
static julong physical_memory() { return _physical_memory; }
63-
static void set_physical_memory(julong phys_mem) { _physical_memory = phys_mem; }
6462
static int active_processor_count();
6563

6664
static void initialize_system_info();
@@ -136,6 +134,9 @@ class Linux {
136134
static int page_size(void) { return _page_size; }
137135
static void set_page_size(int val) { _page_size = val; }
138136

137+
static julong physical_memory() { return _physical_memory; }
138+
static julong host_swap();
139+
139140
static intptr_t* ucontext_get_sp(const ucontext_t* uc);
140141
static intptr_t* ucontext_get_fp(const ucontext_t* uc);
141142

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)