前言:
本节课主要介绍了内存管理知识与自动内存管理机制,并对目前 Go 内存管理过程中存在的问题提出了解决方案,同时结合了上次课程学习的《Go 语言性能优化》相关知识,提供可行性的优化建议 …
Go 语言作为相对新型的语言,自动内存管理肯定是少不了的了,和 Java 一样,Go 语言也有垃圾回收机制,可以实现自动内存管理,下面让我们来了解一些自动内存管理的相关知识。
自动内存管理的概念:
程序运行时的内存分配策略有很多种:单一连续分配、固定分区分配、动态分区分配,现代的程序一般采用动态分区分配,会根据程序在运行时的需求动态地分配内存(malloc()
),将动态分配地内存称作动态内存。
自动内存管理是指由程序语言地运行时系统管理动态内存。
自动内存管理也可以叫垃圾回收(GC),主要负责:
评价 GC 算法的几大指标:
1 - GC 时间 / 程序执行总时
;在学习 Go 内存管理之前,我们先来了解一下垃圾回收的几种实现方案~
追踪垃圾回收是 GC 策略的其中一种,属于间接式的回收策略,这种策略不会直接寻找垃圾本身,而是先去找到存活的对象,然后反过来判断其余的对象应该被回收掉。
✔追踪垃圾回收的步骤:
(根据对象的生命周期,会使用不同的标记和清理策略)
分代 GC 是基于分代假说的内存管理策略,是一种比较常见的策略。
弱分代假说认为绝大多数对象都是朝生夕灭的,生命很短;强分代假说认为活得越久也就是熬过越多次垃圾收集过程的对象就越难以消亡,这两个分代假说共同奠定了垃圾回收的设计原则。
分代 GC 策略会给每个对象一个年龄(经历过 GC 的次数),将对象分成老年代和年轻代,把不同年龄的对象放在堆内存(Heap)的不同区域,为其制定不同的 GC 策略,从而降低整体内存管理的开销。
年轻代(Young generation)
老年代(Old generation)
引用计数是另一种 GC 策略,在这种策略下,每个对象都有一个与之关联的引用数目,只有引用数大于 0 的对象才存活。
⭐引用计数的优点:
引用计数的缺点:
✨引用计数 vs. 其他策略:
时机 | 性能 | 延迟 | |
---|---|---|---|
ARC | 引用计数为 0 马上回收 | 快 | 小 |
other GC | 定时扫描清理 | 慢 | 大 |
Go 的内存分配基于两个思想:分块 & 缓存。
Go 会提前将内存分块,给对象分配内存时会找一个尺寸最接近的块分配。
目标: 为对象在堆(heap)上分配内存。
✔内存分块:
mmap()
向 OS 申请一大块内存,例如 4MB;对象分配: 根据对象的大小,选择最合适的块返回。
Go 的内存分配器为内存做了多级不同的缓存,从而加快整体的内存分配速度。
线程缓存分配(Thread-Caching Malloc,TCMalloc)是用于分配内存的机制,Go 语言的内存分配器就借鉴了 TCMalloc 的设计实现高速的内存分配,它的核心理念是使用多级缓存将对象根据大小分类,并按照类别实施不同的分配策略。
☕分配过程:
了解了这么多内存管理的相关知识和垃圾回收策略,下面来一起看看 Golang 中采用的垃圾回收策略。
Go 语言的垃圾回收总体采用的是经典的 mark-sweep 算法,Go 语言的版本不同,采用的 GC 策略可能是不一样的。Go 1.5 正在实现的垃圾回收器是 “非分代的、非移动的、并发的、三色的标记清除垃圾收集器”。
Go 1.5 使用的三色标记法可以看成 mark-sweep 的增强版,这种方法的 mark 操作是可以渐进执行的而不需每次都扫描整个内存空间,,可以减少 stop the world(STW)的时间;分代 GC 策略在上文已经提及,但是 Go 1.5 还暂时没有采用,Go 官方表示会在 1.6 版本的 GC 优化中考虑~
随着 Golang 的推陈出新,Go 的垃圾回收性能一直在提升,但目前距离 Java 语言的 JVM 使用的成熟的垃圾回收系统还有一定差距。
对象分配是非常高频的操作,每秒分配 GB 级别的内存,小对象占比又比较高,加上分配路径长,这就导致 Go 内存分配比较耗时。
g->m->p->mcache->mspan->memory block->return pointer
pprof()
:对象分配的函数是最频繁调用的函数之一Balanced GC 是字节跳动官方推出的针对 Go SDK 里面对象分配做的优化方案。
< 128 B
;// 一次对象分配:
if top + size <= end {
addr := top
top += size
return addr
}
技术细节:
GAB 对于 Go 内存管理来说是一个大对象。
Balanced GC 作为语言层面的性能优化,开启以后可以减少 CPU 负荷,降低核心接口时延,获得不错的性能收益。