当前位置: 首页 > 知识库问答 >
问题:

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

郜彬
2023-03-14

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

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

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

  • 可能有未收集的垃圾
  • 有碎裂,如果GC没有压缩

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

如果我在读取值之前执行三次< code>gc调用,我会得到以下输出(< code > mvn test | grep loop count with JDK 1 . 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

通过四次< code>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://cruft ex . net/2017/03/28/The-6-Memory-Metrics-You-Should-Track-in-Your-Java-benchmarks . html

共有3个答案

史淇
2023-03-14

首先,你应该看看JMH找出一个适当的Javabechmark应该怎么做。

调用Runtime.getRuntime(). gc()绝对是一种糟糕的做法——无论是在现实生活中还是在对GC进行基准测试时。至少举一个原因,通过强制GC循环,您将直接惩罚任何GC算法的性能。

此外,您不能通过仅执行大约4个GC周期来比较各种GC算法。您应该运行一个合适的GC基准测试——参见JMH,您至少需要运行相当长的时间——根据堆的大小,这可能是10分钟,也可能是几个小时...

我认为开始时最好的办法是长时间运行一个类似JMH的基准测试(大约30分钟),收集GC日志并处理GC日志中的各种统计数据...至少要有某种合理的比较作为开始。

华坚成
2023-03-14

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

一种选择是用以下代码运行程序:

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

然后,切换到实现 2 并重新运行

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

然后下载HPjeter,将两个文件加载到控制台并使用比较gc功能。图表中可能有一点倾斜,但您会很好地了解程序内存配置文件的不同之处。

我不会试图综合援引指导性案例。

米楚青
2023-03-14

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

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

GcFinalization.awaitFullGc();

此方法来自番石榴测试库包,可以作为 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程序(在生产中最好使用的Java程序)和适当的基准测试。例如,我已经有了HyperSQL,并通过基准程序PolePosition来衡量其性能。在没有JIT编译器的JVM上运行的Java程序。谢谢你的帮助! PS:我不能使用程序来对JVM或Java语言本身的性能进行基准测试(例如Wide Finder)。

  • 我有一个应用程序,我想使用对其进行基准测试。任何有关此集成的参考都将是有用的。

  • 我定期关注GitHub Rakudo存储库,查看Rakudo编译器发生了哪些变化。 我有时会看到提交,其中单个函数的速度提高了一定的百分比,时间如下图所示。 评估这一点的工作流是什么?我很想了解这一点,以便了解您的功能是如何执行的,并相应地进一步优化,从而为Rakudo的发展做出贡献。 我在这里阅读帮助。我谷歌了一下,但找不到此信息。我还通过命令行选项了解了MoarVM分析器,它会生成输出。要寻找

  • 对于我的单元测试,我使用以下注释: @runwith(SpringJunit4ClassRunner.class) @SpringBootTest(classes=MyApplication.class) 我的问题发生在我试图用Selenium测试GUI时。在我的gui测试中,我调用了一些页面并验证了html消息的内容。 我不需要模拟,我需要在我的测试前开始申请。什么是优雅的方式来做到这一点? 注

  • 问题内容: 除了以下内容之外,是否有其他方法/软件可以给出执行用Swift编写的代码块所需的准确时间? 问题答案: 如果您只想为代码块提供独立的计时功能,则可以使用以下Swift助手功能: 前者将注销给定代码段所需的时间,后者将返回该时间作为浮点数。作为第一个变体的示例: 将注销类似: map()经过的时间:0.0617449879646301 s 请注意,Swift基准测试会根据您选择的优化级别