47

Assuming I have a class that does some heavy processing, operating with several collections. What I want to do is to make sure that such operation can't lead to out-of-memory or even better I want to set a threshold of how much memory it can use.

class MyClass()
{
   public void myMethod()
   {
      for(int i=0; i<10000000; i++)
      {
         // Allocate some memory, may be several collections
      }
   }
}

class MyClassTest
{
   @Test
   public void myMethod_makeSureMemoryFootprintIsNotBiggerThanMax()
   {
      new MyClass().myMethod(); 
      // How do I measure amount of memory it may try to allocate?
   }
}

What is the right approach to do this? Or this is not possible/not feasible?

Olimpiu POP
  • 5,001
  • 4
  • 34
  • 49
Sergey Makarov
  • 2,491
  • 2
  • 19
  • 23
  • @Steve P.: Getting overall memory usage won't allow you to tell for *what* the memory was used for. – Enno Shioji Nov 05 '13 at 09:28
  • 2
    Yep, but I can set the requirement like "this algorithm must not consume more than 100KB of RAM, should not depend on size of data to process". Idea is to enforce this by creating explicit unit test. – Sergey Makarov Nov 05 '13 at 09:34
  • But why would you set such a requirement? Resources are cheap, Java is designed towards that fact. You shouldn't be worrying about memory consumption until you have an actual problem. Other than that, even if you find a way to measure memory, you still don't have a way to measure that you are interpreting the results correctly and you created a realistic working environment to produce realistic results. You'll just get "a number" and not be any wiser really. – Gimby Nov 05 '13 at 10:10
  • 10
    @Gimby What is the harm if some curious dev is trying to measure two approaches - does it have to be in realistic working env before making any sense - I guess not. Some approaches in general take far less memory/CPU or both in any circumstance than other. Such analysis can come in handy to atleast get a rough idea. – prash Oct 25 '16 at 17:23
  • 6
    @Gimby "resources are cheap" is a rather presumptuous comment when you don't know the infrastructure and staffing budget for the solution – Alex R Jun 15 '19 at 23:51

8 Answers8

27

I can think of several options:

  • Finding out how much memory your method requires via a microbenchmark (i.e. jmh).
  • Building allocation strategies based on heuristic estimation. There are several open source solutions implementing class size estimation i.e. ClassSize. A much easier way could be utilizing a cache which frees rarely used objects (i.e. Guava's Cache). As mentioned by @EnnoShioji, Guava's cache has memory-based eviction policies.

You can also write your own benchmark test which counts memory. The idea is to

  1. Have a single thread running.
  2. Create a new array to store your objects to allocate. So these objects won't be collected during GC run.
  3. System.gc(), memoryBefore = runtime.totalMemory() - runtime.freeMemory()
  4. Allocate your objects. Put them into the array.
  5. System.gc(), memoryAfter = runtime.totalMemory() - runtime.freeMemory()

This is a technique I used in my lightweight micro-benchmark tool which is capable of measuring memory allocation with byte-precision.

Andrey Chaschev
  • 16,160
  • 5
  • 51
  • 68
  • the custom way is still an approximation though. even with running the GC before and after, there is a possibility for the young GC to be invoked in between those two calls (especially if the tested call is memory intensive), even if you allocate all the RAM as heap. also, there are different JVMs and different GCs (G1 is very different than mark & sweep for example) so I don't know how reliable results would be... I like the idea of a custom GC.If JMH can do it though, it's better to use it (as you say). – fabien Jun 28 '16 at 03:41
  • 1
    In practice, it can be helpful to call System.gc() multiple times before allocating memory. Quite obviously, the exact behavior will also depend on the JVM and the GC. – Christian Grün Nov 23 '17 at 14:31
27

You can use profiler (for ex. JProfiler) for view memory usage by classes. Or , how mentioned Areo, just print memory usage:

    Runtime runtime = Runtime.getRuntime();
    long usedMemoryBefore = runtime.totalMemory() - runtime.freeMemory();
    System.out.println("Used Memory before" + usedMemoryBefore);
        // working code here
    long usedMemoryAfter = runtime.totalMemory() - runtime.freeMemory();
    System.out.println("Memory increased:" + (usedMemoryAfter-usedMemoryBefore));
pasha701
  • 6,831
  • 1
  • 15
  • 22
  • 3
    Small tip for further readers : values are given in bytes, divide by 1000000 to get values in MB. – Marc_Alx Jun 14 '16 at 14:07
  • @pasha701 small problem with using this approach, runtime.totalMemory() gives memory assigned to jvm at that instance. This may increased overtime after execution of working code. In that case (usedMemoryAfter-usedMemoryBefore) may even give out negative values in some scenarios. – 95_96 Oct 08 '20 at 23:41
  • 1
    @Marc_Alx: would preceisely be 1024*1024 == 1048576 – MD. Mohiuddin Ahmed Jun 25 '22 at 16:14
5

To measure current memory usage use :

Runtime.getRuntime().freeMemory() , Runtime.getRuntime().totalMemory()

Here is a good example: get OS-level system information

But this measurement is not precise but it can give you much information. Another problem is with GC which is unpredictable.

Community
  • 1
  • 1
Areo
  • 928
  • 5
  • 12
3

Here is an example from Netty which does something similar: MemoryAwareThreadPoolExecutor. Guava's cache class has also a size based eviction. You could look at these sources and copy what they are doing. In particular, Here is how Netty is estimating object sizes. In essence, you'd estimate the size of the objects you generate in the method and keep a count.

Getting overall memory information (like how much heap is available/used) will help you decide how much memory usage to allocate to the method, but not to track how much memory was used by the individual method calls.

Having said that, it's very rare that you legitimately need this. In most cases, capping the memory usage by limiting how many objects can be there at a given point (e.g. by using a bounded queue) is good enough and is much, much simpler to implement.

Enno Shioji
  • 26,542
  • 13
  • 70
  • 109
2

This question is a bit tricky, due to the way in which Java can allocate a lot of short-lived objects during processing, which will subsequently be collected during garbage collection. In the accepted answer, we cannot say with any certainty that garbage collection has been run at any given time. Even if we introduce a loop structure, with multiple System.gc() calls, garbage collection might run in between our method calls.

A better way is to instead use some variation of what is suggested in https://cruftex.net/2017/03/28/The-6-Memory-Metrics-You-Should-Track-in-Your-Java-Benchmarks.html, where System.gc() is triggered but we also wait for the reported GC count to increase:

long getGcCount() {
    long sum = 0;
    for (GarbageCollectorMXBean b : ManagementFactory.getGarbageCollectorMXBeans()) {
        long count = b.getCollectionCount();
        if (count != -1) { sum += count; }
    }
    return sum;
}

long getReallyUsedMemory() {
    long before = getGcCount();
    System.gc();
    while (getGcCount() == before);
    return getCurrentlyAllocatedMemory();
}

long getCurrentlyAllocatedMemory() {
    final Runtime runtime = Runtime.getRuntime();
    return (runtime.totalMemory() - runtime.freeMemory()) / (1024 * 1024);
}

This still gives only an approximation of the memory actually allocated by your code at a given time, but the value is typically much closer to what one would usually be interested in.

1

Here is a sample code to run memory usage in a separate thread. Since the GC can be triggered anytime when the process is running, this will record memory usage every second and report out the maximum memory used.

The runnable is the actual process that needs measuring, and runTimeSecs is the expected time the process will run. This is to ensure the thread calculating memory does not terminate before the actual process.

public void recordMemoryUsage(Runnable runnable, int runTimeSecs) {
    try {
        CompletableFuture<Void> mainProcessFuture = CompletableFuture.runAsync(runnable);
        CompletableFuture<Void> memUsageFuture = CompletableFuture.runAsync(() -> {


            long mem = 0;
            for (int cnt = 0; cnt < runTimeSecs; cnt++) {
                long memUsed = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
                mem = memUsed > mem ? memUsed : mem;
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            ;
            System.out.println("Max memory used (gb): " + mem/1000000000D);
        });

        CompletableFuture<Void> allOf = CompletableFuture.allOf(mainProcessFuture, memUsageFuture);
        allOf.get();
    } catch (Exception e) {
        e.printStackTrace();
    }
}
mob
  • 567
  • 5
  • 12
1

The easiest way to estimate memory usage is to use methods from Runtime class.

I suggest not to rely on it, but use it only for approximate estimations. Ideally you should only log this information and analyze it on your own, without using it for automation of your test or code.

Probably it isn't very reliable, but in closed environment like unit test it may give you estimate close to reality.
Especially there is no guarantee that after calling System.gc() garbage collector will run when we expect it (it is only a suggestion for GC), there are precision limitations of the freeMemory method described there: https://stackoverflow.com/a/17376879/1673775 and there might be more caveats.

Solution:

private static final long BYTE_TO_MB_CONVERSION_VALUE = 1024 * 1024;

@Test
public void memoryUsageTest() {
  long memoryUsageBeforeLoadingData = getCurrentlyUsedMemory();
  log.debug("Used memory before loading some data: " + memoryUsageBeforeLoadingData + " MB");
  List<SomeObject> somethingBigLoadedFromDatabase = loadSomethingBigFromDatabase();
  long memoryUsageAfterLoadingData = getCurrentlyUsedMemory();
  log.debug("Used memory after loading some data: " + memoryUsageAfterLoadingData + " MB");
  log.debug("Difference: " + (memoryUsageAfterLoadingData - memoryUsageBeforeLoadingData) + " MB");
  someOperations(somethingBigLoadedFromDatabase);
}

private long getCurrentlyUsedMemory() {
  System.gc();
  return (Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()) / BYTE_TO_MB_CONVERSION_VALUE;
}
luke
  • 3,435
  • 33
  • 41
  • 1
    This is not very accurate, as System.gc() is only a "request" and does not happen on the spot. You never know when gc really will run. – R.A Nov 30 '21 at 13:35
  • @R.A Yes, you are right. I wrote about it in the answer. This method is simple and it doesn't require any extra tools, but unfortunately It might be not accurate at all. I had a case where It looked like It works, but It was a very simple application and my findings were based purely on my observations without having proof that it works accurately. – luke Dec 30 '21 at 22:23
0

Many of the other answers warn about GC being unpredictable. However, since Java 11, the Epsilon garbage collector has been included in the JVM, which performs no GC.

Specify the following command-line options to enable it:

-XX:+UnlockExperimentalVMOptions 
-XX:+UseEpsilonGC

Then you can be sure that garbage collection will not interfere with the memory calculations.