可靠地测量 JVM 分配

Reliably measure JVM allocations

我有两个相同算法的实现。我想验证它们中没有一个使用了比必要更多的内存,或者换句话说,它们分配了完全相同数量的对象。

我目前的解决方案是通过 threadMXBean.getThreadAllocatedBytes(threadId) 测量过程前后分配的字节数,并将其用作内存占用的近似值。

问题是这个方法不稳定,e.i。有时它 returns 比它应该的多得多。它特别显示在不分配对象的算法上。一个有问题的例子是一种对 int[].

求和的方法

实际代码 (Kotlin):

class MemAllocationTest {

    private val threadMXBean = (ManagementFactory.getThreadMXBean() as? com.sun.management.ThreadMXBean)
            ?: throw RuntimeException("Runtime does not support com.sun.management.ThreadMXBean")

    /**
     * May run [block] several times
     * */
    private inline fun measureAllocatedBytes(block: () -> Unit): Long {
        val threadId = Thread.currentThread().id

        val before = threadMXBean.getThreadAllocatedBytes(threadId)
        block()
        val after = threadMXBean.getThreadAllocatedBytes(threadId)

        return after - before
    }

....

有更好的解决方案吗?

(我不知道如何用 JMH 做到这一点,但恕我直言,这是一个非常接近的话题)

我目前的解决方案是通过多次运行来收集统计信息:

private inline fun stabiliseMeasureAllocatedBytes(block: () -> Unit): Long {
    val runs = List(7) { measureAllocatedBytes(block) }
    val results = runs.drop(2) // skip warm-up

    val counts = results.groupingBy { it }.eachCount()
    val (commonResult, commonCount) = counts.entries.maxBy { (result, count) -> count }!!

    if (commonCount >= results.size / 2)
        return commonResult
    else
        throw RuntimeException("Allocation measurements vary too much: $runs")
}

JMH 有 -prof gc 分析器,它应该是准确的分配分析。尽管它在幕后使用相同的 ThreadMXBean,但它可以过滤掉预热效果,并在多次 @Benchmark 调用中平均打嗝。那里的典型误差在 0.001 byte/op 以内。