导致GC流失一致的技术

dim*_*414 8 java benchmarking multithreading garbage-collection

我正在寻找基准如何在与大量正在进行的垃圾收集竞争时执行的操作.我之前已经对它在稳定的单线程运行中的行为进行了基准测试,现在我想在更加强调的JVM中进行相同的测试; 本质上我想让背景线程以合理一致的速度创建和销毁对象.

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

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

dim*_*414 11

我实现了自己的传递,可以导致稳定的垃圾收集量.完整代码可在此处获取:https://bitbucket.org/snippets/dimo414/argzK

肉是这两种方法,它们在给定的实时时间内构建和释放大量对象(而不是线程时间或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);
}
Run Code Online (Sandbox Code Playgroud)

该类实现,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是我在堆填满之前可以设置的最大大小,我们可能会在更大的情况下看到更好的结果如果我们增加堆的大小.