计算对象从创建到垃圾收集的总时间

时间:2018-03-16 10:12:34

标签: java garbage-collection

在Effective Java第3版,第50页,作者讨论了一个对象从创建到垃圾收集的总时间。

  

在我的机器上,创建一个简单的AutoCloseable对象的时间,关闭   它使用try-with-resources,并让垃圾收集器回收它   12 ns。使用终结器将时间增加到550 ns。

我们如何计算这段时间?是否有一些可靠的机制来计算这个时间?

2 个答案:

答案 0 :(得分:2)

我所知道的唯一可靠方法(在此强调)是java-9通过Cleaner API,类似这样:

static class MyObject {
    long start;
    public MyObject() {
        start = System.nanoTime();
    }
}

private static void test() {
    MyObject m = new MyObject();

    Cleaner c = Cleaner.create();
    Cleanable clean = c.register(m, () -> {
        // ms from birth to death 
        System.out.println("done" + (System.nanoTime() - m.start) / 1_000_000);
    });
    clean.clean();
    System.out.println(m.hashCode());
}

register的文档说:

  

当对象变为幻像可达时,可以调用Runnable

我的问题究竟是什么,幻影可以到达? (这是一个我仍然怀疑我真的理解它的问题)

在java-8中,文档说(对于PhantomReference

  

与软引用和弱引用不同,垃圾收集器在排队时不会自动清除幻像引用。    通过幻像引用可以访问的对象将保持不变    这些引用被清除或自己无法访问。

这里有很好的主题,试图解释为什么会这样,考虑到PhantomReference#get将始终返回null,因此没有太多用于不立即收集它们。

这里还有一些主题(我会尝试挖掘它们),其中显示了finalize方法中复活和对象的容易程度(通过使其再次强烈可达 - 我认为这不是API打算以任何方式开始的。)

java-9中删除粗体句子,以便 收集。

答案 1 :(得分:2)

任何跟踪对象生命周期的尝试都具有足够的侵入性,可以显着改变结果。

对于AutoCloseable变体尤其如此,在最佳情况下可能会受到逃逸分析的影响,将分配和解除分配的成本降低到接近零。任何跟踪方法都意味着创建一个阻碍此优化的全局引用。

实际上,解除分配的确切时间与普通对象(即没有特殊finalize()方法的对象)无关。下次内存管理器实际需要空闲内存时,将全部回收所有无法访问的对象的内存。因此,对于现实生活场景,尝试单独测量单个对象是没有意义的。

如果您想以非侵入性的方式衡量分配和释放的成本,试图更接近真实应用程序的行为,您可以执行以下操作:

  • 将JVM的堆内存限制为 n
  • 运行一个测试程序,分配并放弃大量的测试实例,例如,他们所需的内存量比堆内存 n 高几个数量级。
  • 测量执行测试程序所需的总时间,并将其除以创建的对象数

您确定必须回收不适合有限堆的对象,以便为较新的对象腾出空间。由于这不适用于最后分配的对象,因此您知道最大错误与适合 n 的对象数相匹配。当您按照配方并分配了该数字的大倍数时,您会有一个相当小的错误,特别是在比较数字时会发现类似于变量A的内容,平均需要每个实例约12 ns,而变体B需要550 ns(如{{3} },这些数字清楚地用“在我的机器上”标记,并不意味着可以完全重现。

根据测试环境的不同,您甚至可能需要使用finalize()减慢变量的分配线程,以允许终结器线程赶上。
这是一个现实生活中的问题,当只依赖finalize()时,在循环中分配太多资源会破坏程序。