在Effective Java第3版,第50页,作者讨论了一个对象从创建到垃圾收集的总时间。
在我的机器上,创建一个简单的AutoCloseable对象的时间,关闭 它使用try-with-resources,并让垃圾收集器回收它 12 ns。使用终结器将时间增加到550 ns。
我们如何计算这段时间?是否有一些可靠的机制来计算这个时间?
答案 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()
方法的对象)无关。下次内存管理器实际需要空闲内存时,将全部回收所有无法访问的对象的内存。因此,对于现实生活场景,尝试单独测量单个对象是没有意义的。
如果您想以非侵入性的方式衡量分配和释放的成本,试图更接近真实应用程序的行为,您可以执行以下操作:
您确定必须回收不适合有限堆的对象,以便为较新的对象腾出空间。由于这不适用于最后分配的对象,因此您知道最大错误与适合 n 的对象数相匹配。当您按照配方并分配了该数字的大倍数时,您会有一个相当小的错误,特别是在比较数字时会发现类似于变量A的内容,平均需要每个实例约12 ns,而变体B需要550 ns(如{{3} },这些数字清楚地用“在我的机器上”标记,并不意味着可以完全重现。
根据测试环境的不同,您甚至可能需要使用finalize()
减慢变量的分配线程,以允许终结器线程赶上。
这是一个现实生活中的问题,当只依赖finalize()
时,在循环中分配太多资源会破坏程序。