6

我一直在阅读这些关于 Java 终结器的幻灯片。在其中,作者描述了一个场景(在幻灯片 33 上),该场景CleanResource.finalize()可以由终结器线程运行,同时CleanResource.doSomething()仍在另一个线程上运行。这怎么可能发生?

如果doSomething()是一个非静态方法,那么要执行该方法的某个人,某处必须对它有一个强引用......对吗?那么如何在方法返回之前清除这个引用呢?另一个线程可以突入并取消该引用吗?如果发生这种情况,是否doSomething()仍会在原始线程上正常返回?

这就是我真正想知道的,但是对于一个真正超越的答案,你可以告诉我为什么doSomething()幻灯片 38 上的比doSomething()幻灯片 29 上的更好。为什么简单地调用这个方法就足够了keepAlive()?您不需要将整个调用包装myImpl.doSomething()在一个synchronized(this){}块中吗?

4

1 回答 1

3

编辑3:

结果是终结器和常规方法可以在同一个实例上同时执行。这是如何发生的解释。代码本质上是:

class CleanResource {
   int myIndex;
   static ArrayList<ResourceImpl> all;

   void doSomething() {
     ResourceImpl impl = all.get(myIndex);
     impl.doSomething();
   } 

   protected void finalize() { ... }
}

鉴于此客户端代码:

CleanResource resource = new CleanResource(...);
resource.doSomething();
resource = null; 

这可能会被 JITed 到类似这样的伪 C

register CleanResource* res = ...; call ctor etc..
// inline CleanResource.doSomething()
register int myIndex = res->MyIndex;
ResourceImpl* impl = all->get(myInddex);
impl->DoSomething();
// end of inline CleanResource.doSomething()
res = null;

像这样执行,res在内联完成后被清除CleanResource.doSomething(),因此在该方法完成执行之前不会发生 gc。不可能在同一个实例上与另一个实例方法同时执行 finalize。

但是,在那之后不使用写入res,并且鉴于没有围栏,它可以在执行的早期移动到写入之后:

register CleanResource* res = ...; call ctor etc..
// inline CleanResource->doSomething()
register int myIndex = res->MyIndex;
res = null;    /// <-----
ResourceImpl* impl = all->get(myInddex);
impl.DoSomething();
// end of inline CleanResource.doSomething()

在标记的位置 (<---),没有对 CleanResource 实例的引用,因此它可以被收集并调用终结器方法。由于可以在最后一个引用被清除后的任何时间调用终结器,因此终结器和其余部分CleanResource.doSomething()可以并行执行。

EDIT2: keepAlive() 确保this在方法结束时访问指针,以便编译器无法优化指针的使用。并且这种访问保证按照指定的顺序发生(同步字标记了一个栅栏,不允许在该点之前/之后重新排序读取和写入。)

原帖:

该示例说调用了 doSomething 方法,并且一旦调用,this就可以及早读取通过指针引用的数据(myIndex在示例中)。一旦读取了引用的数据,该this方法就不再需要指针,cpu/编译器可能会覆盖寄存器/将对象声明为不再可访问。因此,GC 可以在对象的 doSomething() 方法运行的同时同时调用终结器。

但是由于this没有使用指针,所以很难看出这会产生什么实际效果。

编辑:好吧,也许如果有指向通过缓存访问的对象字段的缓存指针,从this它被回收之前计算,然后对象被回收,内存引用变得无效。我的一部分人很难相信这是可能的,但话又说回来,这似乎是一个棘手的极端情况,我认为 JSR-133 中没有任何东西可以默认防止这种情况发生。这是一个对象是否被认为仅被指向其基址的指针或指向其字段的指针引用的问题。

于 2010-07-29T13:54:57.267 回答