当前位置:首页 > 技术 > 正文内容

深入解析JVM垃圾回收:标记-压缩算法与LISP2实现机制

访客 技术 2026年10月16日 1

标记-压缩(Mark-Compact)算法是垃圾回收领域的重要基石。它在标记阶段与标记-清除(Mark-Sweep)算法完全一致,核心差异体现在随后的压缩阶段。该阶段的核心目标是将所有存活的对象向堆内存的一端进行滑动,使其紧凑排列。此举能够有效合并空闲内存块,彻底根治标记-清除算法带来的内存碎片化问题。

对象重排策略

在物理移动存活对象时,根据对象排列规则的不同,通常划分为三种策略:

  • 无序移动:忽略对象原有的物理顺序及引用拓扑,随机将存活对象填入空闲区域。这种方式实现简单,但容易破坏内存访问的局部性原理。
  • 拓扑排序移动:依据对象间的引用依赖图进行重排。若对象A引用对象B,则在物理内存上尽量保证A与B相邻,以优化缓存命中率。
  • 滑动平移:严格保持对象在堆中的原有相对顺序,整体向内存一端滑动。目前主流的垃圾收集器多采用无序或滑动平移策略。

Two-Finger(二指)算法

该算法采用无序移动策略,是处理定长对象内存区域的经典方案。

执行逻辑:算法需对堆进行两次扫描。首次扫描时,将位于堆尾部的存活对象搬运至堆头部的空闲区域(这也是其依赖对象定长的原因)。第二次扫描负责修复因对象位移导致的引用失效。整个过程中,分配指针与回收指针分别从堆的两端向中心逼近,直至相遇。

LISP2 算法

相较于二指算法,LISP2 是一种适用性更广的滑动平移算法。它突破了对象必须定长的限制,能够处理变长对象。其核心特征是引入了额外的元数据空间来记录对象的"目标迁移地址",而非直接覆盖原对象内存。整个回收过程被严谨地拆分为三次独立的堆遍历。

第一次遍历:计算目标地址

回收器在此阶段仅负责计算并记录每个存活对象压缩后的目标物理地址。


// 阶段一:计算并记录存活对象的新地址
void phase1_computeAddresses(MemoryRegion heap) {
    Pointer readCursor = heap.getStart();
    Pointer writeCursor = heap.getStart();
    
    while (readCursor < heap.getEnd()) {
        if (readCursor.isMarked()) {
            // 记录当前存活对象迁移后的目标地址
            readCursor.setForwardingAddress(writeCursor);
            // 写入指针向后偏移当前对象的大小
            writeCursor = writeCursor + readCursor.getObjectSize();
        }
        // 读取指针向后偏移当前对象的大小
        readCursor = readCursor + readCursor.getObjectSize();
    }
}

逻辑解析:readCursor 和 writeCursor 初始均指向堆底。readCursor 负责遍历寻找存活对象。当发现存活对象时,将其目标地址记录在对象头(或额外空间)中,该目标地址即为当前 writeCursor 的位置。随后 writeCursor 增加该对象的尺寸,继续寻找下一个存活对象。遍历结束后,writeCursor 的位置即为所有存活对象压缩后的总边界。

第二次遍历:更新对象引用

此阶段旨在修复所有指向已移动对象的引用,使其指向新的目标地址。


// 阶段二:更新根节点及堆内对象的引用关系
void phase2_updateReferences(MemoryRegion heap, List<Pointer> roots) {
    // 1. 修正根节点集合中的引用
    for (Pointer rootField : roots) {
        Pointer target = rootField.dereference();
        if (target != null) {
            rootField.update(target.getForwardingAddress());
        }
    }
    
    // 2. 修正堆内对象之间的相互引用
    Pointer cursor = heap.getStart();
    while (cursor < heap.getEnd()) {
        if (cursor.isMarked()) {
            for (Pointer refField : cursor.getReferenceFields()) {
                Pointer refTarget = refField.dereference();
                if (refTarget != null) {
                    refField.update(refTarget.getForwardingAddress());
                }
            }
        }
        cursor = cursor + cursor.getObjectSize();
    }
}

逻辑解析:首先遍历 GC Roots,将其中指向堆内对象的引用替换为对应对象的新地址。接着,通过 cursor 遍历整个堆,针对每个存活对象内部的引用字段,统一查表(即读取目标对象的 forwarding address)并更新为新地址。

第三次遍历:执行物理搬迁

最后一步是根据前两次遍历计算出的映射关系,执行实际的对象内存拷贝,并清除标记位。


// 阶段三:执行物理内存搬迁并清理标记
void phase3_relocateObjects(MemoryRegion heap) {
    Pointer cursor = heap.getStart();
    while (cursor < heap.getEnd()) {
        if (cursor.isMarked()) {
            Pointer destination = cursor.getForwardingAddress();
            // 执行内存拷贝,将对象移动至目标紧凑区域
            Memory.copy(destination, cursor, cursor.getObjectSize());
            // 清除目标位置的标记位,为下一轮GC做准备
            destination.clearMark();
        }
        cursor = cursor + cursor.getObjectSize();
    }
}

逻辑解析:cursor 再次遍历堆内存,对于带有存活标记的对象,获取其记录的目标地址 destination,执行底层内存拷贝操作。搬迁完成后,立即清除新位置的标记状态,确保下一次垃圾回收周期的正确性。

标签: JVM

相关文章

Linux crontab 详解

1) crontab 是什么cron 是 Linux 的定时任务守护进程;crontab 是用来编辑/查看“按时间周期执行命令”的表(cron table)。常见两类:用户 crontab:每个用户一份(crontab -e 编辑)系统级 crontab / cron.d:可指定执行用户(/etc/crontab、/etc/cron.d/*)2) crontab 时间...

富文本里可以允许的 HTML 属性

一、所有标签默认允许的安全属性(极少)class        (可选)id           (通常建议禁用)title️ 注意:id 容易被滥用做锚点注入,很多系统直接禁用class 允许的话最好只允许固定前缀(如 editor-*)二、a 标签允许属性<a href="" t...

Mac 安装 Node.js 指南

方法一:通过官网安装包(最简单,适合初学者)如果你只是想快速安装并开始使用,这是最直接的方法。访问 Node.js 官网。页面会显示两个版本:LTS (Recommended For Most Users):长期支持版,最稳定。建议选这个。Current:最新特性版,包含最新功能但可能不够稳定。下载 .pkg 安装包并运行。按照安装向导点击“下一步”即可完成。方法二:使用 Homebrew 安装(...

Dom\HTML_NO_DEFAULT_NS 的副作用:自动加闭合标签

在使用Dom\HTMLDocument时,Dom\HTML_NO_DEFAULT_NS 将禁止在解析过程中设置元素的命名空间, 此设置是为了与DOMDocument向后兼容而存在的。当使用它时,已知的一个副作用就是:自动加闭合标签例如 </img> 为什么会这样?当你使用:Dom\HTML_NO_DEFAULT_NS文档会变成 无命名空间模式,此时内部更接近 XML...

Laravel 事件和监听器创建

在 Laravel 中,使用 Artisan 命令创建 Events(事件) 和 Listeners(监听器) 是非常高效的。你可以通过以下几种方式来实现:1. 手动创建单个 Event如果你只想创建一个事件类,可以使用 make:event 命令:Bashphp artisan make:event UserRegistered执行后,文件将生成在 app/Even...

自定义域名解析神器 dnsmasq

什么是 dnsmasq?dnsmasq 是一个轻量级、功能强大的网络服务工具,专为小型和中等规模网络设计。它是一个综合的网络基础设施解决方案[1]。dnsmasq 能做什么?功能说明应用场景DNS 转发与缓存将 DNS 查询转发到上游服务器(ISP、Google DNS 等),并在本地缓存结果加快 DNS 查询速度,减少外部 DNS 流量本地 DNS解析本地网络设备的主机名,无需编辑&n...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。