如何真正基准测试Java应用程序的内存使用情况

时间:2015-01-08 01:55:10

标签: java performance memory garbage-collection benchmarking

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

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

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

  • 可能有未收集的垃圾
  • 如果GC没有压缩,则存在碎片

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

如果我在读取值之前进行了三次gc调用,我会得到此输出(mvn test | grep loopCount和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次调用(已签入),我得到:

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:“做最好的垃圾收集,我会等”?
  • 一般来说,问题陈述可能是最具“未来证明”和可靠的解决方案吗?

更新

虽然上面的一些问题与GC有关,但实际问题并非如此。我想找出单个时间点的应用程序的内存使用情况。一种可能的解决方案还是对所有对象进行深度搜索并总结尺寸。

更新2:

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

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

3 个答案:

答案 0 :(得分:5)

我也在努力解决这个问题,并有兴趣知道是否有任何标准方式。

我能做的最好的事情是告诉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)。我对这些测量的主要关注点是运行时间,但与没有此调用的版本相比,内存使用量也是一致的。

答案 1 :(得分:0)

首先,您应该查看JMH ,了解如何进行正确的Java bechmarking。

调用Runtime.getRuntime().gc()绝对是一种<强>不良做法 - 无论是在现实生活中,还是在对GC进行基准测试时。至少有一个原因,通过强制GC循环,你可以直接惩罚任何GC算法的性能。

此外,无法比较各种GC算法,只需执行 ~4个GC周期。您应该运行适当的GC基准测试 - 请参阅JMH,您需要至少运行相当长的时间 - 取决于堆大小,这可能是10分钟,或几个小时......

我认为开始时最好的选择是长时间(约30分钟)运行类似JMH的基准测试,收集GC日志并处理GC日志以获取各种统计信息......至少要从一开始就进行某种合理的比较。

答案 2 :(得分:0)

  

我想用术语比较Java程序的不同实现   他们的内存使用效率。

一种选择是运行程序:

-Xloggc:gc.log_impl1 -verbose:gc -XX:+PrintGCDetails -XX:+PrintGCTimeStamps

然后,切换到实现2并使用

重新运行
-Xloggc:gc.log_impl2 -verbose:gc -XX:+PrintGCDetails -XX:+PrintGCTimeStamps

然后下载HPjmeter,将两个文件加载到控制台并使用比较gc功能。图表中可能存在一些偏差,但您可以很好地了解程序内存配置文件的不同之处。

我不会尝试合成地调用GC。