问题:
如果把golang分配的变量,其指针通过cgo传给c,并被c存储,那这个变量还能被gc回收吗?
实验代码:
test_memory_leak.go
Go
package main
/*
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
char* cMalloc() {
char *mem = (char*)malloc(1024 * 1024 * 16);
return mem;
}
void cMemset(char* mem) {
memset(mem, '-', 1024 * 1024 * 16);
}
int arrLen = 1000;
int arrIndex;
char* globalMemAddr[1000];
void printAddr(char* mem) {
if (arrIndex+1 >= arrLen) {
arrIndex = 0;
} else {
arrIndex++;
}
globalMemAddr[arrIndex] = mem;
printf("index: %d, addr: %p\n", arrIndex, globalMemAddr[arrIndex]);
}
*/
import "C"
import (
"fmt"
"net/http"
_ "net/http/pprof"
"os"
"sync"
"time"
"unsafe"
)
var size int = 1024 * 1024 * 16
func main() {
go func() {
_ = http.ListenAndServe("0.0.0.0:9091", nil)
}()
if len(os.Args) > 1 && os.Args[1] == "1" {
var wg sync.WaitGroup
for {
wg.Add(1)
go doCMalloc(&wg)
wg.Wait()
time.Sleep(500 * time.Millisecond)
}
} else {
var wg sync.WaitGroup
for {
wg.Add(1)
go doGoMalloc(&wg)
wg.Wait()
time.Sleep(500 * time.Millisecond)
}
}
}
// 无泄漏
func doCMalloc(wg *sync.WaitGroup) {
defer wg.Done()
cptr := C.cMalloc()
C.cMemset(cptr)
C.printAddr(cptr)
bs := C.GoBytes(unsafe.Pointer(cptr), C.int(size))
fmt.Printf("1: %s .. %s\n", string(bs[0:8]), string(bs[size-8:size]))
C.free(unsafe.Pointer(cptr))
}
// 无泄漏
func doGoMalloc(wg *sync.WaitGroup) {
defer wg.Done()
bs := make([]byte, size, size)
cptr := (*C.char)(unsafe.Pointer(&bs[0]))
C.cMemset(cptr)
C.printAddr(cptr)
fmt.Printf("2: %s .. %s\n", string(bs[0:8]), string(bs[size-8:size]))
}
运行代码分支2:
GODEBUG=gctrace=1 ./test_memory_leak 2
终端显示:
top显示:
pprof数据:
go tool pprof -http=10.10.10.244:9000 http://127.0.0.1:9091/debug/pprof/allocs
alloc_objects:
inuse_space:
观察到的数据:
1、变量地址始终在0xc000180000和0xc001200000之间交换,说明内存被回收重复利用。
2、每次gc结束后内存始终控制在16MB。
3、进程使用的内存始终在40多MB,并没有太大变化。
4、inuse_space保持16MB不变,而alloc_objects在不断增长。
结论:
golang分配的变量,其指针被cgo引用,不影响golang对其进行垃圾回收。
--end--