Problem Types and Ways to Fix Them
Memory Leak in Java: How to Detect and Avoid
An in-depth overview of what memory leaks are in Java, how they cause, and workable strategies for preventing them.
The choice of working with Java, which opens up a wide range of possibilities for programming, is well-founded. This is a typeset, high-level programming language created by Sun Microsystems, later redeemed Oracle, with fairly flexible syntax. For more than 20 years, Java has maintained its popularity, taking one of the leading positions in the market of world technologies.
One of its advantages is automated memory management in Java, especially when compared to the C and C++ languages, which don’t have this function. It’s performed using the built-in garbage collector technology «Garbage Collector» or GC. To explain this important feature, it’s worth noting that the system accumulates in its memory objects that occupy memory but are not used. That tool significantly helps in the elimination of such a problem like a memory leak.
Of course, Java is not the only platform that has such an important and useful function as a garbage collector. But in addition to its other features, such as HotSpot optimizer or excellent backward compatibility, comparison — Java certainly stands out among other platforms.
So what’s the problem if Java has such a useful function of distribution and clearing of memory? Let’s see.
What are memory leak and their causes?
To better understand the essence of a direct example, you should start with the Java memory structure.
1. Memory structure and its cleanup
Java application data can be stored in spatial blocks such as «Stack» or «Heap.»
- Stack memory is a repository of store references to «Heap» elements and types of primitive value;
- Heap contains dynamic objects referenced by variables in the stack.
As a result, under default settings, a heap can occupy much more space in memory than a stack. However, these settings can be changed manually by setting the heap size below the stack value after increasing it.
Stack has the LIFO principle (enters last, leaves first). Whenever a new method is called with reference to an element or a primitive value, a block of memory is released at the top of the stack:
The heap, unlike the stack, is not cleaned by itself. So, the system is in dire need of a garbage collector function. Without its presence, we can manage only the size of the heap:
A reference to an object from a heap that may contain a stack variable becomes a suitable target over time for the garbage collector, depending on its type. And, when red-colored items appear in the heap memory, they can be assembled by the collector.
Example of garbage items in memory:
2. Memory leak
Java memory leak is a kind of error when GC leaves elements in the system that are not used. This may occur because of the inability to remove some garbage items that may be referenced in the stack.
A typical example of the leak:
This leak has a negative impact on the capacity to utilize the resources of the system and its overall productivity. If this problem is ignored, the system can completely exhaust data storage space, and end with an irreversible error, «Java Out Of Memory Error.»
It’s better to use tools of memory management to optimize it. The most relevant of them:
- OpenView Operations (OVO) by HP;
- JMETER by Apache;
- IBM Tivoli by IBM;
- JProbe Profiler by TechRepublic.
3. Causes of memory leaks in Java
Memory leaks in Java can occur due to unforeseen errors in the code, which keeps references to unwanted objects in the cloud. These links block GC functions. As a result, it’s not possible to clean the repository, which is not usefully used by these elements.
Main causes of Java memory leaks:
- Unlimited caching;
- Overflow of files in one session;
- Excess of replacement operating system pages;
- Bugs in the system of users' data;
- Inserting elements in a collection without deleting them;
- Non-reproducible ways of listening.
Memory leak in Java and its types
Various types of leaks are possible. Their differences are based on how they arise and what caused them.
Most common types of leaks:
1. Excessive use of static variables
The life cycle of static fields in Java usually corresponds to the application session time, without taking into account garbage collection with «ClassLoader» capabilities. When analyzing the heap of memory during command execution, we can observe an increase in memory between control points 1 and 2:
But stopping method «populateList()» at the point 3, in VisualVM — heap repository remains untreated. But if you don’t take into account «static» in line 2, it will change the memory values:
In this case, after manipulation with the method «populateList()», the heap memory is cleaned by the collector, as all references to objects are deactivated:
2. Availability of internal classes with reference to external
To initialize static classes, you always need examples from external classes. Each non-static default class contains a hidden reference to the class in which it’s saved. If you use an internal class object — it will not collect by GC, even if you close an external class object.
For example, take a class that contains a non-static value and has a reference to several volumetric elements. In this case, the creation of an internal class object has such statistical indicators:
Banal change of the value of the internal or anonymous class to a static, the memory values change radically. This can occur as a result of the contents of the internal class of references to objects external, thus blocking the main function of GC.
3. Unclosed resources in the app
Whenever we create a new connection or open a thread, the JVM always allocates space in memory for new threads or connections. Such connections may have session objects with a comprehensive database.
If we leave such resources not closed, we risk allowing the storage to become blocked. This creates the risk that the garbage collector will not be able to detect and remove these objects. And if you ignore the maintenance of stable and correct closing of resources, they will fill up the memory, up to the appearance of an error «Java Out Of Memory Error.»
4. Using finalize() methods
In the scheme, when we have a class with a redefined finalize() method, its object may not collect GC in time but will queue for deletion. In addition, incorrectly overridden code in finalize() and its speed mismatch to garbage collector can cause an error «OutOfMemoryError.»
For example, take a class with its redefined method finalize() and for which time is needed. If you have a large number of elements collected by GC, such a score of the heap is formed:
If you simply remove a redefined finalize(), you can get this value:
5. Using interned strings
In the Java system version 6, it was necessary to be careful about the use of volumetric strings. Version 7 moved the change of the string pool that moved to HeapSpace from PermGen. If you call a method «intern()» to read a large string — it is saved to a string pool in constant memory (PermGen). This method is stored in PermGen until the end of the session, which causes the application to run out of memory.
An example of permanent memory is when reading a string of a file without its international:
6. ThreadLocals engagement
ThreadLocal — a tool that creates stream security by closing the values of its variables. At the same time, all threads have a hidden link to the duplicate ThreadLocal variable, and save their copies instead of using the resource in all threads.
This feature, besides its usefulness — has errors. They can affect leaks when misused.
ThreadLocal variables must be collected by GC after deleting the thread that contains them. However, some system servers may not be able to use this feature right. This can occur because the server does not create a new thread for all requests but applies the entire thread pool.
Pools in servers reuse threads, making them inaccessible to the garbage collector and taking up memory.
7. Implementing incorrect equals() and hashCode()
In creating new classes, we can often encounter the override error of equals() and hashCode() methods. Such methods are commonly used by HashSet and HashMap, and if they contain override errors, this can cause excessive memory consumption.
For example, you can use the ORM Hibernate, which takes the equals() and hashCode() methods to process items and store them in the cache. In the event that these methods are not overridden, Hibernate will not analyze the items, and the cache will fill their copies, causing a memory leak.
Symptoms and Java memory leak detection
1. Memory leak symptoms
There are several suspicious points that may indicate a leak:
- persistent and unforeseen system failures;
- unstable application functionality support;
- an error «Java.lang.OutOfMemoryError» occurred during a long session;
- connection object removal by the system;
- significant reduction in overall system performance.
2. How to detect Java memory leak
In order to detect a leak, several tools and techniques are required, as well as a combination of them. There is a list of the trusted methods:
2.1. Memory profilers — tools that track files and items that take up space in the repository. They are able to detect leaks and analyze the correct distribution of the elements used in the system. Also, estimate the time taken to process away.
The most common tools for profiling Java memory:
- JProfiler by EJ-Technologies;
- Java VisualVM by Oracle Corporation;
- YourKit by Yourkit GmbH.
2.2. Engagement heap dump. This is a tool for creating instant and timely snapshots of the heap in the Java memory store. Such images are needed to control the number of objects used and their weight in memory. Also, the tool tracks the number of elements created by the system and what may affect the leak.
2.3. Activating the verbose garbage collection log. This tool is able to demonstrate changes to heap configuration in the repository and GC. It provides the most accurate features and performance of the application. And optimizes the collector’s performance by identifying suitable elements in the heap, its alternative methods, and JVM parameters.
For example, activate a verbose collection in app using JVM startup methods:
«-XX: +UseSerialGC -Xms1024m -Xmx1024m -verbose:gc»
The «-verbose:gc» argument activates the recording of collection information. The log, by default, is saved to stdout and produces rows for all GC. Also specify sequential GC, using the «-XX: +UseSerialGC» argument, and set the heap sizes.
This can help to detect a leak in a timely manner and configure application health metrics.
Examples of GC log activation can be found on the GitHub service.
How to fix memory leaks. Error correction methods
In order to address this problem, it is necessary first to consider the reason why it has arisen. Therefore, it is necessary to identify the type of leak and solve the problem based on its variety. For each types, there is a different scheme how for fix memory errors in Java:
1. How to resolve a memory error?
1.1. When using static variables:
Minimize the use of static fields in the system. In this case, instead of urgent loading of objects — you can use the «lazy» one.
1.2. If there are internal classes, with reference to external:
You can convert an internal class to a static class if it does not need external class elements.
1.3. If the resources of the application are not closed:
«finally» should be activated in a timely manner to complete the use of resources.
1.4. When using finalize() methods:
Should reduce to zero any work with finalists.
1.5. When using interned strings
Try to upgrade the Java app to the latest version. This can work by moving the pool of strings to the free place of the heap, after the 6th version. And to avoid the error «Out Of Memory Error», in work with bulk strings you can extend the «PermGen» size.
1.6. When using ThreadLocals
Stable cleaning of ThreadLocal variables when they are not needed. ThreadLocal that has the remove() property — removes variable values for all current threads. It must be closed in the «finally» block to ensure that it is deactivated.
1.7. When implementing incorrect equals() and hashCode()
When creating new elements, it is always optimal to override the equals() and hashCode() path.
2. Other ways to eliminate memory leaks
You can also apply other methods to combat memory leaks when there is no clear understanding of the reason for their occurrence.
2.1. Using benchmarking:
For a detailed analysis of code performance in Java, you can use its testing with benchmarking. In this way, it’s easy to assess the productivity of different ways of performing tasks by comparing their effectiveness. This gives preference to best practices, which will avoid unnecessary memory consumption.
2.2. Reference object app:
The option of applying special reference objects in Java instead of direct references to elements in the application. The use of such links contained in the package «java.lang.ref» allows the simple removal of superfluous objects by the garbage collector.
2.3. Code review:
Banal, but no less effective in some cases method — checking the code. Sometimes it helps one time to eliminate memory leaks.
2.4. Enabling profiling:
By using the above method, you can discover storage areas that can be usefully used to store application resources.
2.5. Verbose garbage collection:
Another way mentioned earlier. Including this mode, we can closely monitor the work of GC.
Compliance with such rules of storage space and its rational use can help eliminate situations that create Java surges back up programming and Java surges back up language. This is the basis for the normal operation of the system as a whole.
3. Error removing tools:
- Quest Foglight for Java (for heap monitoring);
- MAT (for heap dump analysis).
As the expert on working with Java reports, on the portal Habr — a quote:
“ After finding and fixing the leak, it will not be superfluous to go through all the steps again, analyze the memory, and report to the Memory Analyzer to make sure the fix helped.“
For a clear understanding of memory bugs in Java, more information can be found here:
Conclusion
So, analyzing the question — what is memory leak in Java, it becomes clear that it affects the system as a disease that strikes it and worsens its overall condition. Without treatment, it can lead to irreparable consequences. And since this is a very serious problem, though not visible at first sight — it needs to be identified and addressed in a timely manner.
The problem is not easily remedied if it is detected long after the first signs of its existence have emerged. There is no «magic pill» that will fix the leak once, even when involving the work of a highly qualified specialist in Java programming.
But if working with the app consistently involved the use of proven methods, profiling, tracking, Java memory management, and code verification — the appearance of such problems can be reduced to zero.
And have You ever encountered memory leaks in Java? Share your experience of identifying the type of problem and Your method of solving it in the comments!
Contact us to receive more information about memory leaks in Java and operative strategies for preventing them.
Originally published at https://jelvix.com.