我一直在阅读有关Java终结器的these slides。在其中,作者描述了一个场景(在幻灯片33上),其中CleanResource.finalize()
可以由终结器线程运行,而CleanResource.doSomething()
仍然在另一个线程上运行。怎么会发生这种情况?
如果doSomething()
是一个非静态方法,那么要执行该方法,某个地方必须对它有强烈的引用......对吗?那么在方法返回之前如何清除这个引用呢?另一个线程可以突然进入并将该引用置空吗?如果发生这种情况,doSomething()
仍会在原始主题上正常返回吗?
这就是我真正想知道的,但是对于真的超越答案,你可以告诉我为什么幻灯片38上的doSomething()
比{{1}更好幻灯片29.为什么仅仅调用这个doSomething()
方法就足够了?你不需要在keepAlive()
块中将整个电话打包到myImpl.doSomething()
吗?
答案 0 :(得分:3)
EDIT3:
结果是终结器和常规方法可以在同一个实例上同时执行。这是对如何发生这种情况的解释。代码基本上是:
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;
这可能被JIT类似于这个伪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。在同一个实例上不可能与另一个实例方法同时执行。
但是,在此之后没有使用对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()
在标记的位置(&lt; ---),没有对CleanResource实例的引用,因此它有资格进行收集并调用终结器方法。由于可以在清除最后一个引用后的任何时间调用终结器,因此终结器和CleanResource.doSomething()
的其余部分可以并行执行。
EDIT2:keepAlive()确保在方法结束时访问this
指针,这样编译器就无法优化指针的使用。并且这种访问保证按指定的顺序发生(同步的单词标记了一个不允许在该点之前/之后重新排序读写的栅栏。)
原帖:
示例是调用doSomething方法,并且一旦调用,就可以提前读取通过this
指针引用的数据(示例中为myIndex
)。读取引用的数据后,该方法不再需要this
指针,并且cpu /编译器可能会覆盖寄存器/声明对象不再可访问。因此,GC可以在对象的doSomething()方法运行的同时同时调用终结器。
但由于未使用this
指针,因此很难看出它会如何产生任何实际效果。
编辑:好吧,也许如果存在通过缓存访问的对象字段的缓存指针,在回收之前从this
计算,然后回收该对象,则内存引用变为无效。有一部分我很难相信这是可能的,但是再一次,这似乎是一个棘手的角落案例,我认为JSR-133中没有任何东西可以防止这种情况发生。这是一个问题,一个对象是否只被指向它的基地的指针或者指向它的字段的引用。