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

时间:2013-11-05 09:13:25

标签: java unit-testing junit out-of-memory testng

假设我有一个类进行一些繁重的处理,使用多个集合进行操作。我想要做的是确保这样的操作不会导致内存不足甚至更好我想设置一个可以使用多少内存的阈值。

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?
   }
}

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

7 个答案:

答案 0 :(得分:15)

我可以想到几个选项:

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

您还可以编写自己的基准测试来计算内存。想法是

  1. 运行一个线程。
  2. 创建一个新数组来存储要分配的对象。因此,在GC运行期间不会收集这些对象。
  3. System.gc()memoryBefore = runtime.totalMemory() - runtime.freeMemory()
  4. 分配您的对象。把它们放入阵列。
  5. System.gc()memoryAfter = runtime.totalMemory() - runtime.freeMemory()
  6. 这是我在lightweight micro-benchmark tool中使用的一种技术,它能够以字节精度测量内存分配。

答案 1 :(得分:13)

您可以使用分析器(例如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));

答案 2 :(得分:2)

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

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

这是一个很好的例子: get OS-level system information

但这种测量不准确,但它可以为您提供更多信息。 另一个问题是GC无法预测。

答案 3 :(得分:2)

以下是Netty的一个例子:MemoryAwareThreadPoolExecutor。番石榴的cache class也有基于规模的驱逐。您可以查看这些来源并复制他们正在做的事情。特别是,以下是Netty的estimating object sizes。实质上,您估计在方法中生成的对象的大小并保持计数。

获取整体内存信息(比如可用/使用多少堆)将帮助您确定分配给方法的内存使用量,但不会跟踪单个方法调用使用的内存量。

话虽如此,你合法地需要这个是非常罕见的。在大多数情况下,通过限制给定点处的对象数量(例如,通过使用有界队列)来限制内存使用是足够好的并且实现起来要简单得多。

答案 4 :(得分:0)

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

一种更好的方法是改用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);
}

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

答案 5 :(得分:0)

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

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

答案 6 :(得分:0)

估算内存使用量的最简单方法是使用 Runtime 类中的方法。

我建议不要依赖它,而仅将其用于近似估计。理想情况下,您应该只记录此信息并自行分析,而不应将其用于自动化测试或代码。

可能它不是很可靠,但在单元测试等封闭环境中,它可能会给您接近现实的估计。
特别是不能保证在调用 System.gc() 垃圾收集器后会在我们期望的时候运行(这只是对 GC 的建议),那里描述的 freeMemory 方法存在精度限制:{{3}可能还有更多注意事项。

解决方案:

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