How to set memory limit for your Java containers?
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.
#1. Setting memory limit for individual containers
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.
#2. Setting memory limit on Docker Compose
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 port4200
on the container to port4200
on the host machine.api
:Â this is for spring boot application,it exposes port8080
on the container to port8080
on the host machine. Note this is dependent ondb
andrabbitmq
services.db
: this is for postgresdb application,it exposes port5432
on the container to port5432
on the host machine.rabbitmq
: Usesrabbitmq:3.5.3-management
public image from Dockerhub registry and uses ports5672,15672
- All the above services uses
samplenet
network and - Persistent volume
postgresdb
definition is fordb
service - Added
healthcheck
section for db service to keep tab on the health of the database depends_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.
Does JVM know whether it is running on container?
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.
#1.Open JDK 8
#2.Open JDK 9
#3.Open JDK 10
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.
Key considerations while setting memory
- Run performance/load tests to understand the memory requirements of your application and configure the memory limit values and they should be refined iteratively. If there are memory leaks, fix those ones before moving on to containers.
- Swap is slower and less performant than memory but it can provide a buffer if running out of memory.
- Use memory limits for your containers as explained above so that it does not overuse the host memory.
- Check container runtime metrics. Use
docker stats
command to analyze the container’s runtime metrics. It supports CPU, memory usage, memory limit, and network IO metrics. - Analyze cgroup metrics to understand the internals. In the case of Linux Containers, control groups are used to track groups of processes. CPU, memory, and block I/O usage metrics are exposed via pseudo-filesystem like
/sys/fs/cgroup.
Like this post? Don’t forget to share it!
Additional Resources:
- Full list of Compose commands
- Compose configuration file reference
- Most Popular courses of 2019
- Google Cloud Courses Collection
- Trending Skill: Deep Learning Course Collection
- Trending Skill: Python Curated Course Collection
- ULTIMATE GUIDE to Coursera Specializations That Will Make Your Career Better (Over 100+ Specializations covered)
- Ultimate Guide to Data Science Courses (Over 65+ courses covered)
Average Rating