java - 如何真正对 Java 应用程序的内存使用情况进行基准测试

标签 java performance memory garbage-collection benchmarking

我想比较 Java 程序的不同实现在内存使用效率方面的差异。有不同的使用场景制定为 JUnit 测试用例。实际上,所有代码都是开源的:https://github.com/headissue/cache2k-benchmark

获取 Java 程序已用内存的一般智慧是这样的:Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory(),当然是也可以使用 JMX 接口(interface)获取这些值。

但是,已用内存的确定值并不可靠。可能的原因:

  • 可能有未收集的垃圾
  • 有碎片,如果GC没有做compaction

到目前为止,我尝试切换到串行 GC 并在读取值之前使用 Runtime.getRuntime().gc() 强制进行垃圾收集。我已将实验代码放在:https://github.com/cruftex/java-memory-benchmark

如果我在读取值之前执行三个 gc 调用,我会得到这个输出(mvn test | grep loopCount with jdk1.7.0_51):

testBaseline1: used=1084168, loopCount=0, total=124780544
testBaseline2: used=485632, loopCount=0, total=124780544
testBaseline3: used=483760, loopCount=0, total=124780544
testBaseline4: used=483800, loopCount=0, total=124780544
testBaseline: used=484160, loopCount=0, total=124780544
test100MBytes: used=105341496, loopCount=0, total=276828160
test127MBytes: used=133653088, loopCount=0, total=469901312
test27MBytes: used=28795528, loopCount=0, total=317755392
test10MBytes: used=10969776, loopCount=0, total=124784640

通过四个 gc 调用( checkin 时)我得到:

testBaseline1: used=483072, loopCount=0, total=124780544
testBaseline2: used=483728, loopCount=0, total=124780544
testBaseline3: used=483768, loopCount=0, total=124780544
testBaseline4: used=483808, loopCount=0, total=124780544
testBaseline: used=483848, loopCount=0, total=124780544
test100MBytes: used=105341504, loopCount=0, total=276828160
test127MBytes: used=133653096, loopCount=0, total=469901312
test27MBytes: used=28795536, loopCount=0, total=139239424
test10MBytes: used=10969784, loopCount=0, total=124784640

因此根据经验表明,通过四次 GC 调用,结果似乎是正确的。 从 GC 统计输出中我可以看到第一次 GC 填充了永久空间,第四次 GC 调用减少了它:

2015-01-08T02:30:35.069+0100: [Full GC2015-01-08T02:30:35.069+0100: [Tenured: 0K->1058K(83968K)
2015-01-08T02:30:35.136+0100: [Full GC2015-01-08T02:30:35.136+0100: [Tenured: 1058K->1058K(83968K)
2015-01-08T02:30:35.198+0100: [Full GC2015-01-08T02:30:35.198+0100: [Tenured: 1058K->1058K(83968K)
2015-01-08T02:30:35.263+0100: [Full GC2015-01-08T02:30:35.264+0100: [Tenured: 1058K->471K(83968K)

获取内存使用值的最终代码是:

try {
  Runtime.getRuntime().gc();
  Thread.sleep(55);
  Runtime.getRuntime().gc();
  Thread.sleep(55);
  Runtime.getRuntime().gc();
  Thread.sleep(55);
  Runtime.getRuntime().gc();
  Thread.sleep(55);
} catch (Exception ignore) { }
long _usedMem;
long _total;
long _total2;
long _count = -1;
// loop to get a stable reading, since memory may be resized between the method calls
do {
  _count++;
  _total = Runtime.getRuntime().totalMemory();
  try {
    Thread.sleep(12);
  } catch (Exception ignore) { }
  long _free = Runtime.getRuntime().freeMemory();
  _total2 = Runtime.getRuntime().totalMemory();
  _usedMem = _total - _free;
} while (_total != _total2);
System.out.println(_testName + ": used=" + _usedMem + ", loopCount=" + _count + ", total=" + _total);

我非常不确定这种方法是否一直都能产生可靠的结果。所以一些问题:

  • 是否有一些最佳实践可以从 Java 程序中获得可靠且可比较的基准值?
  • 对于如何针对该用例调整(或实际上失调)GC 有什么想法吗?
  • 是否有可靠的来源和可靠的行为来解释所需的四次 GC 调用? (顺便说一句:Java 8 的执行方式相同)
  • 有没有办法让 JVM 说:“尽最大可能进行垃圾收集,我会等待”?
  • 一般来说,对于问题陈述,什么可能是最“面向 future ”和最可靠的解决方案?

更新:

虽然上面的一些问题是GC相关的,但实际问题不是。我喜欢找出应用程序在单个时间点的内存使用情况。一种可能的解决方案还包括对所有对象进行深度搜索并汇总大小。

更新 2:

与此同时,我确实写了很多关于该问题的博客文章,讨论了如何测量实际内存使用情况的不同方法:

https://cruftex.net/2017/03/28/The-6-Memory-Metrics-You-Should-Track-in-Your-Java-Benchmarks.html

最佳答案

我也为这个问题苦苦挣扎,想知道是否有任何标准方法。

我能做的最好的事情就是告诉 JVM 通过在运行之后和下一次运行之前调用以下方法来尽可能多地收集垃圾:

GcFinalization.awaitFullGc();

此方法来自 Guava test-lib 包,可以将其添加为 Maven 依赖项:

 <dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava-testlib</artifactId>
    <version>18.0</version>
</dependency>

实现看起来像这样:

public static void awaitFullGc() {
   final CountDownLatch finalizerRan = new CountDownLatch(1);
   WeakReference<Object> ref = new WeakReference<Object>(
      new Object() {
         @Override protected void finalize() { finalizerRan.countDown(); }
      });

   await(finalizerRan);
   awaitClear(ref);

   // Hope to catch some stragglers queued up behind our finalizable object
   System.runFinalization();
 }

这为我提供了每次运行的非常一致的结果,并使 CPU 用户时间(来自 ThreadMXBean)非常接近纳米时间(来自 System.currentTimeMills)。在这些测量中,我主要关心的是运行时间,但与中间没有此调用的版本相比,内存使用情况也是一致的。

关于java - 如何真正对 Java 应用程序的内存使用情况进行基准测试,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/27831778/

相关文章:

java - 你能在字符串拆分中使用零宽度匹配正则表达式吗?

Java - 在没有 BC 的情况下以编程方式签署证书

java - 在回收站列表中处理过期的有效方法

c# - 将字符串转换为可执行代码性能

c# - 在 IEnumerable 上调用 Count 是否会迭代整个集合?

c# - block 分配

java - 如何在 Liferay serveResource(-, -) 方法中从 AJAX 请求下载文件

sql-server - 聚集索引和非聚集索引实际上意味着什么?

c - linux同进程下的线程如何分配stack或内存

java - 分配大的新字符串时如何使用更少的内存