Techniques for causing consistent GC Churn

我与影子孤独终老i 提交于 2019-12-04 03:11:00

I implemented my own pass at something that could cause a stable amount of garbage collection. The full code is available here: https://bitbucket.org/snippets/dimo414/argzK

The meat is these two methods, which construct and release a large number of objects for a given period of real time (as opposed to thread time or CPU time):

/**
 * 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);
}

The class implements Runnable so you can start it (or several at once) in its own background thread. It will run for as long as you specify, or if you prefer you can start it as a daemon thread (so it doesn't stop the JVM from terminating) and specify it to run forever with 0 seconds as the constructor argument.


I did some benchmarking of this class and found it spent close to a third of its time blocking (presumably on GC) and identified approximate optimal values of 15-25% churn and a size of ~500. Each run was done for 60 seconds, and the graphs below plot the thread time, as reported by java.lang.managment.ThreadMXBean.getThreadCpuTime() and the total number of bytes allocated by the thread, as reported by com.sun.management.ThreadMXBean.getThreadAllocatedBytes().

The control (0% churn) shouldn't introduce essentially any GC, and we can see it allocates hardly any objects and spends nearly 100% of its time in the thread. From 5% up to 95% churn we see fairly consistently about two thirds of the time is spent in thread, presumably the other third is spent in GC. A reasonable percentage, I'd say. Interestingly, at the very high end of the churn percentage we see more time being spent in the thread, presumably because the GC is cleaning up so much, it's actually able to be more efficient. It seems around 20% is a good number of objects to be churning each cycle.

This plots how the thread functions at different target sizes for the map and lists, we can see as the size increases more time must be spent in GC, and interestingly we actually end up allocating fewer objects, as the larger data size means it's unable to make as many loops in the same period of time. Since we're interested in optimizing the amount of GC churn the JVM has to deal with, we want it to need to deal with as many objects as possible, and spend as little time as possible in the working thread. It seems around 4-500 is a good target size, therefore, as it generates a large number of objects and spends a good amount of time in GC.

All these tests were done with the standard java settings, so playing with the heap may cause different behavior - in particular, ~2000 was the maximum size I could set before the heap filled up, it's possible we'd see even better results at a larger size if we increased the size of the heap.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!