41

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

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

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

4

7 回答 7

23

我可以想到几个选择:

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

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

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

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

于 2013-11-05T09:41:47.427 回答
20

您可以使用分析器(例如 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));
于 2013-11-05T09:43:42.410 回答
5

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

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

这是一个很好的例子: 获取操作系统级别的系统信息

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

于 2013-11-05T09:20:40.287 回答
2

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

获取整体内存信息(例如可用/使用的堆数量)将帮助您决定分配给方法的内存使用量,但不能跟踪各个方法调用使用了多少内存。

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

于 2013-11-05T09:20:46.850 回答
1

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

更好的方法是使用https://cruftex.net/2017/03/28/The-6-Memory-Metrics-You-Should-Track-in-Your-Java-Benchmarks 中建议的一些变体。 html , whereSystem.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);
}

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

于 2018-11-05T12:54:21.223 回答
0

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

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

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

解决方案:

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;
}
于 2021-01-06T20:37:45.763 回答
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();
    }
}
于 2019-08-09T19:51:02.437 回答