这是我参与「第五届青训营」伴学笔记创作活动的第 5 天。
今天,我主要学习了Go语言中的自动内存管理的相关概念。
一、C/C++的内存管理及其弊端
在C语言中,我们会使用stdlib库里面的malloc/free来进行内存的分配(C++则更新了new/delete关键字,让其进入了标准语法中),如下所示:
int *a = malloc(sizeof(int) * 1000);
for (int i = 0; i < 1000; ++i)
a[i] = i;
free(a);
对于内存的强大控制能力,也成为了C/C++的一大特性,使得他们能够在很多底层/高性能领域得以应用,例如C被用于操作系统和嵌入式开发,C++在高性能Web开发与游戏引擎开发中的强大地位。
但问题在于,手动管理内存往往会导致一个致命的问题,内存泄漏。我们看看接下来的代码:
int check(char *s) {
char *t = malloc(sizeof(char) * 256);
memcpy(t, s, strlen(s));
//DO something
memcpy(s, t, strlen(s));
return 0;
}
上面的代码有一个十分危险的问题:我们申请了256字节的内存,但是函数结束的时候忘了free了,这使得这一块内存永久的无法被再次使用了(因为在操作系统的眼里,这块内存仍然是被占用的,因为没有被free掉),这种现象一般被认为是“内存泄漏”。
为了处理这种问题,开发者们不断精进自己的编程技巧,加大审查力度,引入了各种新型软件与开发工具来侦测是否出现了内存泄漏,以便快速定位并解决。但是,如果有一种可以一劳永逸,解放程序员的方法呢?为什么不让计算机自己来判断内存是否可以被回收,而不是程序员来告诉系统吗?
二、自动内存管理系统
在很多现代语言中(例如Java,Python,Go等),都有一套自研的自动内存管理系统,用来检查某些内存是不是已经“死”了,并将其回收掉。例如上面的函数,系统就会意识到某块内存在函数使用完毕后,就再也没有其他指针或者类似的东西指向它,那么他就是“死”的,就可以进行回收。
一般来说,垃圾回收(GC)首先需要保证它不会将正在被使用的内存给回收掉(正确性),随后我们需要在速度和效率上达到平衡(前者是指垃圾回收消耗的时间,后者是指回收到的内存空间的大小与实际死亡空间大小的占比)。
一般来说,我们有两种策略:第一种是给所有指针打上标记,隔一段时间就扫描一遍,看看有没有是断开连接,不会被访问的;这种方式的效率很好,但是遍历很占时间。第二种则是选定某个根地点开始搜索,将所有搜索到的点打上标记,随后删除那些没有标记的指针指向的内存。这种方式相对节省时间,但是会导致一定程度的“死角”(部分内存空间是无法被搜索到的)。