可最终确定的对象的前期成本是多少?

时间:2015-05-08 19:00:35

标签: java jvm finalization

讨论Java中的可终结对象通常会讨论当最终化对象(及其相关资源)无法快速被垃圾收集时发生的常见间接成本。

目前,我更感兴趣的是,无论是在内存方面还是在对象分配时间内,最终可实现的直接成本是多少。我已经在很多地方看到了对这种费用存在的倾斜性提法,例如,Oracle's article on finalization memory retention issues注:

  

分配obj时,JVM会在内部记录obj是否可以终止。这通常会减慢现代JVM所具有的快速分配路径。

JVM如何记录对象实例的最终结果,以及这样做的内存和性能成本是多少?

对于那些对我的具体应用感兴趣的人:

我们生产和保留了数以百万计的轻巧物体;向这些对象添加单个指针是非常昂贵的,因此我们已经做了相当多的工作来从它们中删除指针,而是使用较小的数字ID打包到字段的一个子集中。解包该数字允许从使用Map存储它们的池中检索具有该id的共享不可变属性。

剩下的问题是如何处理不再使用的属性值的垃圾收集。

已经考虑的一个策略是使用引用计数;当创建对象并检索值的池化id时,该值的引用计数递增;当它不再使用时,必须递减。

确保发生此减量的一个选项是添加以下finalize方法:

public void finalize() {
    Pool.release(getPropertyId());
}

但是,如果可最终化的行为意味着必须保留指向该对象的附加指针,那么对于该应用程序而言,可最终确定的前期成本将被认为是高的。如果这意味着必须分配额外的对象,那几乎肯定会过高......因此,我的问题是:最终化的直接前期成本是多少?

1 个答案:

答案 0 :(得分:8)

终结器糟糕不仅是因为保留问题,而且也是从性能角度来看。

在Oracle JDK / OpenJDK中,具有attach()方法的对象由Finalizer的实例支持,finalizejava.lang.ref.Reference的子类。

所有终结器都在对象的构造函数的末尾注册,分两步:调用from Java to VM,然后调用Finalizer.register()。 JIT编译器无法内联这种双转换Java-> VM-> Java。但最糟糕的是,Finalizer的构造函数在global lock下创建了一个链表! (捂脸)

终结器在内存占用方面也很糟糕:除了所有参考字段外,它们还有two extra fieldsnextprev

PhantomReferences比终结者好得多:

  • 它们的构造不需要转换到VM并且可以返回并且可以内联;
  • 除了继承自java.lang.ref.Reference;
  • 之外,他们没有额外的字段
  • 没有进行全局同步。

This benchmark比较可终结对象的分配速度和PhantomReference支持的对象:

Benchmark               Mode  Cnt       Score      Error   Units
Finalizer.finalizable  thrpt    5    2171,312 ± 1469,705  ops/ms
Finalizer.phantom      thrpt    5   61280,612 ±  692,922  ops/ms
Finalizer.plain        thrpt    5  225752,307 ± 7618,304  ops/ms