Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

8292083: Detected container memory limit may exceed physical machine memory #180

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions hotspot/src/os/linux/vm/cgroupSubsystem_linux.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -543,7 +543,34 @@ jlong CgroupSubsystem::memory_limit_in_bytes() {
if (!memory_limit->should_check_metric()) {
return memory_limit->value();
}
jlong phys_mem = os::Linux::physical_memory();
if (PrintContainerInfo) {
tty->print_cr("total physical memory: " JLONG_FORMAT, phys_mem);
}
jlong mem_limit = read_memory_limit_in_bytes();

if (mem_limit <= 0 || mem_limit >= phys_mem) {
jlong read_mem_limit = mem_limit;
const char *reason;
if (mem_limit >= phys_mem) {
// Exceeding physical memory is treated as unlimited. Cg v1's implementation
// of read_memory_limit_in_bytes() caps this at phys_mem since Cg v1 has no
// value to represent 'max'. Cg v2 may return a value >= phys_mem if e.g. the
// container engine was started with a memory flag exceeding it.
reason = "ignored";
mem_limit = -1;
} else if (OSCONTAINER_ERROR == mem_limit) {
reason = "failed";
} else {
assert(mem_limit == -1, "Expected unlimited");
reason = "unlimited";
}
if (PrintContainerInfo) {
tty->print_cr("container memory limit %s: " JLONG_FORMAT ", using host value " JLONG_FORMAT,
reason, read_mem_limit, phys_mem);
}
}

// Update cached metric to avoid re-reading container settings too often
memory_limit->set_value(mem_limit, OSCONTAINER_CACHE_TIMEOUT);
return mem_limit;
Expand Down
13 changes: 8 additions & 5 deletions hotspot/src/os/linux/vm/cgroupV1Subsystem_linux.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
#include "runtime/globals.hpp"
#include "runtime/os.hpp"
#include "utilities/globalDefinitions.hpp"
#include "os_linux.hpp"

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

if (memlimit >= _unlimited_memory) {
if (memlimit >= os::Linux::physical_memory()) {
if (PrintContainerInfo) {
tty->print_cr("Non-Hierarchical Memory Limit is: Unlimited");
}
Expand All @@ -114,7 +115,7 @@ jlong CgroupV1Subsystem::read_memory_limit_in_bytes() {
const char* format = "%s " JULONG_FORMAT;
GET_CONTAINER_INFO_LINE(julong, _memory->controller(), "/memory.stat", matchline,
"Hierarchical Memory Limit is: " JULONG_FORMAT, format, hier_memlimit)
if (hier_memlimit >= _unlimited_memory) {
if (hier_memlimit >= os::Linux::physical_memory()) {
if (PrintContainerInfo) {
tty->print_cr("Hierarchical Memory Limit is: Unlimited");
}
Expand All @@ -130,9 +131,11 @@ jlong CgroupV1Subsystem::read_memory_limit_in_bytes() {
}

jlong CgroupV1Subsystem::memory_and_swap_limit_in_bytes() {
julong host_total_memsw;
GET_CONTAINER_INFO(julong, _memory->controller(), "/memory.memsw.limit_in_bytes",
"Memory and Swap Limit is: " JULONG_FORMAT, JULONG_FORMAT, memswlimit);
if (memswlimit >= _unlimited_memory) {
host_total_memsw = os::Linux::host_swap() + os::Linux::physical_memory();
if (memswlimit >= host_total_memsw) {
if (PrintContainerInfo) {
tty->print_cr("Non-Hierarchical Memory and Swap Limit is: Unlimited");
}
Expand All @@ -142,7 +145,7 @@ jlong CgroupV1Subsystem::memory_and_swap_limit_in_bytes() {
const char* format = "%s " JULONG_FORMAT;
GET_CONTAINER_INFO_LINE(julong, _memory->controller(), "/memory.stat", matchline,
"Hierarchical Memory and Swap Limit is : " JULONG_FORMAT, format, hier_memlimit)
if (hier_memlimit >= _unlimited_memory) {
if (hier_memlimit >= host_total_memsw) {
if (PrintContainerInfo) {
tty->print_cr("Hierarchical Memory and Swap Limit is: Unlimited");
}
Expand All @@ -159,7 +162,7 @@ jlong CgroupV1Subsystem::memory_and_swap_limit_in_bytes() {
jlong CgroupV1Subsystem::memory_soft_limit_in_bytes() {
GET_CONTAINER_INFO(julong, _memory->controller(), "/memory.soft_limit_in_bytes",
"Memory Soft Limit is: " JULONG_FORMAT, JULONG_FORMAT, memsoftlimit);
if (memsoftlimit >= _unlimited_memory) {
if (memsoftlimit >= os::Linux::physical_memory()) {
if (PrintContainerInfo) {
tty->print_cr("Memory Soft Limit is: Unlimited");
}
Expand Down
3 changes: 0 additions & 3 deletions hotspot/src/os/linux/vm/cgroupV1Subsystem_linux.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,6 @@ class CgroupV1Subsystem: public CgroupSubsystem {
CachingCgroupController * cpu_controller() { return _cpu; }

private:
julong _unlimited_memory;

/* controllers */
CachingCgroupController* _memory;
CgroupV1Controller* _cpuset;
Expand All @@ -111,7 +109,6 @@ class CgroupV1Subsystem: public CgroupSubsystem {
_cpu = new CachingCgroupController(cpu);
_cpuacct = cpuacct;
_memory = new CachingCgroupController(memory);
_unlimited_memory = (LONG_MAX / os::vm_page_size()) * os::vm_page_size();
}
};

Expand Down
11 changes: 0 additions & 11 deletions hotspot/src/os/linux/vm/osContainer_linux.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,6 @@ CgroupSubsystem* cgroup_subsystem;
* we are running under cgroup control.
*/
void OSContainer::init() {
jlong mem_limit;

assert(!_is_initialized, "Initializing OSContainer more than once");

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

_is_containerized = true;

}

const char * OSContainer::container_type() {
Expand Down
26 changes: 11 additions & 15 deletions hotspot/src/os/linux/vm/os_linux.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -184,21 +184,14 @@ julong os::Linux::available_memory() {
julong avail_mem;

if (OSContainer::is_containerized()) {
jlong mem_limit, mem_usage;
if ((mem_limit = OSContainer::memory_limit_in_bytes()) < 1) {
if (PrintContainerInfo) {
tty->print_cr("container memory limit %s: " JLONG_FORMAT ", using host value",
mem_limit == OSCONTAINER_ERROR ? "failed" : "unlimited", mem_limit);
}
}

jlong mem_limit = OSContainer::memory_limit_in_bytes();
jlong mem_usage;
if (mem_limit > 0 && (mem_usage = OSContainer::memory_usage_in_bytes()) < 1) {
if (PrintContainerInfo) {
tty->print_cr("container memory usage failed: " JLONG_FORMAT ", using host value", mem_usage);
}
}

if (mem_limit > 0 && mem_usage > 0 ) {
if (mem_limit > 0 && mem_usage > 0) {
avail_mem = mem_limit > mem_usage ? (julong)mem_limit - (julong)mem_usage : 0;
if (PrintContainerInfo) {
tty->print_cr("available container memory: " JULONG_FORMAT, avail_mem);
Expand All @@ -225,11 +218,6 @@ julong os::physical_memory() {
}
return mem_limit;
}

if (PrintContainerInfo) {
tty->print_cr("container memory limit %s: " JLONG_FORMAT ", using host value",
mem_limit == OSCONTAINER_ERROR ? "failed" : "unlimited", mem_limit);
}
}

phys_mem = Linux::physical_memory();
Expand Down Expand Up @@ -308,6 +296,14 @@ pid_t os::Linux::gettid() {
}
}

// Returns the amount of swap currently configured, in bytes.
// This can change at any time.
julong os::Linux::host_swap() {
struct sysinfo si;
sysinfo(&si);
return (julong)si.totalswap;
}

// Most versions of linux have a bug where the number of processors are
// determined by looking at the /proc file system. In a chroot environment,
// the system call returns 1. This causes the VM to act as if it is
Expand Down
5 changes: 3 additions & 2 deletions hotspot/src/os/linux/vm/os_linux.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,6 @@ class Linux {
static const int _vm_default_page_size;

static julong available_memory();
static julong physical_memory() { return _physical_memory; }
static void set_physical_memory(julong phys_mem) { _physical_memory = phys_mem; }
static int active_processor_count();

static void initialize_system_info();
Expand Down Expand Up @@ -154,6 +152,9 @@ class Linux {
static intptr_t* ucontext_get_sp(ucontext_t* uc);
static intptr_t* ucontext_get_fp(ucontext_t* uc);

static julong physical_memory() { return _physical_memory; }
static julong host_swap();

// For Analyzer Forte AsyncGetCallTrace profiling support:
//
// This interface should be declared in os_linux_i486.hpp, but
Expand Down
25 changes: 25 additions & 0 deletions hotspot/test/runtime/containers/docker/TestMemoryAwareness.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@

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

import com.oracle.java.testlibrary.Asserts;

public class TestMemoryAwareness {
private static final String imageName = Common.imageName("memory");
Expand Down Expand Up @@ -72,6 +74,7 @@ public static void main(String[] args) throws Exception {
"1G", Integer.toString(((int) Math.pow(2, 20)) * 1024),
"1500M", Integer.toString(((int) Math.pow(2, 20)) * (1500 - 1024))
);
testContainerMemExceedsPhysical();
} finally {
DockerTestUtils.removeDockerImage(imageName);
}
Expand All @@ -90,6 +93,28 @@ private static void testMemoryLimit(String valueToSet, String expectedTraceValue
.shouldMatch("Memory Limit is:.*" + expectedTraceValue);
}

// JDK-8292083
// Ensure that Java ignores container memory limit values above the host's physical memory.
private static void testContainerMemExceedsPhysical()
throws Exception {

Common.logNewTestCase("container memory limit exceeds physical memory");

DockerRunOptions opts = Common.newOpts(imageName);

// first run: establish physical memory in test environment and derive
// a bad value one power of ten larger
String goodMem = Common.run(opts).firstMatch("total physical memory: (\\d+)", 1);
Asserts.assertNotNull(goodMem, "no match for 'total physical memory' in trace output");
String badMem = goodMem + "0";

// second run: set a container memory limit to the bad value
opts = Common.newOpts(imageName)
.addDockerOpts("--memory", badMem);
Common.run(opts)
.shouldMatch("container memory limit (ignored: " + badMem + "|unlimited: -1), using host value " + goodMem);
}


private static void testMemorySoftLimit(String valueToSet, String expectedTraceValue)
throws Exception {
Expand Down