← 返回首页

go面试题

问题:Go 语言的 GC 使用的是什么?

答案:垃圾回收机制是Go一大特(nan)色(dian)。
Go1.3采用标记清除法, Go1.5采用三色标记法,Go1.8采用三色标记法+混合写屏障。

标记清除法:
分为两个阶段:标记和清除
标记阶段:从根对象出发寻找并标记所有存活的对象。
清除阶段:遍历堆中的对象,回收未标记的对象,并加入空闲链表。
缺点是需要暂停程序STW。

三色标记法:
将对象标记为白色,灰色或黑色。
白色:不确定对象(默认色);黑色:存活对象。灰色:存活对象,子对象待处理。
标记开始时,先将所有对象加入白色集合(需要STW)。首先将根对象标记为灰色,然后将一个对象从灰色集合取出,遍历其子对象,放入灰色集合。同时将取出的对象放入黑色集合,直到灰色集合为空。最后的白色集合对象就是需要清理的对象。

这种方法有一个缺陷,如果对象的引用被用户修改了,那么之前的标记就无效了。
因此Go采用了写屏障技术,当对象新增或者更新会将其着色为灰色。

一次完整的GC分为四个阶段:

  1. 准备标记(需要STW),开启写屏障;
  2. 开始标记;
  3. 标记结束(STW),关闭写屏障;
  4. 清理(并发);

基于插入写屏障和删除写屏障在结束时需要STW来重新扫描栈,带来性能瓶颈。混合写屏障分为以下四步:

  1. GC开始时,将栈上的全部对象标记为黑色(不需要二次扫描,无需STW);
  2. GC期间,任何栈上创建的新对象均为黑色;
  3. 被删除引用的对象标记为灰色;
  4. 被添加引用的对象标记为灰色;
    总而言之就是确保黑色对象不能引用白色对象,这个改进直接使得GC时间从 2s降低到2us。

问题:return和defer的执行先后顺序

答案:在Go 语言中,defer 语句的执行在return 语句之前执行,但是在return 语句将返回值赋值给返回值变量之后、最终将函数返回给调用者之前执行。
return 操作实际上分为两个步骤:先将返回值写入返回值变量,然后执行defer 语句,最后才将函数结果返回出去。

问题:内存逃逸是什么,怎么发生的?内存逃逸有什么影响?

答案:内存逃逸是编译器在程序编译时期根据逃逸分析策略,将原本应该分配到栈上的对象分配到堆上的一个过程

主要逃逸场景:
● 返回局部变量指针:函数返回内部变量的地址,变量必须逃逸到堆上。
● interface{}类型:传递给interface{}参数的具体类型会逃逸,因为需要运行时类型信息。
● 闭包引用外部变量:被闭包捕获的变量会逃逸到堆上。
● 切片/map动态扩容:当容量超出编译期确定范围时会逃逸。
● 大对象:超过栈大小限制的对象直接分配到堆上。

影响:
因为堆对象需要垃圾回收机制来释放内存,栈对象会跟随函数结束被编译器回收,所以大量的内存逃逸会给gc带来压力。

问题: STW 是什么意思?

答案:STW 是 Stop the World 的缩写,通常意义上指的是用户代码被完全停止运行,STW 越长,对用户代码造成的影响(例如延迟)就越大,早期 Go 对垃圾回收器的实现中 STW 长达几百毫秒,对时间敏感的实时通信等应用程序会造成巨大的影响。

问题:Channel是分配在栈上,还是堆上?

答案:Channel分配在堆上,Channel 被设计用来实现协程间通信的组件,其作用域和生命周期不可能仅限于某个函数内部,所以 一般情况下golang 直接将其分配在堆上。

问题:new和make的区别?

简单总结:
new:适用于所有类型。它只负责分配内存并清零,返回指针 (*T)。
make:仅适用于切片 (slice)、映射 (map) 和通道 (channel)。它负责分配内存 + 初始化内部数据结构,返回类型本身 (T),而不是指针。

问题:Go什么时候发生阻塞?阻塞时,调度器会怎么做?

● 用于原子、互斥量或通道操作导致goroutine阻塞,调度器将把当前阻塞的goroutine从本地运行队列LRQ换出,并重新调度其它goroutine;

● 由于网络请求和IO导致的阻塞,Go提供了网络轮询器(Netpoller)来处理,后台用epoll等技术实现IO多路复用。