Tuning Configs of Cloud Foundry’s Java Buildpack to Avoid Out-of-Memory Errors
Java apps created in accordance with 12-factors are supposed to run in Cloud Foundry without significant modifications. However, sometimes the defaults provided by the public buildpacks don’t work. In this post, we will look at two ways to resolve out-of-memory errors.
Memory allocation in a Java buildpack
Most Java applications do not need any additional memory configuration to run in a Cloud Foundry environment. In general, it is enough to set the MEMORY_LIMIT
environment variable or provide a manifest.yml
file with a required value.
MEMORY_LIMIT
specifies how much memory will be allocated for a container that will run the application. A Java buildpack uses this value to control the Java Runtime Environment’s (JRE) use of various regions of memory: heap, metaspace, native, and stack.
Memory allocated for these regions is calculated by the memory calculator, using memory-related properties of the Java buildpack.
By default, the Java buildpack specifies the following values for memory distribution.
- heap: 75%
- metaspace: 10%
- native: 10%
- stack: 5%
Check out the readme file of the OpenJDK JRE for an explanation of Java Runtime Environment’s memory sizes and weights, as well as how the Java buildpack calculates and allocates memory to the JRE for your app.
When a Java application is deployed and the native memory is not sufficient, the container will restart before it does its first full garbage collection. This is a clear sign of insufficient allocation of native memory.
There are two ways to solve this issue: a complicated one and a simple one.
Solution #1: Creating a custom buildpack
The first (complicated) way is to create a custom buildpack with updated memory-related JRE options and specify this buildpack in the deployment manifest. Cloud Foundry docs provide exhaustive instructions on how to do that.
This approach has some serious flaws. Creating and maintaining a separate custom version of the Java buildpack just for a single application is not very efficient. It is also far from being trivial.
Solution #2: Overriding buildpack configuration
The second, simpler approach is to override buildpack configuration with an environment variable. The name of the variable needs to match the configuration file you want to override, but without the .yml
extension and with the JBP_CONFIG
prefix. The value of the variable must be a valid inline YAML.
In case of a memory-heuristics property in open_jdk_jre.yml
, the variable might look like in the sample below.
JBP_CONFIG_OPEN_JDK_JRE: '[memory_calculator: {memory_heuristics: {heap: 65, metaspace: 20}}]'
It can be passed to Cloud Foundry directly.
cf set-env [APPLICATION_NAME] JBP_CONFIG_OPEN_JDK_JRE: '[memory_calculator: {memory_heuristics: {heap: 65, metaspace: 20}}]’
Note that you will have to run this command each time the app is redeployed. To avoid that and make the environment reproducible, specify the variable in the application’s manifest.
env: JBP_CONFIG_OPEN_JDK_JRE: '[memory_calculator: {memory_heuristics: {heap: 65, metaspace: 20}}]'
This way, the environment variables won’t get lost if the app gets deployed elsewhere. Don’t forget to restage the app after editing the files.
In a Cloud Foundry environment where apps are automatically restarted, you may not even notice that there is an issue with memory allocation—unless you are specifically looking for it. So, make sure the heap and native memory spaces are sized appropriately for the app. Specifying environment variables in application’s manifest should be the most efficient way of doing that.
About the author