go语言动态生成大块内存 go动态编译( 二 )


【golang】内存逃逸常见情况和避免方式因为如果变量的内存发生逃逸,它的生命周期就是不可知的,其会被分配到堆上,而堆上分配内存不能像栈一样会自动释放,为了解放程序员双手,专注于业务的实现,go实现了gc垃圾回收机制,但gc会影响程序运行性能,所以要尽量减少程序的gc操作 。
1、在方法内把局部变量指针返回,被外部引用,其生命周期大于栈,则溢出 。
2、发送指针或带有指针的值到channel , 因为编译时候无法知道那个goroutine会在channel接受数据,编译器无法知道什么时候释放 。
3、在一个切片上存储指针或带指针的值 。比如[]*string,导致切片内容逃逸,其引用值一直在堆上 。
4、因为切片的append导致超出容量,切片重新分配地址 , 切片背后的存储基于运行时的数据进行扩充,就会在堆上分配 。
5、在interface类型上调用方法,在Interface调用方法是动态调度的 , 只有在运行时才知道 。
1、go语言的接口类型方法调用是动态 , 因此不能在编译阶段确定 , 所有类型结构转换成接口的过程会涉及到内存逃逸发生,在频次访问较高的函数尽量调用接口 。
2、不要盲目使用变量指针作为参数,虽然减少了复制,但变量逃逸的开销更大 。
3、预先设定好slice长度,避免频繁超出容量,重新分配 。
(十一)golang 内存分析编写过C语言程序的肯定知道通过malloc()方法动态申请内存,其中内存分配器使用的是glibc提供的ptmalloc2 。除了glibc,业界比较出名的内存分配器有Google的tcmalloc和Facebook的jemalloc 。二者在避免内存碎片和性能上均比glic有比较大的优势,在多线程环境中效果更明显 。
Golang中也实现了内存分配器,原理与tcmalloc类似 , 简单的说就是维护一块大的全局内存 , 每个线程(Golang中为P)维护一块小的私有内存 , 私有内存不足再从全局申请 。另外 , 内存分配与GC(垃圾回收)关系密切,所以了解GC前有必要了解内存分配的原理 。
为了方便自主管理内存,做法便是先向系统申请一块内存 , 然后将内存切割成小块,通过一定的内存分配算法管理内存 。以64位系统为例,Golang程序启动时会向系统申请的内存如下图所示:
预申请的内存划分为spans、bitmap、arena三部分 。其中arena即为所谓的堆区,应用中需要的内存从这里分配 。其中spans和bitmap是为了管理arena区而存在的 。
arena的大小为512G,为了方便管理把arena区域划分成一个个的page,每个page为8KB,一共有512GB/8KB个页;
spans区域存放span的指针,每个指针对应一个page,所以span区域的大小为(512GB/8KB)乘以指针大小8byte = 512M
bitmap区域大小也是通过arena计算出来 , 不过主要用于GC 。
span是用于管理arena页的关键数据结构,每个span中包含1个或多个连续页,为了满足小对象分配,span中的一页会划分更小的粒度,而对于大对象比如超过页大?。蛲ü嘁呈迪?。
根据对象大小 , 划分了一系列class,每个class都代表一个固定大小的对象 , 以及每个span的大小 。如下表所示:
【go语言动态生成大块内存 go动态编译】 上表中每列含义如下:
class: class ID,每个span结构中都有一个class ID, 表示该span可处理的对象类型
bytes/obj:该class代表对象的字节数
bytes/span:每个span占用堆的字节数,也即页数乘以页大小
objects: 每个span可分配的对象个数,也即(bytes/spans)/(bytes/obj)waste
bytes: 每个span产生的内存碎片,也即(bytes/spans)%(bytes/obj)上表可见最大的对象是32K大小,超过32K大小的由特殊的class表示,该class ID为0,每个class只包含一个对象 。

推荐阅读