朱莉娅的垃圾收集
重点介绍
Julia中有两种类型的分配工具,具体使用的类型由分配大小请求决定。 使用可用内存池分配工具为每个线程分配大小为2KB的对象,通过libc malloc分配大于2KB的对象。
Julia池分配工具将对象划分为不同大小的类,因此池分配工具管理的内存页(涵盖64位平台上的4个操作系统页)仅包含相同大小类的对象。 来自分配池的每个内存页与存储在每个线程的无锁列表中的某些页元数据配对。 页面元数据包含页面上活动对象的存在、可用插槽的数量以及此页面上包含的可用内存列表中第一个和最后一个对象的偏移量等信息。 此元数据用于优化收集阶段:例如,其上没有活动对象的页面可以返回到操作系统,而无需检查它。
虽然没有对象的页面可以返回到操作系统,但关联的元数据是持续分配的,并且可以继续存在比给定页面更长的时间。 如上所述,专用页面的元数据存储在每个线程的无锁列表中。 但是,可用内存页的元数据可以存储在三个独立的无锁列表中,具体取决于该页是否被映射但未被访问(page_pool_clean
),或者该页在延迟模式下被清除并等待来自后台垃圾回收线程的madvise调用(page_pool_lazily_freed
),或者该页收到了madvise调用(page_pool_freed
)。
Julia池分配工具使用"多级"分配原则。 为分配工具请求内存页时,Julia将执行以下操作:
-
它将尝试从`page_pool_lazily_freed`获取一个页面,其中包含在最后停止阶段为空的页面,但尚未收到来自并行GC清理线程的madvise调用。
-
如果无法从`page_pool_lazily_freed’获取页面,则将尝试从`the page_pool_clean`获取页面,其中包含在前一个页面分配请求中收到mmap请求但从未访问过的页面。
-
如果无法从`pool_page_clean`和`page_pool_lazily_freed’获取页面,则将尝试从`page_pool_freed`获取页面,其中包含已从并行GC清理线程接收madvise调用且其基本虚拟地址可以回收的页面。
-
如果上述所有尝试都失败,将对页面包进行mmap调用,为自己请求一个页面,并将所有其他页面插入`page_pool_clean`。
基于生成的标记和收集
Julia中的标记阶段是通过对象图的并行迭代深度优先搜索来实现的。 Julia中的收集器是静止的,因此不能仅通过对象所在的内存区域来确定有关对象年龄的信息,而必须以某种方式在对象头或边表中进行编码。 对象头的两个最低位用于存储,分别是标记位,该标记位在标记阶段扫描对象时设置,以及用于基于生成的集合的年龄位。
基于生成的集合是使用固定位实现的:对象被放置在标记堆栈上,这意味着只有在没有设置它们的标记位时才会跟踪它们。 当对象进入最旧的一代时,它们的标记位在所谓的"快速清洁"期间不会被重置,这导致这些对象在随后的标记阶段没有被跟踪。 然而,通过"完全清理",所有对象的标记位都被重置,这导致在随后的标记阶段跟踪所有对象。 在每个完成的清理阶段,对象都会提升到下一代。 在修改器端,使用写屏障截取字段记录,如果对象在最后一代中,并且如果正在记录的字段中的对象不在其中,则将对象的地址放在每个流的记忆集合中。 然后在标记阶段跟踪来自此记忆集的对象。
结算
Julia的对象池清理可以分为两类:如果由池分配工具管理的此页至少包含一个活动对象,则需要通过其非活动对象列出可用内存;如果此页不包含活动对象,则其基本物理内存可以返回操作系统,例如使用Linux上的madvise系统调用。
第一类清洗通过拦截工作并行化。 对于第二类清理:如果使用`--gcthreads=X,1`标志启用了并行页面清理,则在后台清理线程中与修饰符线程并行执行madvise系统调用。 在停止收集器的阶段,池中分配的不包含活动对象的页最初被发送到’pool_page_lazily_freed'。 然后唤醒后台清理线程,它负责从pool_page_lazily_freed中删除页面,为它们调用madvise,并将它们插入到pool_page_freed中。 如上所述’pool_page_lazily_freed’也与修饰符流一起使用。 这意味着在高分配多线程工作负载中,修饰符线程通常通过直接从页面分配到`pool_page_lazily_freed`来避免页面分配错误(由于使用mmap调用访问页面或使用madvise调用访
启发法
垃圾收集启发式通过更改收集之间的分配间隔来纠正垃圾收集。
垃圾收集启发式测量收集后堆大小的大小,并根据页面上描述的算法设置下一个收集https://dl.acm.org/doi/10.1145/3563323 … 结果表明,总堆体积应具有与活动堆的平方根比,并且还应根据垃圾收集器释放对象的速度以及修饰符执行分配的速度进行缩放。 启发法通过使用malloc计算使用的页面和对象的数量来测量堆的大小。 以前,我们通过计数活动对象来测量堆的大小,但这没有考虑碎片,这可能导致错误的决策。 这也意味着我们使用了关于流(分配)的本地信息来做出关于整个过程的决策(何时执行垃圾回收),页面维度意味着决策是全局的。
当堆大小达到允许的最大值的80%时,GC将执行完整构建。