Skip to main content

Out of memory issues with Java Extend apps

Last updated on October 24, 2024

Overview

Java Extend app uses a garbage collector to automatically manage system memory. However, memory issues may still occur due to:

  • Inefficient data structures
  • Holding references onto large objects for far longer than necessary
  • Memory leaks
  • General high memory usage

Best practices

  1. Choose the right data structure for your needs, e.g., use Hashmap over TreeMap when sorting isn't required. For ArrayList, if you know the expected size, specify it during creation to minimize resizing operations that may be costly.
  2. Avoid String concatenation since it's immutable. Use StringBuilder instead.
  3. Remove objects from collections as soon as they are no longer required, especially large objects.
  4. Incorporate CPU and memory profiling into your development cycle to catch memory leaks and performance bottlenecks early.
  5. Optimize garbage collection and heap size. Tune the garbage collection and heap size based on your performance goals. Tuning the heap size for the initial and max heap (-Xms/Xmx) prevents resizing during runtime. Set the heap size to be large enough to reduce the excessive garbage collection activity.
  6. Minimize object creation or reuse objects whenever possible. Consider using pools for expensive objects that are frequently created and destroyed.
  7. Leverage lazy initialization to delay creation until it's needed to reduce initial memory consumption and improve startup time.
  8. Limit the amount of data retrieved from other sources.
  9. Avoid using native code as it often requires manual memory management which bypasses the Java virtual machine's (JVM) memory management. Incorrect usage of native code can also lead to memory leaks, crashes, and unexpected behaviors. When using native code, carefully manage native resources to ensure they are properly released.

To manage your CPU and memory usage, we recommend using the following tools:

IntelliJ Profiler

IntelliJ Profiler is a performance analysis tool with a range of profiling features designed to optimize code by analyzing CPU and memory usage. To integrate Intellij with your Extend app, follow these steps:

  1. Start the Extend application in IntelliJ by selecting Profile with IntelliJ Profiler.
  2. Run your application under Run > Profile [your application name] with IntelliJ Profiler.
  3. When the profiling is complete, click Stop Recording and Show Results. The CPU sampling - Flame Graph result appears. IntelliJ Profiler CPU Samples - Flame Graph
  4. Switch the results view between CPU samples and Memory samples. The following image shows the results in the Memory samples view. IntelliJ Profiler Memory Samples

For more information, refer to IntelliJ Profiler's documentation on CPU profiling and on finding memory leaks.

IntelliJ Profiler with VisualVM

You can use the IntelliJ Profiler with VisualVM to retrieve live results from Java Management Extensions (JMX) and from saved Java Flight Recorder (JFR) files. Follow these steps:

  1. In VisualVM, select the running application under Local and double-click on the application to connect. When connecting live, VisualVM shows the live monitoring results of the CPU and Memory usage.
  2. You can then proceed to:
  • Go to the Sampler tab to start sampling the CPU and memory.
    • CPU samples Sampler view on CPU
    • Memory samples Sampler view on memory
  • Go to the Profiler tab to find the memory usage of the objects all the way from to source method. Image shows a profiler view on memory

For more information, refer to VisualVM's documentation.

Docker Container

note

Depending on the build and version of your JVM, some features may not be supported (e.g., the ibm-semeru-runtimes:open-17-jre build may have heap dumping disabled). For more information about Docker's supported features, refer to Docker Container's documentation.

To use Docker Container for your Java Extend app, follow these steps:

  1. Open up a JMX connection for remote monitoring.
    1. Enable the JMX connection via JVM arguments. For our event-handler app template, you can enable the JMX connection via the JAVA_OPTS environment variable from docker-compose.yaml. - JAVA_OPTS=-Dcom.sun.management.jmxremote=true -Dcom.sun.management.jmxremote.port=9010 -Dcom.sun.management.jmxremote.rmi.port=9010 -Dcom.sun.management.jmxremote.local.only=false -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Djava.rmi.server.hostname=127.0.0.1
    2. Make sure to expose the JMX port, for example, add - "9010:9010" to docker-compose.yaml under Ports.
  2. Start the application using docker compose.
  3. Open VisualVM, then add the new JMX Connection and enter the address (e.g., localhost:9010 for the exposed JMX port configured in step 1).

Java Flight Recorder (JFR) and JDK Mission Control (JMC)

Java Flight Recorder (JFR) and JDK Mission Control (JMC) help you collect diagnostic data, including event logs all the way from the application startup. For more information about these tools, refer to Oracle's JFR Runtime Guide.

note

Double-check your JDK distribution for commercial licensing usage.

To use JFR and JDK for your Java Extend app, follow these steps:

  1. Open up a JMX connection for remote monitoring.

    1. Enable the JMX connection via JVM arguments. For our event-handler app template, you can enable the JMX connection via the JAVA_OPTS environment variable from docker-compose.yaml. - JAVA_OPTS=-Dcom.sun.management.jmxremote=true -Dcom.sun.management.jmxremote.port=9010 -Dcom.sun.management.jmxremote.rmi.port=9010 -Dcom.sun.management.jmxremote.local.only=false -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Djava.rmi.server.hostname=127.0.0.1
    2. To automatically capture a 60-second recording with JFR when starting your Java extend application, set the following environment variable: - JAVA_OPTS=-XX:StartFlightRecording=filename=/tmp/startupProfiling.jfr,duration=60s,settings=profile
    3. Make sure to expose the JMX port, for example, add - "9010:9010" to docker-compose.yaml under Ports.
  2. Open JMC and locate the generated JFR file. If the JFR file isn't local, you can copy from the Docker container.

  3. Start your analysis. Optionally, you can also instruct JFR to start and stop flight recording on demand.

    JMC - Java Application page

  • Sample results for Memory JMC - Memory

  • Sample results for Method Profiling JMC - Method Profiling

  • Sample results for Thread CPU Load under Event Types Tree JMC - Event Types Tree - Thread CPU Load

References

We recommend reading the following articles from Oracle's troubleshooting guide: