4 min read

Garbage collection in Java: best practices

This article explores two main garbage collection issues, explaining the causes and including examples, and highlights the best ways of minimizing them. It also provides several tips that you can follow to optimize performance in Java applications.

The author of this article is EPAM Senior Software Engineer Vaibhavi Deshpande.

Read other author’s articles:

In the first part of this article, we focused on the concept of garbage collection in Java, its history, and real-world examples of how it works.

Now, let's dive into two common garbage collection issues and explore best practices to mitigate them, using examples.

Two common garbage collection issues

1. Memory leaks

Memory leaks occur when objects are unintentionally kept in memory due to retained references, preventing their garbage collection. This can lead to excessive memory usage and potential out-of-memory errors.

Example: Consider a web application that manages user sessions. If the session objects are not properly invalidated or removed when a user logs out or becomes inactive, those objects may continue to be held in memory, causing a memory leak.

The invalidateSession() method

In the above example, when a user logs out or becomes inactive, the invalidateSession() method should be called to remove the session object from the sessions map, preventing memory leaks.

2. Performance problems

Inefficient use of memory or inappropriate garbage collection configurations can result in performance degradation. Frequent garbage collection cycles or long pauses can impact the responsiveness and throughput of an application.

Example: Suppose you have a real-time trading system where performance is critical. If the garbage collector is configured with settings that cause frequent stop-the-world pauses, it can disrupt the timely execution of trades and impact overall system performance.

Garbage collection best practices

  • Minimize object creation

    Creating unnecessary objects can put a strain on the garbage collector. Minimize object creation by reusing objects and utilizing immutable objects where possible.

    Example: In a high-performance financial application, instead of creating new BigDecimal objects for calculations, you can reuse a single instance by using the BigDecimal.valueOf() method.

    The BigDecimal.valueOf() method

    In the above example, the BigDecimal.valueOf() method creates a single BigDecimal instance for each iteration, reducing unnecessary object creation.

    • Use local variables

    Use local variables within methods rather than storing data in member variables. Local variables have a shorter lifespan and can be quickly garbage collected.

    Example: Instead of storing intermediate calculation results in member variables, use local variables within methods to avoid unnecessary object retention.

    The performCalculation() method

    In the above example, the result variable is a local variable within the performCalculation() method, ensuring that it is eligible for garbage collection once the method execution completes.

    • Object lifecycle awareness

    Understand the lifecycle of objects and ensure that references are properly cleared when objects are no longer needed. Avoid retaining references to objects longer than necessary.

    Example: When managing a cache, make sure to remove stale or expired objects from the cache to avoid unnecessary retention.

    The remove() method

    In the above example, the remove() method is used to explicitly remove objects from the cache when they are no longer needed.

    • Configure garbage collection

    Optimize garbage collection settings based on the specific requirements of your application. Different garbage collectors and their configurations can have varying impacts on performance, latency, and memory usage.

    Example: For latency-sensitive applications, consider using a low-pause garbage collector such as the Garbage-First (G1) collector and configure appropriate heap size and garbage collection parameters.

    The heap size 4GB

    In the above example, the JVM is configured to use the G1 garbage collector with a maximum heap size of 4GB for the MyApp application.

    Let’s focus on what the above command does:

    The command java -XX:+UseG1GC -Xmx4G -Xms4G is used to launch a Java application with specific JVM (Java Virtual Machine) options related to garbage collection and heap memory configuration. Let's break down the meaning of each option:

    • -XX:+UseG1GC: enables the use of the Garbage-First (G1) garbage collector. The G1 garbage collector is designed to provide low-pause garbage collection with good throughput by dividing the heap into regions and collecting them independently.
    • -Xmx4G: sets the maximum heap size to 4GB. The maximum heap size represents the upper limit of the memory that can be allocated to the Java application. In this case, it allocates 4GB of memory to the Java application.
    • -Xms4G: sets the initial heap size to 4GB. The initial heap size is the amount of memory allocated to the Java application at the start. By setting it equal to the maximum heap size (-Xmx4G), the JVM allocates the entire heap size specified from the beginning.

    By specifying these options when running a Java application, you configure the JVM to use the G1 garbage collector, set the maximum heap size to 4GB, and allocate the entire heap size at the start. These settings can help optimize the garbage collection behavior and memory allocation for the application.

    It's important to note that the values specified for -Xmx and -Xms options can be adjusted according to the specific requirements and available system resources of your application. The values can be specified in different units, such as megabytes (M) or gigabytes (G), depending on your needs.

    Overall, the java -XX:+UseG1GC -Xmx4G -Xms4G command is used to launch a Java application with specific garbage collection and memory allocation settings to achieve optimal performance and memory utilization.

    Conclusion

    To optimize memory usage and performance in Java applications, it is crucial to address common garbage collection issues. Mitigating memory leaks involves proper invalidation and removal of objects. To avoid performance problems, minimize object creation, utilize local variables, be aware of object lifecycles, and configure the garbage collector appropriately. By following these best practices, you can minimize common garbage collection issues and optimize memory usage and performance in your Java applications.