众所周知,GC有时会在内存中移动对象.而且我的理解是,只要在移动对象时(在调用任何用户代码之前)更新所有引用,这应该是非常安全的.
但是,我看到有人提到参考比较可能是不安全的,因为GC在参考比较的中间移动了对象,这样即使两个引用都应该引用同一个对象,比较也可能失败?
即,是否存在以下代码不能打印"真实"的情况?
Foo foo = new Foo(); Foo bar = foo; if(foo == bar) { System.out.println("true"); }
我试着谷歌搜索这个,缺乏可靠的结果让我相信说这个的人是错的,但我确实找到了各种各样的论坛帖子(比如这个)似乎表明他是正确的.但那个帖子也有人说不应该这样.
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能够在执行字节码指令期间运行,对象的引用也不会改变.它在循环之前和之后仍然是相同的对象.
因此,简而言之,您的问题的答案是否定的,它将始终输出为真.
资源:
https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.21.3
简短的回答是,看看java 8规范:不.
所述==
操作者将总是执行对象相等性检查(假定既没有参考为空).即使移动了对象,对象仍然是同一个对象.
如果你看到这样的效果,你刚刚发现了一个JVM错误.去提交吧.
当然,可能是JVM的某些模糊实现并没有因为任何奇怪的性能原因而强制执行此操作.如果是这种情况,那么从那个JVM继续前进是明智的......
你不应该考虑那种东西,这是一个黑暗的地方.Java已经清楚地说明了它的规格,你不应该怀疑它.
2.7.对象的表示
Java虚拟机不要求对象的任何特定内部结构.
资料来源:JVMS SE8.
我对此表示怀疑!如果你也许会怀疑这个非常基本的操作,你可能会发现自己怀疑一切,感到沮丧和偏执与信任问题是不是你想要去的地方.
如果它发生在我身上怎么办?不应该存在这样的错误.您提供的报告说,几年前发生的事情,不知怎么讨论OP决定弹出,截至没有理由,或者没有这样的错误的可靠的文件中的错误Oracle的讨论现在存在天.但是,如果您遇到此类错误或其他错误,请在此处提交.
为了让您的后顾之忧消失,Java已经将指针调整指针调整到JVM 指针表中,您可以在这里阅读更多关于它的效果.
GC只发生在程序中的状态定义良好的点上,并且JVM具有确切的知识,其中所有内容都在寄存器/堆栈中/堆上,因此所有引用都可以在对象移动时修复.
即,它们不能在任意汇编指令的执行之间发生.从概念上讲,你可以想到它们在JVM的字节码指令之间发生,GC调整了之前指令生成的所有引用.
你问一个错误前提的问题.由于==
操作员不比较存储器位置,因此对存储器位置本身的改变是不明智的.无论JVM如何实现它==
,应用于引用的运算符都会比较引用对象的标识.
举一个反映通常理解的例子,分布式JVM可能在不同计算机的RAM中保存对象,包括本地拷贝的可能性.所以简单地比较地址是行不通的.当然,由JVM实现来确保Java语言规范中定义的语义不会发生变化.
如果一个特定的JVM实现通过直接比较对象的内存位置来实现引用比较,并且有一个可以改变内存位置的垃圾收集器,当然,由JVM来确保这两个特性不会相互干扰.不相容的方式.
如果您对如何工作感到好奇,例如内部优化的JIT编译代码,那么粒度就不如您想象的那么精细.每个顺序代码(包括前向分支)都可以被认为运行得足够快,以便将垃圾收集延迟到完成.因此垃圾收集不能在优化代码中随时发生,但必须在某些点允许,例如
向后分支(请注意,由于循环展开,并非每个循环迭代都意味着向后分支)
内存分配
线程同步动作
调用尚未内联/分析的方法
也许是特别的东西,我忘记了
因此,JVM发出的代码包含已知的某些"安全点",当前持有哪些引用,如何更换它们,当然,更改位置对正确性没有影响.在这些点之间,代码可以运行而无需关心更改内存位置的可能性,而垃圾收集器将在必要时等待代码到达安全点,这保证在有限的,相当短的时间内发生.
但是,如上所述,这些是实施细节.在正式级别上,不存在更改内存位置等内容,因此无需明确指定不允许更改Java代码的语义.不允许执行细节.