go内存管理 | 青训营笔记

133 阅读4分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 5 天

一、自动内存管理

动态内存

程序在运行时根据需求动态分配的内存:malloc()

垃圾回收

由程序语言运行时系统管理动态内存,避免手动内存管理,专注于实现业务逻辑,保证内存使用的正确性和安全性

gc算法

垃圾回收(英语:Garbage Collection,缩写为GC)是一种自动内存管理机制. 垃圾回收器(Garbage Collector)尝试回收不再被程序所需要的对象所占用的内存. GC最早起源于LISP语言, 1959年左右由John McCarthy创造用以简化LISP中的内存管理. 所以GC可以说是一项"古老"的技术, 但是直到20世纪90年代Java的出现并流行, 广大的普通程序员们才得以接触GC. 当前许多语言如Go, Java, C#, JS和Python等都支持GC

mutator

mutate的是变化的意思, mutator就是改变者, 在GC里, 指的是改变对象之间引用关系的实体, 可以简单的理解为我们写的的应用程序(运行我们写的代码的线程, 协程).

allocator, collector

自动内存管理机制一般包含allocator(分配器)和collector(回收器). allocator负责为应用代码分配对象, 而collector则负责寻找存活的对象, 并释放不再存活的对象.

引用计数

引用计数的思想非常简单:每个单元维护一个域,保存其它单元指向它的引用数量(类似有向图的入度)。当引用数量为 0 时,将其回收。引用计数是渐进式的,能够将内存管理的开销分布到整个程序之中。C++ 的 share_ptr 使用的就是引用计算方法。

引用计数算法实现一般是把所有的单元放在一个单元池里,比如类似 free list。这样所有的单元就被串起来了,就可以进行引用计数了。新分配的单元计数值被设置为 1(注意不是 0,因为申请一般都说 ptr = new object 这种)。每次有一个指针被设为指向该单元时,该单元的计数值加 1;而每次删除某个指向它的指针时,它的计数值减 1。当其引用计数为 0 的时候,该单元会被进行回收。虽然这里说的比较简单,实现的时候还是有很多细节需要考虑,比如删除某个单元的时候,那么它指向的所有单元都需要对引用计数减 1。那么如果这个时候,发现其中某个指向的单元的引用计数又为 0,那么是递归的进行还是采用其他的策略呢?递归处理的话会导致系统颠簸。关于这些细节这里就不讨论了,可以参考文章后面的给的参考资料。

优点

  1. 渐进式。内存管理与用户程序的执行交织在一起,将 GC 的代价分散到整个程序。不像标记-清扫算法需要 STW (Stop The World,GC 的时候挂起用户程序)。
  2. 算法易于实现。
  3. 内存单元能够很快被回收。相比于其他垃圾回收算法,堆被耗尽或者达到某个阈值才会进行垃圾回收。

缺点

  1. 原始的引用计数不能处理循环引用。大概这是被诟病最多的缺点了。不过针对这个问题,也除了很多解决方案,比如强引用等。
  2. 维护引用计数降低运行效率。内存单元的更新删除等都需要维护相关的内存单元的引用计数,相比于一些追踪式的垃圾回收算法并不需要这些代价。
  3. 单元池 free list 实现的话不是 cache-friendly 的,这样会导致频繁的 cache miss,降低程序运行效率。

二、编译器和静态分析的关系

源码->(Scanner - 词法Lexical分析-Regular Expression)->(Parser- 语法Syntax分析-Context-Free Grammar), 生成AST ->(Type Checker - 语义Semantic分析 - Attribute Grammar),生成 Decorated AST -> Translator,生成IR,进行静态分析 -> Code Generator

1-1-编译器原理

2.AST vs IR

AST :高级,更接近于语法结构,依赖于语言种类,适用于快速类型检查,缺少控制流信息

IR:低级,更接近于机器码,不依赖语言种类,压缩且简洁,包含控制流信息。是静态分析的基础

1-2-AST&IR

3.IR:3-地址代码(3AC)

// 最多1个操作符
a+b+3  ->  t1 = a+b
                 t2 = t1+3
AddressName:ab
    Constant: 3
    编译器的临时变量:t1t2

1-3-常用3地址码