Java 单元测试:如何测量方法调用的内存占用

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/19785290/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-12 20:11:44  来源:igfitidea点击:

Java unit testing: how to measure memory footprint for method call

javaunit-testingjunitout-of-memorytestng

提问by Sergey Makarov

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?

这样做的正确方法是什么?或者这是不可能的/不可行的?

采纳答案by Andrey Chaschev

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.
  • 通过微基准(即jmh)找出您的方法需要多少内存。
  • 基于启发式估计的构建分配策略。有几种开源解决方案实现类大小估计,即ClassSize。一种更简单的方法是利用缓存来释放很少使用的对象(即 Guava 的缓存)。正如@EnnoShioji 所提到的,Guava 的缓存具有基于内存的驱逐策略。

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()
  1. 运行一个线程。
  2. 创建一个新数组来存储要分配的对象。所以这些对象在 GC 运行期间不会被收集。
  3. System.gc(), memoryBefore = runtime.totalMemory() - runtime.freeMemory()
  4. 分配您的对象。将它们放入数组中。
  5. System.gc(), memoryAfter = runtime.totalMemory() - runtime.freeMemory()

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

这是我在我的轻量级微基准测试工具中使用的一种技术,它能够以字节精度测量内存分配。

回答by Areo

To measure current memory usage use :

要测量当前内存使用情况,请使用:

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

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 GCwhich is unpredictable.

但是这种测量并不精确,但它可以为您提供很多信息。另一个问题GC是不可预测的。

回答by Enno Shioji

Here is an example from Netty which does something similar: MemoryAwareThreadPoolExecutor. Guava's cache classhas 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.

这是来自 Netty 的一个例子,它做了类似的事情:MemoryAwareThreadPoolExecutor。Guava 的缓存类也有一个基于大小的驱逐。您可以查看这些来源并复制它们在做什么。特别是,下面是 Netty 是如何估计对象大小的。本质上,您将估计在该方法中生成的对象的大小并进行计数。

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.

话虽如此,您合法地需要它是非常罕见的。在大多数情况下,通过限制给定点上可以存在的对象数量(例如,通过使用有界队列)来限制内存使用量就足够了,而且实现起来要简单得多。

回答by pasha701

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

您可以使用分析器(例如 JProfiler)按类查看内存使用情况。或者,如何提到 Areo,只需打印内存使用情况:

    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));

回答by Robin Keskisarkka

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.

这个问题有点棘手,因为 Java 可以在处理期间分配大量短期对象的方式,这些对象随后将在垃圾收集期间被收集。在接受的答案中,我们不能肯定地说垃圾收集已在任何给定时间运行。即使我们引入了循环结构,通过多次System.gc()调用,垃圾收集也可能在我们的方法调用之间运行。

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:

更好的方法是使用https://cruftex.net/2017/03/28/The-6-Memory-Metrics-You-Should-Track-in-Your-Java-Benchmarks 中建议的一些变体。 html,在那里System.gc()被触发,但我们也等待报告的 GC 计数增加:

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.

这仍然只给出在给定时间由您的代码实际分配的内存的近似值,但该值通常更接近人们通常感兴趣的值。

回答by mob

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.

这是在单独的线程中运行内存使用的示例代码。由于 GC 可以在进程运行时随时触发,这将记录每秒内存使用情况并报告使用的最大内存。

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

runnable是需要测量实际的过程,runTimeSecs是在预期的时间过程将运行。这是为了确保线程计算内存不会在实际进程之前终止。

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();
    }
}