GC的认识 Q&A
什么是GC,有什么作用?
- GC全称garbage collection, 即垃圾回收,是一种自动内存管理的机制
- 当程序向操作系统申请的内存不在需要时,垃圾回收主动将其回收并供其它代码申请内存时复用,或者归还给操作系统
这种针对内存级别资源的自动回收过程,称为垃圾回收,而负责垃圾回收的组件,称为垃圾回收器
根对象到底是什么
- 根对象在垃圾回收的术语中又叫根集合,它是垃圾回收器在标记过程时最先检查的对象包括
- 全局变量:程序在编译器就能确定的那些存在于程序整个生命周期的变量
- 执行栈:每个goroutine都包含自己的执行栈,这些执行栈上包含栈上的变量以及指向分配的堆内存块的指针
- 寄存器:寄存器的值可能表示一个指针,参与计算的这些指针可能指向某些赋值器分配的堆内存区块
常见的GC实现方式有哪些,go语言的GC使用的是什么?
- 所有GC算法存在的形式可以归结为tracing追踪,reference counting引用计数,这两种形式的混合使用
- tracing追踪式GC
从根对象出发,根据对象之间的引用信息,一步步推进直到扫描完整个堆并确定并确定要保留的对象,从而回收所有
可回收的对象,Go,java,v8对javascript的实现等均未追踪式GC
- 引用计数式GC
每个对象自身包含一个被引用的计数器,当计数器归零时自动得到回收,因为此方法缺陷较多,在追求高性能时
通常不被使用,python,objective-C是引用式计数GC
- 目前常见的GC实现方式包括
- 追踪式,分为多种不同类型,例如:
- 标记清扫:从根对象出发,将确定存活的对象进行标记,并清扫可以回收的对象
- 标记整理:为了解决内存碎片问题而提出,在标记过程中,将对象尽可能整理到一块连续的内存上
- 增量式:将标记与清扫的过程分配执行,每次执行很小的部分,从而增量的推进垃圾回收,达到近似实时,
几乎无停顿的目的
- 增量整理:在增量式的基础上,增加对对象的整理过程
- 分代式:将对象根据存活时间的长短进行分类,存活时间小于某个值的为年轻代,存活时间大于某个值的为老年代
永远不会参与回收的为永久代,并根据分代假设(如果一个对象存活时间不长则倾向于被回收,如果一个对象已经存活
很长时间了则倾向于存活更长时间)对 对象进行回收
- 引用计数
根据对象自身的引用计数来回收,当引用计数为0时立即回收
- Go 的 GC 目前使用的是无分代(对象没有代际之分)、不整理(回收过程中不对对象进行移动与整理)、
并发(与用户代码并发执行)的三色标记清扫算法。原因[1]在于:
- 对象整理的优势是解决内存碎片问题以及“允许”使用顺序内存分配器。但 Go 运行时的分配算法基于 tcmalloc,基本上没有碎片问题。
并且顺序内存分配器在多线程的场景下并不适用。Go 使用的是基于 tcmalloc 的现代内存分配算法,
对对象进行整理不会带来实质性的性能提升。
- 分代 GC 依赖分代假设,即 GC 将主要的回收目标放在新创建的对象上(存活时间短,更倾向于被回收),而非频繁检查所有对象。
- 但 Go 的编译器会通过逃逸分析将大部分新生对象存储在栈上(栈直接被回收),
- 只有那些需要长期存在的对象才会被分配到需要进行垃圾回收的堆中。也就是说,分代 GC 回收的那些存活时间短的对象在
- Go 中是直接被分配到栈上,当 goroutine 死亡后栈也会被直接回收,不需要 GC 的参与,进而分代假设并没有带来直接优势。
- 并且go的垃圾回收器与用户代码并发执行,使得 STW 的时间与对象的代际、对象的 size 没有关系。
- Go 团队更关注于如何更好地让 GC 与用户代码并发执行(使用适当的 CPU 来执行垃圾回收),而非减少停顿时间这一单一目标上。
三色标记法是什么
- go语言中采用三色标记法进行gc垃圾回收
- 当我们谈到三色标记法时其实就是指标记清扫的垃圾回收,三色标记法的作用就是用逻辑上严密推导标记清理这种垃圾回收法的正确性
STW是什么意思
如何观察GO GC
有了GC 为什么还会发生内存泄漏
- 在一个具有GC的语言中,我们常说的内存泄漏,用严谨的话说应该是,预期能很快被释放的内存附着在了长期存活的内存上,
或生命期以外的被延长,导致预计能回收的内存长时间得不到回收
- 在go中,由于goroutine的存在,所谓的内存泄漏除了附着在长期的对象上以外,还存在多种不同的形式
- 形式1 预期能被迅速释放的内存由于被根对象引用而没有被快速释放
当有一个全局对象,可能不经意间某个变量附着在其上时,且忽略了将其进行释放,则该内存永远不会得到释放,例如
var cache = map[interface{}]interface{}{}
func keepAlloc() {
for i := 0; i < 10000; i++{
m := make([]byte, 2<<10)
cache[i] = m
}
}
func keepalloc2() {
for i := 0; i < 100000; i++ {
go func() {
select {}
}()
}
}
- 形式3: channel导致goroutine泄漏
var ch = make(chan int)
func keepalloc3() {
for i := 0; i < 5; i++ {
// 没有接收方,goroutine 会一直阻塞
// 但是由于没有接收方对ch进行接收,所以会直接panic
go func() { ch <- 11 }()
}
}
func main() {
keepalloc3()
select {
}
}
并发标记清除法的难点是什么
什么是写屏障,混合写屏障,如何实现
参考文章