垃圾收集是否会改变Java中对象的地址?
垃圾回收会改变Java中对象的地址吗?
在Java中,如果将一个对象在内存中移动,它的地址会发生改变。因此,指向该对象的引用需要被更新。当在内存中连续的对象序列中删除一个对象时,会导致内存碎片化。这会在内存空间中创建一个"空洞",这通常是不好的,因为连续的内存块具有更快的访问时间和更高的适应缓存线的概率,等等。需要注意的是,使用间接表可以防止引用更新,最多可以使用间接级别。
垃圾回收在Java中有一定的性能开销,不仅在Java中,在其他语言中(如C#)也是如此。Java如何处理这个问题,执行垃圾回收的策略以及如何最小化对性能的影响取决于使用的特定JVM,因为每个JVM可以根据自己的喜好实现垃圾回收;唯一的要求是它符合JVM规范。
然而,作为程序员,有一些最佳实践应该遵循,以充分利用垃圾回收并最大程度地减少对应用程序性能的影响。请参阅这个,还有这个,这个,这篇博文和这篇其他博文。你可能想查看JVM规范,但它有点复杂。
引用不一定需要更新,可以使用间接表。是的,我添加了这一点,为了完整起见。
垃圾回收是否会改变Java中的对象地址?
垃圾回收可能导致运行时的内存碎片问题。但这不仅仅是垃圾收集堆的问题。在手动管理堆内存时,如果释放的内存顺序与之前的分配不同,也可能会导致堆碎片化问题。而且,堆内存具有与自动存储(即堆栈内存)的后进先出顺序不同的生命周期,这是使用堆内存的主要动机之一。
为了解决这个问题,JVM会进行压缩,将所有活动对象分配到连续的内存中。但并不是所有的对象都会被移动,典型的实现策略将内存划分为逻辑区域,只会将某个特定区域的对象移动到另一个区域,而不是一次性移动所有现有对象。这些策略可能会考虑对象的年龄,例如将年轻代对象从Eden空间移动到Survivor空间的代收集器,或者剩余对象的分布情况,例如“Garbage First”收集器,该收集器会首先清理具有最高垃圾比例的碎片,这样可以获得最小的工作量来获取一个连续的空闲内存块。
这意味着对象的地址必须不时改变,当然是的。
此外,如果这种情况发生了:
1. 对这些对象的引用是否也需要重新分配?规范没有规定对象引用的实现方式。间接指针可能消除了适应所有引用的需求,可以参考相关问题与答案。然而,对于使用直接指针的JVM来说,这确实意味着这些指针需要进行适应。
2. 这是否会引起严重的性能问题?Java如何应对这个问题?首先,我们必须考虑从中获得的好处。消除“内存碎片化”并不是一个目的。如果不这样做,我们必须扫描可达对象之间的空隙,并创建一个维护这些信息的数据结构,我们称之为“空闲内存”。我们还需要将内存分配实现为在此数据结构中搜索匹配块或者在找不到完全匹配的情况下拆分块。与从连续的空闲内存块中分配相比,这是一种相对昂贵的操作,我们只需要将指针移动到所需大小的下一个空闲字节。
考虑到分配的频率远远高于垃圾收集,垃圾收集只在内存满时(或者超过阈值)运行,这已经可以证明更昂贵的复制操作是有价值的。这也意味着仅仅使用更大的堆可以解决性能问题,因为它减少了所需的垃圾收集器运行次数,而幸存对象的数量不会随着内存的增加而增加(无法访问的对象保持不可访问,无论您延迟收集多长时间)。事实上,推迟收集会增加在此期间更多对象变为不可访问的几率。
适应引用的成本不比在标记阶段遍历引用的成本更高。实际的复制是更昂贵的方面,但正如上面解释的,它通过不复制所有对象,而是使用基于典型应用行为的特定策略(如代际方法或“垃圾优先”策略)来减少所需的工作量。