Files
bulma/gc_tri_color_demo.go

366 lines
11 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package main
import (
"container/list"
"fmt"
"sync"
"sync/atomic"
"time"
)
// 三色标记常量(使用int32以便原子操作)
const (
White int32 = iota // 0: 未标记/垃圾对象
Grey // 1: 待处理态
Black // 2: 完成态/存活对象
)
// Object 模拟堆内存中的引用对象
// 注意:Color字段使用原子操作保护
type Object struct {
ID string
Color int32 // 使用int32支持原子操作
Children []*Object // 子对象用指针,模拟真实引用关系
mu sync.Mutex // 保护Children修改
}
// NewObject 创建新对象,初始为白色
func NewObject(id string) *Object {
return &Object{
ID: id,
Color: White,
Children: make([]*Object, 0),
}
}
// GetColor 安全获取对象颜色(原子读取)
func (o *Object) GetColor() int32 {
return atomic.LoadInt32(&o.Color)
}
// SetColor 安全设置对象颜色(原子写入)
func (o *Object) SetColor(newColor int32) {
atomic.StoreInt32(&o.Color, newColor)
}
// CompareAndSetColor 原子比较并设置颜色(CAS操作)
func (o *Object) CompareAndSetColor(oldColor, newColor int32) bool {
return atomic.CompareAndSwapInt32(&o.Color, oldColor, newColor)
}
// AddChild 线程安全地添加子对象
func (o *Object) AddChild(child *Object) {
o.mu.Lock()
defer o.mu.Unlock()
o.Children = append(o.Children, child)
}
// GetChildren 线程安全地获取子对象
func (o *Object) GetChildren() []*Object {
o.mu.Lock()
defer o.mu.Unlock()
// 创建副本避免并发修改问题
children := make([]*Object, len(o.Children))
copy(children, o.Children)
return children
}
// 模拟写屏障:当黑色对象引用白色对象时,将白色对象标记为灰色
func WriteBarrier(parent *Object, child *Object, grayQueue *list.List, queueMutex *sync.Mutex) {
// 原子读取颜色,避免竞态
parentColor := parent.GetColor()
childColor := child.GetColor()
if parentColor == Black && childColor == White {
// 使用CAS确保颜色转换的原子性
if child.CompareAndSetColor(White, Grey) {
queueMutex.Lock()
grayQueue.PushBack(child)
queueMutex.Unlock()
fmt.Printf("WriteBarrier: [%s](黑)→ [%s](白),强制将[%s]标记为灰色\n",
parent.ID, child.ID, child.ID)
}
}
}
// 辅助方法:递归查找第一个黑色对象,用于模拟业务协程场景
func findBlackObject(objs []*Object) *Object {
for _, obj := range objs {
if obj.GetColor() == Black {
return obj
}
// 递归遍历子对象
children := obj.GetChildren()
if child := findBlackObject(children); child != nil {
return child
}
}
return nil
}
// 辅助方法:递归获取所有对象,模拟堆内存遍历
func getAllObjects(objs []*Object) []*Object {
var all []*Object
visited := make(map[*Object]bool)
var mutex sync.Mutex
var traverse func(*Object)
traverse = func(o *Object) {
mutex.Lock()
if visited[o] {
mutex.Unlock()
return
}
visited[o] = true
mutex.Unlock()
all = append(all, o)
children := o.GetChildren()
for _, child := range children {
traverse(child)
}
}
for _, o := range objs {
traverse(o)
}
return all
}
// 三色标记与清除主流程
func TriColorMarkAndSweep(rootObjects []*Object) ([]*Object, []*Object) {
// 1. 初始化灰色队列和同步锁(保证并发安全)
grayQueue := list.New()
var queueMutex sync.Mutex
// 2. 初始标记阶段(STW):标记根对象
fmt.Println("===== 阶段1:初始标记(STW =====")
for _, root := range rootObjects {
// 原子检查并设置颜色
if root.GetColor() == White {
root.SetColor(Grey)
fmt.Printf("根对象[%s] 白→灰,加入灰色队列\n", root.ID)
queueMutex.Lock()
grayQueue.PushBack(root)
queueMutex.Unlock()
}
}
// 3. 并发标记阶段(无STW)
fmt.Println("\n===== 阶段2:并发标记(无STW =====")
var wg sync.WaitGroup
// 模拟业务协程运行,并发修改引用关系
wg.Add(1)
go func() {
defer wg.Done()
time.Sleep(50 * time.Millisecond) // 等待GC标记协程启动并处理部分对象
fmt.Println("\n[业务协程] 正在运行,修改对象引用关系...")
// 模拟并发修改:黑色对象指向新的白色对象
blackObj := findBlackObject(rootObjects)
if blackObj != nil {
newWhiteObj := NewObject("新创建的白色对象")
fmt.Printf("[业务协程] 尝试让黑色对象[%s]引用新白色对象[%s]\n",
blackObj.ID, newWhiteObj.ID)
// 触发写屏障,拦截危险引用
WriteBarrier(blackObj, newWhiteObj, grayQueue, &queueMutex)
// 线程安全地添加子对象
blackObj.AddChild(newWhiteObj)
// 验证写屏障效果
time.Sleep(10 * time.Millisecond)
if newWhiteObj.GetColor() == Grey {
fmt.Printf("[业务协程] 验证: [%s] 已被写屏障保护,状态=灰色\n", newWhiteObj.ID)
} else {
fmt.Printf("[业务协程] 警告: [%s] 未被写屏障保护,状态=%d\n", newWhiteObj.ID, newWhiteObj.GetColor())
}
} else {
fmt.Println("[业务协程] 未找到黑色对象,跳过引用修改")
}
}()
// GC标记协程:遍历灰色队列,处理对象标记
wg.Add(1)
go func() {
defer wg.Done()
for {
var grayObj *Object
// 原子操作获取灰色对象
queueMutex.Lock()
if grayQueue.Len() == 0 {
queueMutex.Unlock()
break
}
// 取出队列头部的灰色对象
elem := grayQueue.Front()
grayObj = elem.Value.(*Object)
grayQueue.Remove(elem)
queueMutex.Unlock()
fmt.Printf("处理灰色对象: [%s] (当前颜色=%d)\n", grayObj.ID, grayObj.GetColor())
// 原子获取子对象列表(避免并发修改)
children := grayObj.GetChildren()
// 遍历当前对象的所有子对象
for _, child := range children {
// 原子检查子对象颜色
if child.GetColor() == White {
// 使用CAS确保状态转换正确
if child.CompareAndSetColor(White, Grey) {
fmt.Printf(" [%s] 引用了 [%s],白→灰,加入队列\n", grayObj.ID, child.ID)
queueMutex.Lock()
grayQueue.PushBack(child)
queueMutex.Unlock()
}
}
}
// 所有子对象处理完成,当前对象转为黑色
// 使用CAS确保从灰色到黑色的转换
if grayObj.CompareAndSetColor(Grey, Black) {
fmt.Printf(" [%s] 所有子对象标记完成,灰→黑\n", grayObj.ID)
} else {
fmt.Printf(" [%s] 颜色转换失败,当前状态=%d\n", grayObj.ID, grayObj.GetColor())
}
time.Sleep(10 * time.Millisecond) // 模拟标记耗时,便于观察流程
}
fmt.Println("灰色队列已空,并发标记阶段完成")
}()
// 等待业务协程和GC标记协程完成
wg.Wait()
// 4. 重新标记阶段(STW):处理写屏障捕获的漏标对象
fmt.Println("\n===== 阶段3:重新标记(STW =====")
queueMutex.Lock()
for grayQueue.Len() > 0 {
elem := grayQueue.Front()
obj := elem.Value.(*Object)
grayQueue.Remove(elem)
if obj.GetColor() == Grey {
// 遍历子对象,确保标记完整
children := obj.GetChildren()
for _, child := range children {
if child.GetColor() == White {
if child.CompareAndSetColor(White, Grey) {
fmt.Printf("重新标记补标: [%s] 引用的 [%s](白→灰)\n", obj.ID, child.ID)
grayQueue.PushBack(child)
}
}
}
// 标记完成后转为黑色
obj.SetColor(Black)
fmt.Printf("重新标记确认: 对象[%s]为存活,灰→黑\n", obj.ID)
}
}
queueMutex.Unlock()
// 5. 收集所有堆对象(包括未被根对象引用的孤立对象)
fmt.Println("\n===== 阶段4:收集所有堆对象 =====")
allObjects := getAllObjects(rootObjects)
// 6. 创建真正的孤立对象(无根可达)
trueGarbage1 := NewObject("真正的孤立对象-临时会话")
trueGarbage2 := NewObject("真正的孤立对象-过期验证码")
// 添加到所有对象集合,模拟真实堆内存
allObjects = append(allObjects, trueGarbage1, trueGarbage2)
// 7. 并发清除阶段:回收白色对象
fmt.Println("\n===== 阶段5:并发清除(无STW =====")
var garbageObjects []*Object
var liveObjects []*Object
for _, obj := range allObjects {
if obj.GetColor() == White {
garbageObjects = append(garbageObjects, obj)
fmt.Printf("对象[%s] 为白色,判定为垃圾,执行回收\n", obj.ID)
} else {
liveObjects = append(liveObjects, obj)
fmt.Printf("对象[%s] 为存活对象(颜色=%d),保留\n", obj.ID, obj.GetColor())
// 重置为白色,为下一次GC做准备
obj.SetColor(White)
}
}
return garbageObjects, liveObjects
}
func main() {
// ========== 构建对象引用链路,模拟真实业务场景 ==========
// 根对象:模拟全局变量/栈帧变量
root1 := NewObject("根对象-用户服务")
root2 := NewObject("根对象-订单服务")
// 构建引用链路:用户服务 → 用户缓存 → 具体用户
userCache := NewObject("用户缓存")
user1 := NewObject("用户-张三")
user2 := NewObject("用户-李四")
userCache.AddChild(user1)
userCache.AddChild(user2)
root1.AddChild(userCache)
// 构建引用链路:订单服务 → 订单缓存 → 具体订单
orderCache := NewObject("订单缓存")
order1 := NewObject("订单-202311001")
orderCache.AddChild(order1)
root2.AddChild(orderCache)
// 创建真实可访问对象
validObj := NewObject("有效会话数据")
order1.AddChild(validObj)
// ========== 执行三色标记GC流程 ==========
rootObjects := []*Object{root1, root2}
garbage, live := TriColorMarkAndSweep(rootObjects)
// ========== 输出GC执行结果 ==========
fmt.Printf("\n===== GC执行完成,统计结果 =====\n")
fmt.Printf("存活对象数量: %d\n", len(live))
fmt.Printf("垃圾对象数量: %d\n", len(garbage))
fmt.Println("\n垃圾对象详情:")
for _, g := range garbage {
fmt.Printf("- %s (最终颜色=%d)\n", g.ID, g.GetColor())
}
// 验证关键对象状态
fmt.Println("\n===== 关键对象状态验证 =====")
expectedGarbage := []string{"真正的孤立对象-临时会话", "真正的孤立对象-过期验证码"}
found := 0
for _, g := range garbage {
for _, expected := range expectedGarbage {
if g.ID == expected {
fmt.Printf("✓ 对象[%s] 被正确识别为垃圾对象 (颜色=%d)\n", g.ID, g.GetColor())
found++
}
}
}
fmt.Printf("垃圾对象识别准确率: %.0f%% (%d/%d)\n", float64(found)/float64(len(expectedGarbage))*100, found, len(expectedGarbage))
// 验证写屏障对象状态
fmt.Println("\n===== 写屏障保护验证 =====")
newObjFound := false
for _, l := range live {
if l.ID == "新创建的白色对象" {
newObjFound = true
if l.GetColor() == Black {
fmt.Printf("✓ 对象[%s] 被正确标记为存活 (颜色=%d),写屏障保护成功\n", l.ID, l.GetColor())
} else {
fmt.Printf("✗ 对象[%s] 未被正确标记 (颜色=%d),写屏障保护失败\n", l.ID, l.GetColor())
}
}
}
if !newObjFound {
fmt.Println("⚠ 未找到写屏障创建的新对象,可能未被正确处理")
}
}