JVM GC 能否在引用比较的中间移动对象,从而导致比较失败,即使双方都引用同一对象?

2022-08-31 16:38:43

众所周知,GC有时会在内存中移动对象。我的理解是,只要在移动对象时(在调用任何用户代码之前)更新所有引用,这应该是完全安全的。

但是,我看到有人提到引用比较可能是不安全的,因为GC在引用比较过程中移动了对象,因此即使两个引用都应该引用同一对象,比较也可能失败?

即,是否存在以下代码不会打印“true”的情况?

Foo foo = new Foo();
Foo bar = foo;
if(foo == bar) {
    System.out.println("true");
}

我试着用谷歌搜索这个,缺乏可靠的结果使我相信说这一点的人是错误的,但我确实发现了各种各样的论坛帖子(比如这个),似乎表明他是正确的。但这个话题也有人说不应该是这样。


答案 1

Java字节码指令相对于GC总是原子的(即在执行单个指令时不会发生循环)。

GC 唯一运行的时间是在两个字节码指令之间。

查看javac为代码中的if指令生成的字节码,我们可以简单地检查GC是否有任何效果:

// a GC here wouldn't change anything
ALOAD 1
// a GC cycle here would update all references accordingly, even the one on the stack
ALOAD 2
// same here. A GC cycle will update all references to the object on the stack
IF_ACMPNE L3
// this is the comparison of the two references. no cycle can happen while this comparison
// "is running" so there won't be any problems with this either

另外,即使GC能够在字节码指令的执行期间运行,对象的引用也不会改变。在循环之前和之后,它仍然是相同的对象。

因此,简而言之,您的问题的答案是否定的,它将始终输出真实。


答案 2

源:

https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.21.3

简短的答案是,看看java 8规范:不。

运算符将始终执行对象相等性检查(假设两个引用均不为 null)。即使移动了对象,该对象仍然是同一个对象==

如果您看到这样的效果,那么您刚刚发现了一个JVM错误。去提交它。

当然,可能是JVM的一些晦涩的实现由于任何奇怪的性能原因而没有强制执行这一点。如果是这样的话,那么简单地从那个JVM继续前进将是明智的......