我知道如何找到根,但问题是,(AFAIK)它们必须在运行时找到。为此,你需要一个可能溢出的固定大小的容器或一个可调整大小的容器。我不想选择固定大小的容器,因为不容易知道要预留多少空间(而且可能很浪费)。可调整大小的容器似乎是最好的,但问题是,GC运行的时候空间不够,所以可调整大小的容器无法存储需要的东西。那么有了这些条件,GC的根是如何存储的呢?
GC根是堆外的一个位置,可以包含对堆内对象的引用。一个位置可以是任何可以存储引用的东西。通常它是四个或八个字节的内存,存储32位或64位的地址,但它也可以是一个机器寄存器或磁盘上的空间。有时,一个位置被称为 "槽",因为你可以 "槽 "正好一个引用。一个经典的标记&扫描收集器的工作方式是首先标记所有由根引用的对象,然后从那里继续追踪。
根的存储位置和存储方式取决于虚拟机,当你考虑到部分GC、线程和JIT等问题时,就会变得非常复杂。但从概念上讲,它很简单。假设你有一个类似Python的语言,只有函数和globals,并有以下代码。
0: FOO = "hel"
1: BAR = "hi"
2: def foo(x):
3: y = x + "there"
4: <GC HERE>
5: return
6: def bar(x):
7: y = x + "lo"
8: foo(y)
9: return
10: bar(FOO)
11: ...
假设GC发生在指定的行上 调用栈看起来像这样。
Return address to line 11
Reference to object "hello"
Return address to line 9
Reference to object "hellothere"
GC会扫描这个调用栈 区分返回地址和引用,并标记它发现的对象。然后它将对全局引用做同样的处理。它们可以存储在堆上的字典(hashmap)中,并由一个根引用。
{name("FOO") : "hel", name("BAR") : "hi"}
请注意,存储所有根所需要的空间是很小的。你只需要8个字节(一个引用)来存储globals,8个字节来存储callstack上的foreach元素。你可能会用完堆栈空间,并得到一个堆栈溢出,但如果为堆栈预分配256kb的空间,并进行适当的尾部调用优化,这就不是问题了。