Unexpected Result from using getRuntime().freeMemory()

Viewed 96

I was planning to use getRuntime().freeMemory() to get a rough estimate of how much memory is consumed by a java method . I was trying to achieve it with the following:

long beforeUsedMem=Runtime.getRuntime().totalMemory()-Runtime.getRuntime().freeMemory();
foo();
long afterUsedMem=Runtime.getRuntime().totalMemory()-Runtime.getRuntime().freeMemory();
long actualMemUsed=afterUsedMem-beforeUsedMem;

For testing if this could actually work, I ran the code below

long beforeUsedMem=Runtime.getRuntime().totalMemory()-Runtime.getRuntime().freeMemory();
List<Integer> list = new ArrayList<>();
for(int i = 0 ;i<1000;i++)
  list.add(i);

long afterUsedMem=Runtime.getRuntime().totalMemory()-Runtime.getRuntime().freeMemory();
long actualMemUsed=afterUsedMem-beforeUsedMem;
System.out.println("mem used "+actualMemUsed);

However, the output of the program comes out to be

mem used 0

However, if I loop from 0 -> 100000 , the output comes out to be

mem used 2684368

Can anyone explain why jvm may be working like this?

1 Answers

The problem is that getRuntime().freeMemory() doesn't do what you think it is doing.

The JVM will allocate memory in chunks as it needs them, and depending on the GC you've used, will potentially return memory to the OS as well. So the results that you see from the memory options are dependent on what the JVM is doing, and not what an individual method does.

If you want to measure the amount of objects allocated by a Java method, you should look at JMH and specifically the GC profiler. This will run your method repeatedly, and then take an average based on information gathered by the garbage collection runs through JMX.

I wrote a blog post on the performance of StringBuilder which used JMH and prof gc. The source is available at GitHub for reference.

The TL;DR is that you want something like:

@State(Scope.Benchmark)
public class StringBuilderBenchmark {
    
    @Benchmark
    public String testEmptyBuffer() {
        StringBuffer buffer = new StringBuffer();
        return buffer.toString();
    }
}

You then compile/build this with JMH using the mvn jmh arhectype and run it with prof gc:

java -jar target/benchmarks.jar \
   -wi 5 -tu ns -f 1 -bm avgt -prof gc

It will then run the method repeatedly and give you an average of the number of bytes allocated per invocation of your method.

Note that it will not measure one-off initialisations required; if, for example, you lazily allocate a giant Map with a million entries, it won't be counted by this process.

More information on JMH is available on the JMH homepage.

Related