如何真正衡量Java应用程序的内存使用情况 [英] How to really benchmark the memory usage of a Java application

查看:130
本文介绍了如何真正衡量Java应用程序的内存使用情况的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想根据内存使用效率比较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-内存基准测试



如果我在读取值之前执行三次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调用(如签入)我得到:

<$ p $ b $ 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

因此t是经验性显示的,结果缝是正确的。
从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 :完全GC2015-01-08T02:30:35.136 + 0100:[终身:1058K-> 1058K(83968K)
2015-01-08T02:30:35.198 + 0100:[完全GC2015- 01-08T02:30:35.198 + 0100:[终身:1058K-> 1058K(83968K)
2015-01-08T02:30:35.263 + 0100:[Full GC2015-01-08T02:30:35.264 + 0100 :[终身制: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;
//循环来获得稳定的读数,因为内存可能会在方法调用之间调整大小
do {
_count ++;
_total = Runtime.getRuntime()。totalMemory();
尝试{
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有关,但实际问题并非如此。我喜欢为单个时间点找出应用程序的内存使用情况。一个可能的解决方案还将对所有对象进行深度搜索并总结尺寸。我还努力解决这个问题并有兴趣知道是否有任何标准方式。



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

  GcFinalization.awaitFullGc(); 

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

 <依赖关系> 
< groupId> com.google.guava< / groupId>
< artifactId> guava-testlib< / artifactId>
< version> 18.0< / version>
< /依赖关系>

实现如下所示:

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

//希望能够捕获排队在可终结对象后面的某些零散元素
System.runFinalization();
}

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


I want to compare different implementations of Java programs in terms of their memory usage efficiency. There are different usage scenarios formulated as JUnit test cases. Actually, all the code is open source at: https://github.com/headissue/cache2k-benchmark

The general wisdom to get to the used memory of a Java program is this: Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory(), of course it is also possible to use the JMX interface to get these values.

However, the determined values of used memory is not reliable. Possible reasons:

  • There may be uncollected garbage
  • There is fragmentation, if the GC did no compaction

So far I experimented with switching to serial GC and to force garbage collection with Runtime.getRuntime().gc() before reading out the value. I've put the experimental code for this at: https://github.com/cruftex/java-memory-benchmark

If I do three gc calls before reading the values, I get this output (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

With four gc calls (as checked in) I get:

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

So t is empirically shown, that with four GC calls, the results seam to be correct. From the GC statistics output I can see that the first GC fills the tenured space and the fourth GC call reduces it:

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)

The final code, to get the memory usage value is:

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

I am pretty unsure about whether this approach is producing reliable results all the time. So some questions:

  • Is there some best practice to get reliable and comparable benchmark values from Java programs?
  • Any ideas how to tune (or actually detune) the GC for that usage case?
  • Is there a reliable source and a reliable behavior explaining the needed four GC calls? (BTW: java 8 is performing the same way)
  • Is there a way to say the JVM: "Do best possible garbage collection, I'll wait"?
  • In general, what might be the most "future proof" and reliable solution for the problem statement?

Update:

Although some questions above are GC related, the actual problem is not. I like to find out the memory usage of an application for a single point in time. A possible solution would also to do a depth search of all object and sum up the sizes.

解决方案

I also struggled with this issue and interested to know if there is any standard way.

The best I could do was to tell JVM to do its best to gather garbage as much as possible by calling the following method after a run and before the next one:

GcFinalization.awaitFullGc();

This method is from the Guava test-lib package, which can be added as a Maven dependency as:

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

The implementation looks like this:

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

This gave me very consistent results for each run and makes the CPU user time (from ThreadMXBean) very close to nano time (from System.currentTimeMills). My main concern in those measurements was running time, but the memory usage was also consistent, compared to the version without this call in between.

这篇关于如何真正衡量Java应用程序的内存使用情况的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆