【问题标题】:Techniques for causing consistent GC Churn导致一致 GC Churn 的技术
【发布时间】:2013-03-26 05:09:24
【问题描述】:

我希望在处理大量正在进行的垃圾收集时对某物的性能进行基准测试。我之前已经对它在稳定的单线程运行中的行为进行了基准测试,现在我想在压力更大的 JVM 中进行相同的测试;本质上,我希望后台线程以相当一致的速度创建和销毁对象。

我正在寻找有关如何实现稳定但 GC 密集型操作的建议。它需要完成几个目标:

  • 在 GC 上花费相当多的时间(例如,20-50%)
  • 随着时间的推移,做大致一致的工作量,并为 GC 创建类似一致的工作量
  • 避免堆泛滥并触发Java heap space 错误
  • 避免 GC 过载并触发 GC overhead limit exceeded 错误

【问题讨论】:

  • 谢谢你,堆栈溢出需要你更多。

标签: java multithreading garbage-collection benchmarking


【解决方案1】:

我在可能导致稳定数量的垃圾收集的情况下实现了自己的通行证。完整代码在这里:https://gist.github.com/dimo414/5243162

关键是这两种方法,它们在给定的实时时间段内(相对于线程时间或 CPU 时间)构造和释放大量对象:

/**
 * Loops over a map of lists, adding and removing elements rapidly
 * in order to cause GC, for runFor seconds, or until the thread is
 * terminated.
 */
@Override
public void run() {
    HashMap<String,ArrayList<String>> map = new HashMap<>();

    long stop = System.currentTimeMillis() + 1000l * runFor;
    while(runFor == 0 || System.currentTimeMillis() < stop) {
        churn(map);
    }
}

/**
 * Three steps to churn the garbage collector:
 *   1. Remove churn% of keys from the map
 *   2. Remove churn% of strings from the lists in the map
 *      Fill lists back up to size
 *   3. Fill map back up to size
 * @param map
 */
protected void churn(Map<String,ArrayList<String>> map) {
    removeKeys(map);
    churnValues(map);
    addKeys(map);
}

该类实现了Runnable,因此您可以在其自己的后台线程中启动它(或同时启动多个)。只要您指定它就会运行,或者如果您愿意,您可以将它作为一个守护线程启动(因此它不会阻止 JVM 终止)并指定它以 0 秒作为构造函数参数永远运行。


我对这个类进行了一些基准测试,发现它花费了将近三分之一的时间阻塞(大概在 GC 上),并确定了大约 15-25% 的流失率和大约 500 的大小的最佳值。每次运行持续 60 秒,下图绘制了java.lang.managment.ThreadMXBean.getThreadCpuTime() 报告的线程时间和com.sun.management.ThreadMXBean.getThreadAllocatedBytes() 报告的线程分配的总字节数。

控制(0% 流失)本质上不应该引入任何 GC,我们可以看到它几乎没有分配任何对象,并且几乎 100% 的时间都花在线程中。从 5% 到 95% 的流失率,我们相当一致地看到大约三分之二的时间花在线程上,大概另外三分之一花在 GC 上。一个合理的百分比,我会说。有趣的是,在流失率的非常高的一端,我们看到更多的时间花在线程上,大概是因为 GC 清理了很多,它实际上能够更有效率。似乎 20% 左右是每个周期都在搅动的大量对象。

这描绘了线程如何在不同的目标大小下运行映射和列表,我们可以看到随着大小的增加,必须在 GC 上花费更多的时间,有趣的是,我们实际上最终分配的对象更少,因为更大的数据大小意味着它无法在同一时间段内进行尽可能多的循环。由于我们对优化 JVM 必须处理的 GC 流失量感兴趣,因此我们希望它需要处理尽可能多的对象,并在工作线程中花费尽可能少的时间。因此,似乎 4-500 左右是一个不错的目标大小,因为它会生成大量对象并在 GC 中花费大量时间。

所有这些测试都是使用标准 java 设置完成的,因此使用堆可能会导致不同的行为 - 特别是,~2000 是我在堆填满之前可以设置的最大大小,我们可能会看到如果我们增加堆的大小,在更大的大小上会得到更好的结果。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-03-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-03-06
    • 2023-04-02
    • 2012-05-04
    相关资源
    最近更新 更多