现代 JVM 有时会在垃圾回收期间重新定位内存中的对象,即使 的值System.identityHashCode(object)
在对象的生命周期内保持不变。我不明白的是为什么 JVM 会在内存中重新定位对象?
2 回答
如果内存碎片化(即中间的点点滴滴被删除),则可以通过对其进行碎片整理来提高内存访问速度。此外,对大型对象进行碎片整理会打开更大的内存空间。
为清楚起见进行编辑:正如在此答案下方的评论中发布的那样,垃圾收集算法通常会维护内存的“区域”。有些是为了持久,有些则不是。该算法在不同的 JVM 上有所不同,但它通常包括将对象从一个区域移动到另一个区域,以便完全清空一个区域(如果它已经几乎是空的)。然后可以使该区域作为一个整体可用。JVM 非常聪明地发现哪些对象可以持久,哪些不能持久,但有时它必须调整其策略并重新定位对象。
我不明白的是为什么 JVM 会在内存中重新定位对象?
对于后代,我认为在这里总结对另一个答案的一些评论是很好的。@assylias prolly 应该写这个,但他听起来很忙。正如@Ghostkeeper 指出的那样,可重定位对象的重要好处之一是降低(或消除)内存碎片中固有的问题。可以将更持久的较小对象移动到类似大小的其他对象旁边,以打开连续的可用空间以供较大对象使用。
但是对象重定位如此重要的一个更重要的原因是内存和垃圾收集性能。大多数现代 JVM 实现了一个分代垃圾收集器,它维护着许多不同的内存池。通过使用 jconsole 并连接到正在运行的应用程序,这些池通常是可见的。有关更多信息,请参阅此答案:Java 内存池是如何划分的?
短期对象进入一个池,高性能 GC 算法可以在它们上运行,因为有这么多对象被立即收集以供重用。通过使用有关池的信息,GC 可以轻松确定对象不可达,而无需遍历整个引用树。例如,如果您有如下的日志行,则会创建隐含StringBuilder
和结果String
,然后可以立即从短期对象池中收集:
logger.info("automation run took " + diffMillis + "ms");
// when we reach this line, the StringBuilder and String are no longer in use
因为对象是可重定位的,所以当高性能 GC 算法在短期池中找到仍在使用的对象时,会将其复制(重定位)到另一个池中,并使用新位置更新对该对象的引用。然后 JVM 有一个干净的短期内存池,它可以再次开始填充。中期或长期内存池中的对象是使用运行频率较低的更昂贵的 GC 算法(标记和清除)收集或重新定位的。这种根据对象在哪个池中使用不同 GC 算法以及能够在池之间迁移对象的能力是当今 JVM 中现代高性能 GC 系统的关键部分。
因此,可重定位对象的最终目标是整体内存性能和碎片整理。