By default, Docker containers have no resource constraints and can use as much as the host’s kernel memory and scheduler allows but there could be scenarios where you have to set it explicitly. For example in case if your container consumes too much of the host machine’s memory and if the kernel detects that there is not enough memory to perform system functions, the kernel would take over and start killing processes to free up memory.
This could effectively bring your application down, in this post we are going to look at how to set the memory limit for containers and in specific how to address the challenges in case of running Java applications on Docker containers.
Quick Snapshot
This quickstart assumes basic understanding of Docker concepts, please refer to earlier posts for understanding on Docker & how to install and containerize applications.
To limit memory for the container, we can use the --memory
flag or just -m
during the startup of the container. For example, we set the memory limit of NGINX server to only 256 MB of RAM.
To check the memory usage run docker stats
command to check the same.
If you get below warning message during docker run
command then cgroups swapping is disabled by default. cgroups (control groups) is basically a Linux kernel setting through which you can set limits the user accounts for the resource usage (CPU, memory, disk I/O, network, etc.) for a collection of processes. By this, you can isolate the processes and their resource usage from each other.
WARNING: Your kernel does not support swap limit capabilities or the cgroup is not mounted. Memory limited without swap.
For enabling swap, add/modify the below line in /etc/default/grub
file and update grub configuration by sudo update-grub
command.
GRUB_CMDLINE_LINUX="cgroup_enable=memory swapaccount=1"
For the changes to take effect, reboot the docker host machine.
If you have multiple containers and if you’re using compose to start the containers, follow the next section on how to set memory limits.
Below is the sample compose file for sample application with Angular as front end, Spring Boot as API, and Postgres as Database.
version: '3' services: ui: build: context: . dockerfile: UIDockerfile ports: - '4200:4200' networks: - samplenet links: - 'api:api' api: build: context: . dockerfile: AppDockerfile ports: - '8080:8080' depends_on: - db - rabbitmq networks: - samplenet links: - 'db:db' db: build: context: . dockerfile: DBDockerfile volumes: - 'postgresdb:/var/lib/postgresql/data' environment: POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres POSTGRES_DB: testdb ports: - '5432:5432' healthcheck: test: - CMD-SHELL - 'pg_isready -U postgres' interval: 10s timeout: 5s retries: 5 networks: - samplenet rabbitmq: image: 'rabbitmq:3.5.3-management' container_name: rabbitmq2 ports: - '5672:5672' - '15672:15672' networks: - samplenet networks: samplenet: null volumes: postgresdb: {}
Above compose file defines 4 services as below:
ui
: this is for angular application,it exposes port 4200
on the container to port 4200
on the host machine.api
: this is for spring boot application,it exposes port 8080
on the container to port 8080
on the host machine. Note this is dependent on db
and rabbitmq
services.db
: this is for postgresdb application,it exposes port 5432
on the container to port 5432
on the host machine.rabbitmq
: Uses rabbitmq:3.5.3-management
public image from Dockerhub registry and uses ports 5672,15672
samplenet
network andpostgresdb
definition is for db
servicehealthcheck
section for db service to keep tab on the health of the databasedepends_on
denotes the service dependencies. When you start the services, compose would start the dependent services as well.For example, if you want to set memory limits for particular container say api
services. We can use mem_limit
option like below:
api: build: context: . dockerfile: AppDockerfile ports: - '8081:8081' depends_on: - db - rabbitmq networks: - samplenet mem_limit: 1024MB links: - 'db:db'
Above option would work only if you’re using compose version 2.x
starting from 3.x
, compose is aligned with Swarm and you have to use resources
option like below
api: build: context: . dockerfile: AppDockerfile ports: - '8081:8081' depends_on: - db - rabbitmq networks: - samplenet resources: limits: memory:1024M links: - 'db:db'
Having seen how to set memory limits for containers, the next step is to address the memory-related challenges with respect to Java applications running on containers. I am sure, most of you who have running applications on Docker have faced Out of memory exceptions or improper heap memory issues. In the next section, we are going to check out how to resolve those issues.
For setting memory limits, like in any conventional Java application the maximum Java heap size can be set explicitly via JVM command-line option -Xmx
. When running applications on Docker, you can use the same command-line option to restrict the memory but consider the scenario that you have not mentioned any of the JVM command-line options, when a Java application is using Java SE 8u121 and earlier on Docker container, the JVM would automatically use the underlying host configuration of memory i.e., JVM would not know that its running on container and more likely your Java process would get killed if its taking too much memory.
Luckily from Java SE 8u131 and in later editions, JVM is made Docker aware with respect to Docker memory limits and starts to adjust like its running on the bare machine. To enable this, there are few command-line options.
-XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap -XX:MaxRAMFraction=1
If the above JVM command-line options are specified and -Xmx is not specified, the JVM will look at the Linux cgroup configuration for setting up the memory limits. With -XX:MaxRAMFraction=1
set to 1, we are instructing JVM to use almost all the available memory as max heap.
In the compose file, you can pass these options as an environment variable like in the example below :
environment:
- "JAVA_OPTS=-XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap -XX:MaxRAMFraction=1"
Next steps, we will check if JVM is able to detect how much memory is available when running inside a Docker container for different JDKs. We are going to run the container with 1GB memory and measure how much Max Heap is set.
For JDK 10, -XX:MaxRAM
parameter is deprecated and JVM will correctly detect the memory.
Congrats! Today we have learned how to set memory limit for your Java containers and also we have analyzed and set JVM environment variables for different JDKs.
Before we close off, let us look at key considerations while setting up memory.
docker stats
command to analyze the container’s runtime metrics. It supports CPU, memory usage, memory limit, and network IO metrics./sys/fs/cgroup.
Like this post? Don’t forget to share it!
In today's digital-first world, businesses must adopt effective strategies to stay competitive. Social media marketing…
62% of UX designers now use AI to enhance their workflows. Artificial intelligence (AI) rapidly…
The integration of artificial intelligence into graphic design through tools like Adobe Photoshop can save…
The cryptocurrency trading world has grown significantly in recent years, with automation playing a key…
The non-fungible token (NFT) market has witnessed explosive growth over the past few years, transforming…
There are few things as valuable to a business as well-designed software. Organizations today rely…
This website uses cookies.