我遇到过这个问题,有时我对某个对象的弱引用就消失了,即使我在局部变量中记住了强引用。但在我看来,这可能与 JIT 优化有关。 今天在 Java 11 中,在我看来,我可以找到这种行为的可重复确认。所以有可能,在局部变量中记住对弱引用的某些内容的引用的情况下,它可能不会阻止该对象被垃圾收集。我可以使用以下结构重现该问题:
SomeObjectStructure locId = generateIdAndRememberInternallyWeakReferenceToIdObject();
// This did not work:
// Reference.reachabilityFence(locId);
// The only working solution for my singleton was to assign
// the Id to the ThreadLocal field value of the singleton:
// currentThreadId.set(locId)
try {
// do some stuff here, but do not work with variable locId
// directly, only try to ask for the weak reference sometimes
somethingAskingWeakReferenceToId();
// In the above method sometimes, the weak reference shows null, so
// even if one would think, the local variable locId
// is preventing the object from garbage collection, it does not.
// There were situations where at this time the id has
// already gone. In other cases it was still there.
// This has then serious consequences if you rely
// on the presence of the Id in the weak reference
// container in the code here:
doSomethingOnTheBasisOfContentOfRememberedWeakReference();
} finally {
// Following statement has been intended to release the strong reference
// locId to allow the GC of the the weakly internally remembered Id
// after the finally block ends, but
// sometimes this can be ignored by the compiler
// or optimizer, the variable
// could be released much earlier than after this place in the code:
locId = null;
// Instead of the above statement I used later this one:
// currentThreadId.set(null);
// to release the locId remembered in the ThreadLocal variable
// of class containing the code.
}
我发现 Oracle 优化的页面和类似的东西也可能从中得出结论,请参见here。我说得对,是这样吗?还是对此行为有其他解释?如果我还记得 id 到一些 ThreadLocal 变量中(我的对象是单例),那么问题也会消失。同样在this question中讨论了类似的问题并提出了解决方案
Reference.reachabilityFence(locId);
在我也测试过的变量分配之后,但在我的情况下这没有帮助并且变量内容在到达 finally 块之前仍然被垃圾收集......为什么会这样?
但是带栅栏的解决方案也有缺点,如果我理解正确的话,变量内容应该被记住,直到变量的可见范围结束,但直到方法结束。但是还是不行。。。
还有其他方法可以防止编译器/优化器的这种行为吗?
我遇到过这个问题,有时我对某个对象的弱引用就消失了,即使我在局部变量中记住了强引用。
弱引用的本质是它不会阻止引用的对象被 GC。我知道在对同一对象持有强引用的局部变量范围内发生这种情况是令人惊讶的,但这实际上是可能的。由于您描述了使用 Java 11 执行测试,因此这里是 JLS 11 的相关文本:
A reachable 对象是可以在任何潜在访问 从任何活动线程继续计算。
A finalizer-reachable 对象可以从一些终结器到达 对象通过一些引用链,但不是来自任何活动线程。
An unreachable object cannot be reached by either means.
必须仔细阅读。特别要注意,对象可访问的定义不是(仅)基于持有的引用,而是基于对象是否可以实际访问,这是一个更强的条件。 (通过
Reference
对象的访问被明确排除,尽管不是通过该文本。)如果没有任何线程可以执行的代码路径将访问有问题的对象,那么它是不可访问的。
该文本是专门为允许在诸如您呈现的情况下收集对象而编写的,其中持有一个或多个强引用,但是,根据程序的实际代码,可以确定该对象实际上不会被收集访问(除非通过
Reference
对象)。
或者对这种行为有其他解释吗?
不需要特别说明。您描述的是允许的行为。它可能更有可能被 JIT 代码执行,但这将是一个功能,而不是错误。
也在这个问题中讨论了类似的问题,并且 建议使用解决方案
Reference.reachabilityFence(locId);
在我也测试过的变量分配之后,但在我的 如果这没有帮助并且变量内容仍然是垃圾 在到达 finally 块之前收集...为什么会这样?
你误解了
Reference.reachabilityFence()
的用法。它不会阻止指定对象在未来变得不可访问,而是在过去变得不可访问。因此,在变量赋值之后立即使用它是没有意义的。另一方面,在示例的 finally
块中使用它应该可以防止在执行 try
块终止之前收集对象(或清除引用)。
还有其他方法可以防止编译器/优化器的这种行为吗?
见上文。而且,如果这给您带来麻烦,那么您可能误用了弱引用。那么,另一种解决方案是改用强引用。