Gabbage collect(GC) 垃圾回收算法Go实现

GC最早 出现 是在 lisp 语言设计开始1年后,的1959年夏天,实现GC的是一个叫D.Edwards的人。自从Gc被提出来的 70多年里这方面有了大量的研究,不过GC之所以能被很多现代高级语言作为 标配,还是以Java的兴起带了个头。有了GC程序员就不需要自己去管理麻烦的内存分配回收也避免了一些可能的回收内存所带来的的安全性问题。

GC需要做的有两件事

  1. 找到内存空间里的垃圾
  2. 回收垃圾,让程序员能再次利用这部分空间

没有GC的情况下:
1.需要自己手动管理那些地方的内存需要回收
2.释放没用空间的同事对于引用的指针忘记初始化了,那么就会造成"悬垂指针",可能会造成严
3.如果释放了使用中的内存程序直接发生错误。
4.以上BUG都是没有正确的释放内存所导致,如果出现了很难找到原因。
重的安全问题。
使用GC后:
1.交给计算机回收内存引用,释放没有引用的内存空间,不需要再考虑内存释放出现Bug的问题。

GC常见的三种算法
1.标记-清除算法
2.引用计数器(缺点:循环应用 无法清理)
3.GC复制算法

在这里插入图片描述

引用-标记法 Go实现

在这里插入图片描述

type Heap struct {
	root *Obj //根节点
	size int
	objs []*Obj
}

type ObjHead struct {
	myname string
	ismark bool
	ref []*Obj
}
//是否标记
func ( oh *ObjHead) ifmark() bool{
	return oh.ismark
}

//是否标记
func (oh *ObjHead) help(){
	fmt.Printf("%s 被标记了 快去救救它吧!\n ",oh.myname)
}
//添加引用
func ( oh *ObjHead) addRef(obj *Obj){
		fmt.Printf("%s  在山里 捡到一个 %s ! !\n ",oh.myname,obj.oh.myname)
	if oh.ref == nil{
		oh.ref = make([]*Obj,0,1)
	}
	oh.ref = append(oh.ref, obj)

}

//添加引用
func ( oh *ObjHead) subRef(obj *Obj){
	for i,m := range oh.ref {
		if m ==  obj{
			fmt.Printf(" %s 抛弃了 %s !\n",oh.myname,obj.oh.myname)
			oh.ref[i] = nil
			return
		}
	}
	fmt.Printf(" %s 和 %s 没有引用关系 不需要解除!\n",oh.myname,obj.oh.myname)
}

//对象
type Obj struct {
	//对象实体
	data interface{}
	//对象头
	oh ObjHead
}

//生成对象
func NewObj(name string) *Obj{
	head :=ObjHead{myname:name,ismark:false,ref:nil}
	fmt.Printf("伟大的 %s 诞生了....\n",name)
	return &Obj{
		data: nil,
		oh:   head,
	}
}

//清理分为2 标记 和 清除
func(h *Heap) Sweep(){
	//标记
	mark(h.root)
	//清理
	sweep(h.objs)
}


//创建堆
func NewHeap() *Heap{
	return &Heap{
		size:0,
		objs:make([]*Obj,0,10)}
}

//创建对象
func(h *Heap)NewObj(name string) *Obj{
	obj := NewObj(name)
	usenilspace := false
	//利用已经被回收的内存
	for i,space := range h.objs{
		if space == nil{
			h.objs[i] = obj
			usenilspace = true
		}
	}
	if usenilspace == false{
		h.objs = append(h.objs[h.size:],obj)
	}
	h.size ++ 
	return obj
}

//从根节点 扫描所有引用对象
func  mark(obj *Obj) {
	if obj == nil{
		return
	}
	//从root开始扫描所有引用对象
	if (obj.oh.ismark == false){
		obj.oh.ismark = true
	}
	for _,i:= range obj.oh.ref  {
		mark(i)
	}
}

//回收内存
func  sweep(objs []*Obj) {
	for i,m := range objs{
		if m.oh.ismark == false{
			m.oh.help()
			//回收对象 指针被修改为nil 遍历数组时可以复用
			objs[i] = nil
		}else{
			//修改为false 为下次回收做准备
			m.oh.ismark = false
		}
	}
}

func main(){
	hp := NewHeap()
	root := NewObj("爷爷")
	//设置更节点
	hp.root = root

	o1 := hp.NewObj("大娃")
	o2 := hp.NewObj("二娃")
	o3 := hp.NewObj("三娃")
	o4 := hp.NewObj("四娃")
	o5 := hp.NewObj("五娃")
	o6 := hp.NewObj("六娃")
	hp.NewObj("七娃")//七娃 生成了对象没被引用

	root.oh.addRef(o1) //相当于 root = o1
	o1.oh.addRef(o2)
	o1.oh.addRef(o3)
	o1.oh.addRef(o4)
	root.oh.addRef(o5)
	o2.oh.addRef(o6)
	root.oh.subRef(o1) //大娃 关联了 二娃 三娃 四娃 二娃 关联了 六娃 二三四六娃 会随着大娃的解出引用同时解除引用
	hp.Sweep()
}

在这里插入图片描述

引用计数法

GC 原本是一种“释放怎么都无法被引用的对象的机制”。那么人们自然而然地就会 想到,可以让所有对象事先记录下“有多少程序引用自己”。让各对象知道自己的“人气指 数”,从而让没有人气的对象自己消失,这就是引用计数法(Reference Counting),它是 George E. Collins [6] 于 1960 年钻研出来的。

type Heap struct {
	root *Obj //根节点
	size int
	objs []*Obj
}

//创建堆
func NewHeap() *Heap{
	return &Heap{
		size:0,
		objs:make([]*Obj,0,10)}
}
//创建堆
func (hp *Heap)ScanHeap(){
	for i,m:= range hp.objs{
		if m.oh.ref_cnt == 0{
			fmt.Printf("%s 计数器 为0 已经牺牲了!\n",m.oh.myname)
			hp.objs[i] = nil
		}else{
			fmt.Printf("%s 存活确认!\n",m.oh.myname)
		}
	}

}

type ObjHead struct {
	ref_cnt int
	myname string
	ref []*Obj
}
//对象
type Obj struct {
	//对象实体
	data interface{}
	//对象头
	oh ObjHead
}

func NewObj(name string,isroot bool) *Obj{
	oh := ObjHead{
		ref_cnt: 0,
		myname:  name,
		ref:     nil,
	}
	if isroot == true{
		oh.ref_cnt = 1
	}
	return &Obj{
		data: nil,
		oh:  oh,
	}
}

//设置对象引用
func (o *Obj)addRef(index int,obj *Obj){
	if len(o.oh.ref) == 0{
		o.oh.ref = make([]*Obj,0,10)
		if index != -1{
			fmt.Println("引用对象不存在!")
			return
		}
	}
	if index == -1{
		o.oh.ref = append(o.oh.ref, obj)
		updata_ptr(nil,obj)
	}else{
		if o.oh.ref[index] != nil{
			ptr := o.oh.ref[index]
			updata_ptr(&ptr,obj)
		}

	}
}
//计数器加1
func inc_ref_cnt(obj *Obj){
	if obj == nil{
		return
	}
	fmt.Printf("%s 计数器 加 1\n",obj.oh.myname)
	obj.oh.ref_cnt ++
}

//计数器减1
func dec_ref_cnt(obj *Obj){
	obj.oh.ref_cnt --
	fmt.Printf("%s 计数器 减 1\n",obj.oh.myname)
	//如果 当前技术器 为 0那么就变成 垃圾了 对其 引用的对象 引用减1
	if (obj.oh.ref_cnt == 0 || obj == nil){
		for _,v := range obj.oh.ref{
			dec_ref_cnt(v)
		}
	}
}

func updata_ptr(ptr **Obj,obj *Obj){
	inc_ref_cnt(obj)
	if ptr != nil{
		dec_ref_cnt(*ptr)
		*ptr = obj
	}
}

//创建对象
func(h *Heap)NewObj(name string) *Obj{
	var obj *Obj
	if h.size == 0{
		obj = NewObj(name,true)
	}else{
		obj = NewObj(name,false)
	}
	usenilspace := false
	for i,space := range h.objs{
		if space == nil{
			h.objs[i] = obj
			usenilspace = true
		}
	}
	if usenilspace == false{
		h.objs = append(h.objs,obj)
	}
	h.size ++
	return obj
}
func main(){
	hp := NewHeap()
	root := hp.NewObj("爷爷")
	o1 := hp.NewObj("大娃")
	o2 := hp.NewObj("二娃")
	o3 := hp.NewObj("三娃")
	o4 := hp.NewObj("四娃")
	o5 := hp.NewObj("五娃")
	o6 := hp.NewObj("六娃")
	o7 := hp.NewObj("七娃")//七娃 生成了对象没被引用
	root.addRef(-1,o1)
	root.addRef(-1,o2)
	o2.addRef(-1,o3) //添加 二娃 第 0个引用
	o2.addRef(-1,o4)//添加 二娃 第 1个引用
	o4.addRef(-1,o5)
	o4.addRef(-1,o6)

	//下面就是经典的 循环引用了
	o6.addRef(-1,o7)
	o7.addRef(-1,o6)
	o2.addRef(1,nil)  //把 二娃引用 四娃置为nil
	//o5.addRef(0,o6)
	hp.ScanHeap()
}

在这里插入图片描述

引用计数器 问题之一 就是无法解决循环引用问题。
在这里插入图片描述

优点

在引用计数法中,每个对象始终都知道自己的被引用数(就是计数器的值)。当被引用数 的值为 0 时,对象马上就会把自己作为空闲空间连接到空闲链表。也就是说,各个对象在变 成垃圾的同时就会立刻被回收。要说这有什么意义,那就是内存空间不会被垃圾占领。垃圾 全部都已连接到空闲链表,能作为分块再被利用。
另一方面,在其他的 GC 算法中,即使对象变成了垃圾,程序也无法立刻判别。只有当 分块用尽后 GC 开始执行时,才能知道哪个对象是垃圾,哪个对象不是垃圾。也就是说,直 到 GC 执行之前,都会有一部分内存空间被垃圾占用。

分代垃圾回收
对象的年龄

人们从众多程序案例中总结出了一个经验:“大部分的对象在生成后马上就变成了垃圾, 很少有对象能活得很久。”分代垃圾回收利用该经验,在对象中导入了“年龄”的概念,经历 过一次 GC 后活下来的对象年龄为 1 岁。

新生代对象和老年代对象

分代垃圾回收中把对象分类成几代,针对不同的代使用不同的 GC 算法,我们把刚生成 的对象称为新生代对象,到达一定年龄的对象则称为老年代对象。
众所周知,新生代对象大部分会变成垃圾。如果我们只对这些新生代对象执行 GC 会怎 么样呢?除了引用计数法以外的基本算法,都会进行只寻找活动对象的操作(如 GC 标记 - 清除算法的标记阶段和 GC 复制算法等)。因此,如果很多对象都会死去,花费在 GC 上的时 间应该就能减少。
我们将对新对象执行的 GC 称为新生代 GC(minor GC)。minor 在这里的意思是“小规模的”。 新生代 GC 的前提是大部分新生代对象都没存活下来,GC 在短时间内就结束了。
另一方面,新生代 GC 将存活了一定次数的新生代对象当作老年代对象来处理。我们把
类似于这样的新生代对象上升为老年代对象的情况称为晋升(promotion) 。因为老年代对象很难成为垃圾,所以我们对老年代对象减少执行 GC 的频率。相对于新生代 GC,我们将面向老年代对象的 GC 称为老年代 GC(major GC)。在这里有一点需要注意,那就是分代垃圾回收不能单独用来执行 GC。我们需要把它和
之前介绍的基本算法结合在一起使用,来提高那些基本算法的效率。 也就是说,分代垃圾回收不是跟 GC 标记 - 清除算法和 GC 复制算法并列在一起供我们
选择的算法,而是需要跟这些基本算法一并使用。
在这里插入图片描述

比较熟知的Java用的就是分代回收的算法。 分代回收还是有点麻烦的代码就懒得写了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值