Memory leak and memory consumption test in Java

Tan Bui
2 min readJul 4, 2020

Stateful lambdas keep references of its enclosing scope or external instances as arguments (see Lambda in memory). Lately, those references may be hard to follow, for example in nested lambdas, which introduces memory leak.

There are many other types of memory leaks, but Lambda is the one that draws my attention to testing against memory performance.

Garbage collection when testing

During tests, we’ll try to garbage-collect all unused instances before measuring Memory. But System.gc() doesn’t guarantee the result, as the Java Virtual Machine has made a best effort to reclaim space from all discarded objects.

A workaround is to keep running gc() until having a stable amount of used memory. Learning from Vaadin Flow, I suggest an implementation of this workaround:

Memory consumption

A basic script for testing memory consumption is:

  • Warm-up
  • Get initially used memory
  • Run the logic that consumes memory
  • Run runGC() then get the final used memory
  • The memory consumption is (final — initial) memory.

Here is an example of measuring the memory size of a String object:

You can replace new String() at line 10 by any other object you want to test. Again, this implementation gives a close-estimation of memory consumption for a type but not the exact number. The theoretical number can be calculated by field types in the class recursively.

With this example implementation, I can replace objectsArray and the for loop with other logic to see how much memory it takes, approximately.

Memory leak

With a small twist, I can test the Memory Leak of an execution by measuring its memory consumption after garbage-collected.

At line 4, when the Runnable is executing, there are local variables (in Stack) which hold references to newly-created instances (in Heap). After the execution, those variables are moved from Stack thus instances should be collected by runGC().

In that case, finalSize == initSize . In case instances are not fully garbage-collected, there is a memory leak and finalSize > initSize .

To illustrate, I create the memory leak-able `StaticTest` class:

Running the memory leak test by:

public class App {

public static void main(String[] args) {
long myLeak = MemoryLeakTest.getMemoryLeakAverage(() -> {
StaticTest.main();
}, 20);
System.out.println(myLeak + " bytes");
}

}

The average leaking amount I’ve got is 2950242 bytes. From Java VisualVM, Heap and Metaspace jump up quickly and remain high.

By removing static keyword in line 3 of StaticTest class, the problem is gone. I’ve got 184 bytes which is acceptable.

Memory tests can also be used for integration testing where you can measure the server’s memory leak when thousands of connections are closed.

There’s no way to measure memory leak exactly since System.gc() and Runtime.freeMemory() are approximate. However, it’s still worth writing tests for memory performance especially in large applications where memory leak is noticeable.

--

--

Tan Bui

Software Engineer @Smartly.io, phototaker, naturelover.