Go 指针陷阱

2 篇文章 1 订阅

Go 指针陷阱

uintptr被GC当做普通整数对象,不会阻止所“引用”的对象被回收

package main

import (
	"time"
	"unsafe"
)

type data struct {
	x [1024 * 100]byte
}

func test() uintptr {
	p := &data{}
	return uintptr(unsafe.Pointer(p))
}
func main() {
	const N = 10000
	cache := new([N]uintptr)
	for i := 0; i < N; i++ {
		cache[i] = test()
		time.Sleep(time.Millisecond)
	}
}
root@ljolan:/home/ljolan# go build -o test gc_if.go && GODEBUG="gctrace=1" ./test
gc 1 @0.064s 0%: 0.10+0.17+0.001 ms clock, 0.40+0/0.072/0.011+0.006 ms cpu, 4->4->0 MB, 5 MB goal, 4 P
gc 2 @0.133s 0%: 0.56+0.30+0.001 ms clock, 2.2+0/0.056/0+0.006 ms cpu, 4->4->0 MB, 5 MB goal, 4 P
gc 3 @0.211s 0%: 0.014+0.067+0.001 ms clock, 0.057+0/0.051/0.020+0.005 ms cpu, 4->4->0 MB, 5 MB goal, 4 P
gc 4 @0.283s 0%: 0.041+0.75+0.008 ms clock, 0.16+0/0.27/0.12+0.035 ms cpu, 4->4->0 MB, 5 MB goal, 4 P
gc 5 @0.363s 0%: 0.11+0.099+0.001 ms clock, 0.46+0/0.063/0.005+0.006 ms cpu, 4->4->0 MB, 5 MB goal, 4 P
gc 6 @0.441s 0%: 0.014+0.93+0.002 ms clock, 0.057+0/0.063/0.066+0.011 ms cpu, 4->4->0 MB, 5 MB goal, 4 P
gc 7 @0.519s 0%: 0.052+0.17+0.001 ms clock, 0.21+0/0.066/0+0.007 ms cpu, 4->4->0 MB, 5 MB goal, 4 P

合法的unsafe.Pointer被当做普通指针,会阻止所“引用”的对象被回收

package main

import (
	"time"
	"unsafe"
)

type data struct {
	x [1024 * 100]byte
}

func test() unsafe.Pointer {
	p := &data{}
	return unsafe.Pointer(p)
}
func main() {
	const N = 10000
	cache := new([N]unsafe.Pointer)
	for i := 0; i < N; i++ {
		cache[i] = test()
		time.Sleep(time.Millisecond)
	}
}
root@ljolan:/home/ljolan# go build -o test gc_if.go && GODEBUG="gctrace=1" ./test
gc 1 @0.070s 0%: 0.10+0.76+0.001 ms clock, 0.43+0/0.10/0.011+0.005 ms cpu, 4->4->3 MB, 5 MB goal, 4 P
gc 2 @0.137s 0%: 0.10+0.17+0.001 ms clock, 0.42+0/0.069/0.003+0.006 ms cpu, 7->7->7 MB, 8 MB goal, 4 P
gc 3 @0.277s 0%: 0.097+0.17+0.001 ms clock, 0.39+0/0.081/0.004+0.006 ms cpu, 14->14->14 MB, 15 MB goal, 4 P
gc 4 @0.554s 0%: 0.033+0.17+0.001 ms clock, 0.13+0/0.075/0.004+0.006 ms cpu, 29->29->29 MB, 30 MB goal, 4 P
gc 5 @1.098s 0%: 1.7+0.65+0.001 ms clock, 7.1+0/0.090/0+0.006 ms cpu, 56->56->56 MB, 58 MB goal, 4 P
gc 6 @2.136s 0%: 0.11+0.11+0.001 ms clock, 0.44+0/0.10/0.003+0.005 ms cpu, 111->111->111 MB, 113 MB goal, 4 P
gc 7 @4.111s 0%: 0.050+0.15+0.001 ms clock, 0.20+0/0.13/0.004+0.005 ms cpu, 216->216->216 MB, 222 MB goal, 4 P

指向对象成员的unsafe.Pointer,同样能确保对象不被回收

package main

import (
	"time"
	"unsafe"
)

type data struct {
	x [1024 * 100]byte
	y int
}

func test() unsafe.Pointer {
	p := data{}
	return unsafe.Pointer(&p.y)
}
func main() {
	const N = 10000
	cache := new([N]unsafe.Pointer)
	for i := 0; i < N; i++ {
		cache[i] = test()
		time.Sleep(time.Millisecond)
	}
}
root@ljolan:/home/ljolan# go build -o test gc_if.go && GODEBUG="gctrace=1" ./test
gc 1 @0.068s 0%: 0.037+0.13+0.001 ms clock, 0.14+0/0.095/0.022+0.007 ms cpu, 4->4->3 MB, 5 MB goal, 4 P
gc 2 @0.144s 0%: 0.051+0.14+0.001 ms clock, 0.20+0/0.051/0.031+0.006 ms cpu, 7->7->7 MB, 8 MB goal, 4 P
gc 3 @0.289s 0%: 0.13+0.22+0.003 ms clock, 0.54+0/0.10/0.087+0.013 ms cpu, 14->14->14 MB, 15 MB goal, 4 P
gc 4 @0.569s 0%: 0.25+0.26+0.006 ms clock, 1.0+0/0.20/0.047+0.024 ms cpu, 29->29->29 MB, 30 MB goal, 4 P
gc 5 @1.087s 0%: 0.075+0.15+0.001 ms clock, 0.30+0/0.083/0.005+0.006 ms cpu, 56->56->56 MB, 58 MB goal, 4 P

由于可以用 unsafe.Pointer、uintptr 创建 “dangling pointer【悬挂指针】” 等非法指针,所以在使⽤时需要特别小心。另外,cgo C.malloc 等函数所返回指针,与 GC 无关

  • 指针构成的 “循环引用” 加上 runtime.SetFinalizer 会导致内存泄露
package main

import (
	"fmt"
	"runtime"
	"time"
)

type data struct {
	x [1024 * 100]byte
	y *data
}

func test() {
	a, b := data{}, data{}
	a.y = &b
	b.y = &a
	runtime.SetFinalizer(&a, func(a *data) {
		fmt.Printf("a %p final.\n", a)
	})
	runtime.SetFinalizer(&b, func(b *data) {
		fmt.Printf("b %p final.\n", b)
	})
}
func main() {
	for {
		test()
		time.Sleep(time.Millisecond)
	}
}
root@ljolan:/home/ljolan# go build -gcflags "-N -l" -o test2 gc_if2.go && GODEBUG="gctrace=1" ./test2
gc 1 @0.040s 0%: 0.007+0.81+0.001 ms clock, 0.031+0.20/0.69/0.41+0.005 ms cpu, 4->4->7 MB, 5 MB goal, 4 P
gc 2 @0.116s 0%: 0.028+1.2+0.001 ms clock, 0.11+0.59/1.1/1.8+0.006 ms cpu, 15->15->22 MB, 16 MB goal, 4 P
gc 3 @0.327s 0%: 0.017+3.0+0.001 ms clock, 0.071+0/2.9/7.5+0.006 ms cpu, 44->44->66 MB, 45 MB goal, 4 P
gc 4 @0.965s 0%: 0.10+8.0+0.002 ms clock, 0.40+0/7.7/22+0.009 ms cpu, 130->130->194 MB, 133 MB goal, 4 P
gc 5 @7.312s 0%: 0.68+23+0.001 ms clock, 2.7+0.59/23/66+0.006 ms cpu, 379->379->564 MB, 388 MB goal, 4 P
gc 6 @23.550s 0%: 0.048+89+0.002 ms clock, 0.19+0.56/86/213+0.010 ms cpu, 1100->1101->1639 MB, 1129 MB goal, 4 P

垃圾回收器能正确处理 “指针循环引用”,但⽆法确定 Finalizer 依赖次序,也就⽆法调用 Finalizer 函数,这会导致目标对象无法变成不可达状态,其所占⽤内存⽆法被回收。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值