2023-05-26:golang关于垃圾回收和析构的选择题,代码如下:

代码的运行结果是什么?并说明原因。注意析构是无序的。

A. 结束

B. a被回收--b被回收--结束

C. b被回收--a被回收--结束

D. B和C都有可能

答案2023-05-26:

golang的垃圾回收算法跟java一样,都是根可达算法。代码中main0函数里a和b是互相引用,但是a和b没有外部引用。因此a和b会被当成垃圾被回收掉。而析构函数的调用不是有序的,所以B和C都有可能,答案选D。让我们看看答案是什么,如下:

在这里插入图片描述
src/runtime/mfinal.go

看代码,看不出什么。其端倪在注释中。注意如下注释:

// Finalizers are run in dependency order: if A points at B, both have

// finalizers, and they are otherwise unreachable, only the finalizer

// for A runs; once A is freed, the finalizer for B can run.

// If a cyclic structure includes a block with a finalizer, that

// cycle is not guaranteed to be garbage collected and the finalizer

// is not guaranteed to run, because there is no ordering that

// respects the dependencies.

这段英文翻译成中文如下:

Finalizers(终结器)按照依赖顺序运行:如果 A 指向 B,两者都有终结器,并且它们除此之外不可达,则仅运行 A 的终结器;一旦 A 被释放,可以运行 B 的终结器。如果一个循环结构包含一个具有终结器的块,则该循环体不能保证被垃圾回收并且终结器不能保证运行,因为没有符合依赖关系的排序方式。

这意思很明显了,析构函数会检查当前对象A是否有外部对象指向当前对象A。如果有外部对象指向当前对象A时,A的析构是无法执行的;如果有外部对象指向当前对象A时,A的析构才能执行。

代码中的a和b是循环依赖,当析构判断a和b时,都会有外部对象指向a和b,析构函数无法执行。析构无法执行,内存也无法回收。因此答案选A。

去掉析构函数后,a和b肯定会被释放的。不用析构函数去证明,那如何证明呢?用以下代码就可以证明,代码如下:

在这里插入图片描述

根据运行结果,内存大小明显变小,说明a和b已经被回收了。

让我们再看看有析构函数的情况,运行结果是咋样的,如下:

在这里插入图片描述

根据运行结果,有析构函数的情况下,a和b确实是无法被回收。

总结

1.不要怀疑八股文的正确性,golang的垃圾回收确实是根可达算法。

2.不要用析构函数去测试无用对象被回收的情况,上面的例子也看到了,两对象的循环引用,析构函数的测试结果就是错误的。只能根据内存变化,看无用对象是否被回收。

3.在写代码的时候,能手动设置引用为nil,最好手动设置,这样能更好的避免内存泄漏。