为什么堆被划分为Eden、Survivor和Old Generation空间?

7

请问您关于JVM垃圾回收过程的问题能否回答一下?

为什么堆被分成Eden、Survivor空间和老年代?

当进行年轻代清理时,对象通过引用从根开始访问以查找不可达对象。可达对象标记为“存活”,而不可达对象未被标记并将被消除。

因此,所有对象都被考虑在内,包括在老年代分配的对象也会被访问和标记为可达状态。

据我所知,同时回收年轻代和老年代是很困难的,因为这些代位于内存的不同连续部分。

但是,如果在年轻代清理阶段进行最简单的标记,我们就可以得到包含所有存活和死亡对象的位图,因为所有可达和不可达对象都已知并且可以删除,那么为什么我们需要这种分割呢?

我还知道弱代假说,但是我们为什么需要这种划分呢?

2个回答

3
基本前提是当新对象被创建时,旧对象对新对象不存在引用,对于许多对象甚至大多数对象而言,这种情况永远不会改变。这意味着如果你能证明旧对象到新对象没有引用关系,或者你精确地知道哪些引用已经被创建,那么你可以仅扫描年轻代进行“次要”垃圾回收。
这意味着必须跟踪和记住对旧对象的引用更改(但请注意这样的更改并不经常发生)。
一种实现策略是卡标记
如果垃圾回收器不收集整个堆(增量收集),则需要知道未收集部分的堆到正在收集的堆的指针位置。这通常用于分代垃圾回收器,其中未收集的堆通常是老年代,而收集的堆是年轻代。用于保留此信息的数据结构(从老年代指向年轻代对象的指针)是一个“记忆集”。卡表是一种特定类型的记忆集。Java HotSpot VM使用字节数组作为卡表。每个字节称为一张卡片。卡片对应于堆中的地址范围。“弄脏”一张卡片意味着将字节的值更改为“脏值”;脏值可能包含老年代到年轻代的新指针,在卡片覆盖的地址范围内。处理一张卡片意味着查看该卡片,以查看是否存在从老年代到年轻代的指针,并可能对该信息执行某些操作,例如将其转移到另一个数据结构。
当然,仅使用代际提供的好处在于,如果它使我们能够跳过扫描期间的某些内存区域,并且维护这些记忆集不会超过节省的成本。

谢谢您的回答!您能详细解释一下“仅扫描年轻代”的工作原理吗? - user5536368
@PavelPavel 这意味着仅扫描堆的一部分 - 堆的较小部分,由于它很小,所以扫描和回收内存所需的时间非常少。 - Eugene
我认为,只需从特定的gc根开始(扫描局部变量,但不扫描“静态”字段),并且不遍历任何指向旧一代的引用。 - Holger
@Holger 谢谢!我找不到任何有关在小集合中进行扫描的逐步教程。 - user5536368
这并不奇怪。大多数在线资源只试图提供一个总体概述,而不是“逐步教程”,因为你不应该成为垃圾收集器,甚至不应该自己实现垃圾收集器...顺便说一句,当我第一次听到“小型收集”时,你的问题也是我的第一个问题。考虑到垃圾收集器跟踪的是活动引用而不是垃圾,这听起来是矛盾的。只有在了解了记忆集并且在这样的设置中修改旧对象变成了受监控的操作之后,才有意义。 - Holger

1
分代垃圾回收在考虑移动收集器时非常有用。如果没有分离,年轻的集合将在堆中留下很多空洞,需要进行空闲列表管理或老年代压缩。另一方面,如果年轻代作为半空间GC实现,则不需要进行此类清理和跟踪,因为经过小型集合后,疏散空间将仅包含死对象,并且随后可以视为空闲空间。这还使得在年轻代中启用颠簸指针分配。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接