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

Java单元测试:如何测量方法调用的内存占用,java,unit-testing,junit,out-of-memory,testng,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 } } } cl

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

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?
   }
}
class MyClass()
{
公共方法()
{

对于(int i=0;i来测量当前内存使用情况:

Runtime.getRuntime().freemory()
Runtime.getRuntime().totalMemory()

下面是一个很好的例子:

但是这个测量并不精确,但它可以给你很多信息。
另一个问题是不可预测的
GC

这里有一个来自Netty的例子,它做了类似的事情:。Guava's也有一个基于大小的逐出。你可以查看这些源并复制它们正在做的事情。特别是,下面是Netty的情况。本质上,你要估计你在方法中生成的对象的大小d和保持计数

获取总体内存信息(如可用/使用的堆数量)将帮助您决定分配给方法的内存使用量,而不是跟踪单个方法调用使用的内存数量


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

我可以想出几个选项:

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

  • 运行一个线程
  • 创建一个新数组来存储要分配的对象。这样在GC运行期间不会收集这些对象
  • System.gc()
    memoryBefore=runtime.totalMemory()-runtime.freemory()
  • 分配对象。将它们放入数组中
  • System.gc()
    memoryAfter=runtime.totalMemory()-runtime.freemory()
  • 这是我在my中使用的一种技术,能够以字节精度测量内存分配。

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

    这个问题有点棘手,因为Java可以在处理过程中分配大量短期对象,这些对象随后将在垃圾收集过程中被收集。在公认的答案中,我们不能确定垃圾收集是否在任何给定时间运行。即使我们引入循环结构,使用multiple
    System.gc()
    调用时,垃圾收集可能会在方法调用之间运行

    更好的方法是使用中建议的一些变体,其中会触发
    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);
    }
    

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

    下面是一个在单独线程中运行内存使用情况的示例代码。由于进程运行时可以随时触发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();
        }
    }
    
    public void recordMemoryUsage(Runnable Runnable,int runTimeSecs){
    试一试{
    CompletableFuture mainProcessFuture=CompletableFuture.runAsync(可运行);
    CompletableFuture memUsageFuture=CompletableFuture.runAsync(()->{
    长记忆=0;
    对于(int cnt=0;cntmem?memUsed:mem;
    试一试{
    时间单位。秒。睡眠(1);
    }捕捉(中断异常e){
    e、 printStackTrace();
    }
    }
    ;
    System.out.println(“使用的最大内存(gb):”+mem/1000000000D);
    });
    CompletableFuture allOf=CompletableFuture.allOf(mainProcessFuture,memUsageFuture);
    allOf.get();
    }捕获(例外e){
    e、 printStackTrace();
    }
    }
    
    估计内存使用量的最简单方法是使用运行时中的方法。 我建议不要依赖它,而只用于近似估计。理想情况下,您应该只记录这些信息并自己分析,而不使用它来自动化测试或代码。

    它可能不太可靠,但在单元测试这样的封闭环境中,它可能会让您的估计更接近现实。
    特别是,不能保证在调用
    System.gc()
    垃圾收集器之后,它会在我们期望的时候运行(这只是对gc的一个建议),这里描述的
    freemory
    方法存在精度限制:可能还有更多的警告

    解决方案:
    private static final long BYTE_TO_MB_CONVERSION_VALUE=1024*1024;
    @试验
    公共无效内存测试(){
    long MemorySageBeforLoadingData=getCurrentlyUsedMemory();
    log.debug(“加载某些数据之前使用的内存:“+memoryUsageBeforeLoadingData+”MB”);
    列出一些
    
    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;
    }