不考虑分配与合并情况下,GO实现GCMarkSweep(标记清除算法)

news2025/8/6 0:06:17

观前提醒

  • 熟悉涉及到GC的最基本概念到底什么意思(《垃圾回收的算法与实现》
  • 我用go实现(因为其他的都忘了,(╬◣д◢)ムキー!!

源码地址(你的点赞,是我开源的动力)kokool/GCByGo: 《垃圾回收的算法与实现》有感而发 (github.com)

流程

轮到具体实现了,最基本的就是总体划分成两个阶段。

func main(){
    mark_phase()
    sweep_phase()
}

让我们先看一下书本中堆的初始状态

在这里插入图片描述

肯定会得到疑问,我们该具体选择哪种数据结构实现如下的选项?它们需不需要面向对象?

  • 根(roots)?
  • 堆(Heap)?
  • 空闲链表(free-list)?
  • 对象/分块(Object/Chunked)?

到底是理解流程?

其实就是把从roots从上往下指向的object进行mark,这些被markobject就成为了active object

而不满足条件的就变成了non-active object,接着把所有mark清掉。

伪代码

标记阶段

mark_phase()的伪代码。

mark_phase(){
    for(r : $roots)
    //mark(obj)的形参是对象
    mark(*r)
}

用go实现的初步代码

func mark_phase() {
	for r := range roots {
        //获得的r是index,*r则要变成对象,而堆放的是活动对象/非活动对象
		mark(*r)
	}
}

mark()的伪代码:就是简单的递归,另外把标记mark属性改名成marked以防混淆

mark(obj){
    //检查对象没被标记
    if(obj.mark == FALSE)
        //那么就标记它
        obj.mark = TRUE
        //标记后对该对象的指针数组继续访问下一个obj(想象成树结构),继续标记未标记完成的obj
        for(child : children(obj))
            mark(*child)
}

用go实现的初步代码

func mark(obj *Object){
    if obj.marked==false {
        obj.marked=true
        //注意是一定要想象成树结构,因为你指向的不是只有一个对象
        for child:=range obj.children {
            //注意这个*child是对象
            mark(*child)
        }
    }
}

清除阶段

collector 会遍历整个堆,回收没有打上标记的对象

sweep_phase()

sweep_phase(){
    //$heap_start为index,是堆首地址
    sweeping = $heap_start
    //在没遍历到底时
    while(sweeping < $heap_end)
        //如果被标记了
        if(sweeping.mark == TRUE)
            //你都回收了,那么肯定去除标记
            sweeping.mark = FALSE
        //如果找到非活动对象
        else
            //把没标记的对象拉到空闲链表
            sweeping.next = $free_list
            $free_list = sweeping
        //空闲链表的长度改变
        sweeping += sweeping.size
}

分析上面的伪代码,这里的sweeping含有两种属性

  • index
  • object

$free_list的属性有

  • length->index
  • object.next

总之sweeping变得多余了,$free_list既带有空闲链表的长度和下标的属性,更是可以抽象成一个表结构。

那么说这么多废话,写的go的初步代码为

func sweep_phase() {
	free_list = head_start
    heap=[]obj//书本1.4
	for i := range heap {
		if heap[i].marked == true {
			heap[i].marked = false
		} else {
            //move_pointer(*heap[i])//去除指针指向,伪代码隐含了这层意思,无用的指针是要清掉的
			heap[i].next_freeIndex = free_list//指向空闲链表对应的位置
            free_list = i//长度改变
		}
	}
}

看懂上面的废话就觉得可以了?实际上计算机可是比人严谨多了,99%的错误都是由人造成的

尽管不想承认,最终还是感谢参考的内容,可恶的霓虹人,希望各位给他的github.com点赞。

在上图中并不清楚roots指向的各个object中的域到底存放什么信息?因为书本只表示了指针pointer

经过我本人的两天的debug,发现如果使用B树的结构,让每个object既存放data又存放指向其他objectpointer,造成的结果就是:

  • 查找效率低
  • data被计算机理解成pointer,之后递归就面临out of index或者栈溢出->判断条件if marked==false会规避掉这风险->找不到,Go语言贴心地帮你point成默认的0值(真的,它太,我哭死!

总而言之,因为没有正确解决掉识别data还是pointer的问题,就造成这样的结果。

既然如此,那么干脆就不识别了,直接分开就对了,然后使用如下的结构。

B+树

性质

  • 叶子节点存放data,非叶子节点存储key关键字,而我们的key就是要指向active object用的pointer
  • 所有叶子节点增加一个链表指针,正好表示free_list

代码

目录结构

在这里插入图片描述

具体实现

先让我们有个共识

  • -1数值表示指向null
  • -100数值表示不指向任何对象,即叶子节点
  • 域本身要通过objectflag判断是表示成pointer还是data

根据算法篇 第1章的知识与上面的伪代码内容,就应该清楚我们应该如何实现Object,Heap,roots的结构体了

GCByGO\GCMakSweep\notConsidered\PKG\object.go

package PKG

type Object struct {
	//可以用go的空接口类型表示指针数组以方便未来的扩展,当然你也可以用[]string,用map[]其实更便于理解
	//总之我们要实现的要求如下:
	//key为本对象的index
	children       []int//[]string
	node_type      NodeType // flag
	marked         bool     //是否被标记
	size           int      //域的大小,假设一个指针占1字节的域
	next_freeIndex int      //free_list的指向
}

var roots []int//既然堆选择的是存放对象,那么让roots代表指向堆中对象的下标

var free_list int

//设个常量,自己看着办
const (
	HEAP_SIZE = 7 //就拿书本的例子
)

var heap [HEAP_SIZE]Object //书本1.4,堆存放的就是对象

type NodeType string

//专门区分到底是放pointer还是data
func newNodeType(node_type string) NodeType {
	if node_type == "Key" || node_type == "Data" {
		return NodeType(node_type)
	} else {
		panic("error type")
	}
}

那么roots是什么?在书本1.8作者就明示了

在这里插入图片描述

GCByGO\GCMakSweep\notConsidered\PKG\mark_sweep.go

  • Mark_phase()函数
func Mark_phase() {
	for r := range roots {
		var heap_index = roots[r]
		mark(&heap[heap_index])
	}
}
  • mark()函数
func mark(obj *Object) {
	if obj.marked == false {
		obj.marked = true
		if obj.node_type == "Key" {
			for i := range obj.children {
				//共有三种写法实现字符串转整数
				//strconv.Atoi(obj.children[i])
				//fmt.Sprintf("%d",obj.children[i])
				// index,_:=strconv.ParseInt(obj.children[i],10,64)
				index := obj.children[i]
				fmt.Println(index)
				mark(&heap[index])
			}
		}
	}
}
  • Sweep_phase()函数
func Sweep_phase() {
	//默认-1表示指向null
	free_list = -1
	for id := range heap {
		if heap[id].marked == true {
			heap[id].marked = false
		} else {
			move_pointer(&heap[id])
			heap[id].next_freeIndex = free_list
			free_list = id
		}
	}
}

//当这是我们要清除标记的情况:
//	字符串就要设为空字符串切片
//	整数数组则写充满-1的整数切片,因为我们默认-1
//	当然我们其实还有很多表示方法,看自己喜欢,
func move_pointer(obj *Object) {
	// obj.children = []string{""}
	obj.children = []int{-1}
}

数据集测试

GCByGO\GCMakSweep\notConsidered\PKG\create_data.go

  • Init_data()函数:创建数据集
func Init_data() {
	//初始化堆中的所有对象,对于多出来的对象则进行默认操作表示成非活动对象
	h := Object{marked: false, node_type: "Null", children: []int{-1}, size: 0, next_freeIndex: -100}
	for i := range heap {
		heap[i] = h
	}

	var key_type = newNodeType("Key")
	var data_type = newNodeType("Data")

	//对象指向的对象(活动对象)
	heap[1] = Object{children: []int{11}, node_type: data_type, marked: false, size: 2, next_freeIndex: -100}
	heap[3] = Object{children: []int{5, 4}, node_type: key_type, marked: false, size: 2, next_freeIndex: -100}
	heap[4] = Object{children: []int{44}, node_type: data_type, marked: false, size: 2, next_freeIndex: -100}
	heap[5] = Object{children: []int{55}, node_type: data_type, marked: false, size: 2, next_freeIndex: -100}
	//对象指向的对象(非活动对象)
	heap[0] = Object{children: []int{20}, node_type: data_type, marked: false, size: 2, next_freeIndex: -100}
	heap[2] = Object{children: []int{1}, node_type: key_type, marked: false, size: 2, next_freeIndex: -100}
	heap[6] = Object{children: []int{66}, node_type: data_type, marked: false, size: 2, next_freeIndex: -100}
	//roots指向的对象
	roots = []int{1, 3}
}
  • Print_data()函数:输出标记阶段结束后的堆状态
func Print_data() {
	for i := range heap {
		fmt.Printf("--- object %d ---\n", i)
		fmt.Println(heap[i])
	}
}

GCByGo\GCMakSweep\notConsidered\main.go

main()修改

package main

import (
	MS "GCByGo/GCMarkSweep/notConsidered/PKG"
	"fmt"
)

func main() {
	MS.Init_data()
	fmt.Println("### init ###")
	MS.Print_data()

	MS.Mark_phase()
	fmt.Println("### mark phase ###")
	MS.Print_data()

	MS.Sweep_phase()
	fmt.Println("### sweep phase ###")
	MS.Print_data()
}

结果

执行GC前堆的状态,init阶段

在这里插入图片描述

请添加图片描述

标记阶段结束后的堆状态,mark阶段

在这里插入图片描述

请添加图片描述

清除阶段结束后的堆状态,sweep阶段

在这里插入图片描述

请添加图片描述

满足书本的最终结果

请添加图片描述

参考

[1]中村成洋《垃圾回收的算法与实现》

[2] github.com垃圾回收

[3] https://zhuanlan.zhihu.com/p/361287050

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/394252.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于鲸鱼算法优化SVM的发电功率回归预测,eemd-woa-svm

目录 支持向量机SVM的详细原理 SVM的定义 SVM理论 鲸鱼算法的原理及步骤 SVM应用实例,基于eemd分解+鲸鱼算法改进SVM的回归分析 代码 结果分析 展望 支持向量机SVM的详细原理 SVM的定义 支持向量机(support vector machines, SVM)是一种二分类模型,它的基本模型是定义在…

Linux中将多块新硬盘合并成一个,挂载到/mysqldata目录下

需求&#xff1a; 将两块空硬盘合并为“一块”&#xff0c;挂载到指定目录&#xff08;/data&#xff09;下&#xff0c;达到在一个目录使用2块硬盘所有空间的效果。 使用 fdisk -l 命令查看当前系统中的硬盘&#xff0c;如下图&#xff1a; 系统中存在两块未分配的硬盘&#…

< Linux > 进程信号

目录 1、信号入门 生活角度的信号 技术应用角度的信号 前台进程 && 后台进程 信号概念 用kill -l命令察看系统定义的信号列表 信号处理的方式 2、信号产生前 用户层产生信号的方式 3、产生信号 3.1、通过终端按键产生信号 3.2、核心转储core dump 3.3、调用系统函数…

目前全网最全Linux学习笔记

操作系统 用户和计算机的接口&#xff0c;同时也是计算机硬件和其他软件的接口&#xff1b; 用户程序是运行的操作系统之上&#xff1b; 功能&#xff1a; 管理计算机系统的硬件、软件及数据资源&#xff0c;控制程序运行&#xff0c;改善人机界面&#xff0c;为其他应用软…

6.4 深度负反馈放大电路放大倍数的分析

实用的放大电路中多引入深度负反馈&#xff0c;因此分析负反馈放大电路的重点是从电路中分离出反馈网络&#xff0c;并求出反馈系数 F˙\pmb{\dot F}F˙。 一、深度负反馈的实质 在负反馈放大电路的一般表达式中&#xff0c;若 ∣1A˙F˙∣>>1|1\dot A\dot F|>>1…

微服务之 CAP原则

文章目录微服务CAP原则AC 可用性 一致性CP 一致性 分区容错性AP 可用性 分区容错性提示&#xff1a;以下是本篇文章正文内容&#xff0c;SpringCloud系列学习将会持续更新 微服务CAP原则 经过前面的学习&#xff0c;我们对 SpringCloud Netflix 以及 SpringCloud 官方整个生…

Windows 上 执行docker pull命令 提示:The system cannot find the file specified.

错误提示error during connect: This error may indicate that the docker daemon is not running.: Get "http://%2F%2F.%2Fpipe%2Fdocker_engine/v1.24/version": open //./pipe/docker_engine: The system cannot find the file specified.解决办法在cmd 窗口中执…

视觉SLAM十四讲ch7-1视觉里程计笔记

视觉SLAM十四讲ch7-1 视觉里程计笔记本讲目标从本讲开始&#xff0c;开始介绍SLAM系统的重要算法特征点法ORB特征BRIEF实践特征提取与匹配2D-2D&#xff1a;对极几何八点法求E八点法的讨论从单应矩阵恢复R&#xff0c;t小结三角化![在这里插入图片描述](https://img-blog.csdni…

网络割接概述

网络割接概述割接背景企业网络的变化割接概述割接难点割接的操作流程情景模拟及解决方案常见的割接场景割接背景 随着企业业务的不断发展&#xff0c;企业网络为了适应业务的需求不断的改造和优化。无论是硬件的扩容、软件的升级、配置的变更&#xff0c;凡是影响现网运行业务…

转速/线速度/角速度计算FC

工业应用中很多设备控制离不开转速、线速度的计算,这篇博客给大家汇总整理。张力控制的开环闭环方法中也离不开转速和线速度的计算,详细内容请参看下面的文章链接: PLC张力控制(开环闭环算法分析)_plc的收卷张力控制系统_RXXW_Dor的博客-CSDN博客里工业控制张力控制无处不…

十四届蓝桥选拔赛Scratch-2023.01.15 试题解析

十四届蓝桥选拔赛Scratch-2023.01.15 试题解析 单选题&#xff1a; 1. 运行以下程序&#xff0c;当角色被点击时会出现什么效果&#xff1f;&#xff08; C &#xff09; *选择题严禁使用程序验证&#xff0c;选择题不答和答错不扣分 A. 小猫说&#xff1a;“你好&#xff01…

前端脚手架搭建(part4)动态插入

例如控制模板的name、选择使用的插件&#xff0c;动态插入之前vue模板是有选择是否使用pinia、unocss&#xff0c;通过用户的选择&#xff0c;在项目中动态配置插件需要用到ejs读取模板&#xff0c;然后动态修改npm install ejs在libs/utils/index.js添加ejs模板的操作函数impo…

IOS - 抓包通杀篇

IOS中大多数情况&#xff0c;开发者都会使用OC提供的api函数&#xff0c;CFNetworkCopySystemProxySettings来进行代理检测&#xff1b; CFNetworkCopySystemProxySettings 检测函数直接会检测这些ip和端口等&#xff1a; 采用直接附加页面进程&#xff1a; frida -UF -l 通…

04 | 在OAuth 2.0中,如何使用JWT结构化令牌? 笔记

04 | 在OAuth 2.0中&#xff0c;如何使用JWT结构化令牌&#xff1f; JWT 结构化令牌 JSON Web Token&#xff08;JWT&#xff09;是一个开放标准&#xff08;RFC 7519&#xff09;&#xff0c;它定义了一种紧凑的、自包含的方式&#xff0c;用于作为 JSON 对象在各方之间安全地…

28个案例问题分析---10---对生产环境的敬畏--生产环境

一&#xff1a;背景介绍 1&#xff1a;上午9:23&#xff0c;老师没有进行上课&#xff0c;但是却又很多的在线人员&#xff0c;并且在线人员的时间也不正确。 2&#xff1a;开发人员及时练习用户&#xff0c;查看用户上课情况。 3&#xff1a;10点整&#xff0c;询问项目组长发…

BFD配置实验

BFD配置实验拓扑图静态路由联动BFDOSPF联动BFD拓扑图 其中地址各自配置自己的loopback接口&#xff0c;然后接口地址按照AB.1.1.X进行配置。 静态路由联动BFD AR1&#xff1a; [Huawei]bfd [Huawei]bfd 1 bind peer-ip 12.1.1.2 source-ip 12.1.1.1 auto [Huawei]ip route-st…

【强化学习】一文读懂,on-policy和off-policy

一文读懂&#xff0c;on-policy和off-policy 我来谈一下我的理解&#xff0c;不一定对。本文以 Sarsa 和 Q-learning 为例 Sarsa:on-policyQ-learning:off-policy 1. 什么是on-policy和off-policy&#xff1f; 我们有两个策略&#xff1a;行动策略和目标策略 on-policy: 行…

【python】为你绘制玫瑰一束,爱意永存

前言 嗨喽~大家好呀&#xff0c;这里是魔王呐 ❤ ~! 若是有真情&#xff0c;爱意如溪水&#xff0c; 若是有真爱&#xff0c;爱意如阳光&#xff0c; 若是两情相悦&#xff0c;又岂在朝朝暮暮&#xff0c; 女子淡淡的情愫&#xff0c;深深地想念&#xff0c; 浓浓的爱意&a…

Linux :理解编译的四个阶段

目录一、了解编译二、认识编译的四个阶段&#xff08;一&#xff09;预处理&#xff08;二&#xff09;编译&#xff08;三&#xff09;汇编&#xff08;四&#xff09;链接1.静态链接2.动态链接三、分步编译&#xff08;一&#xff09;创建.c文件&#xff08;二&#xff09;预…

【项目】Java树形结构集合分页,java对list集合进行分页

Java树形结构集合分页需求难点实现第一步&#xff1a;查出所有树形集合数据 &#xff08;需进行缓存处理&#xff09;selectTree 方法步骤&#xff1a;TreeUtil类&#xff1a;第二步&#xff1a;分页 GoodsCategoryController分页getGoodsCategoryTree方法步骤&#xff1a;第三…