什么是内存逃逸? 内存逃逸是一种程序错误,它指程序在运行过程中分配了内存,但是之后没有正确的释放这些被分配的内存,从而导致这些内存一直被占用,这就有可能会引起系统内存耗尽,从而导致程序崩溃。
我们都知道程序运行的时候需要将其拷贝到内存中才能执行,而内存空间划分成了不同的区域,其中最重要的两个区域是:堆区 - Heap 和 栈区 - Stack
为什么要分成堆区和栈区呢? 主要是为了满足不同的编程需求:
1、效率:栈区自动管理,适合快速的内存分配和释放,而堆区则适合存储需要长时间存在的大量数据。
2、灵活性:堆区基本可以随心所欲动态的分配内存,适合需要灵活使用大块内存的情况,而栈区刚好相反适合局部短时间使用的情况。
3、内存管理:有助于更好的管理和优化内存使用,提高程序的性能和稳定性。
栈区:用来存放函数调用时的局部变量和函数调用信息的;一般是由系统自动管理的,当函数调用开始时分配内存,函数调用结束时自动释放内存;栈区的分配和释放速度都非常快;栈区一般是有大小限制的,所以如果函数嵌套调用过多,或者局部变量太多也可能会导致栈溢出。
堆区:用于存储动态分配的内存(比如使用new或者malloc等函数分配的内存);特别是C语言中是需要一由程序员手动管理分配和释放的;因为是手动管理分配和释放,所以速度一般比较慢;堆区的大小一般来说比栈区大,可以存储更多的数据。
分配快慢的问题:
栈分配内存只需要用到两个CPU指令,即PUSH分配和RELEASE释放;
堆分配内存首先需要找到一块大小合适的内存块,之后还要通过垃圾回收才能释放。
Go语言为了解决内存逃逸实现了一套内存逃逸分析,所谓逃逸分析就是指程序在编译阶段对代码中哪些变量需要在栈中分配,哪些变量需要在堆上分配进行静态分析的方法。逃逸分析的目的是做到更好内存释放分配,提高程序的运行效率。
Go逃逸分析的源码位于src/cmd/compile/internal/gc/escape.go,感兴趣的话可自行阅读研究,其逃逸分析原理在源码的注释中也已有说明:
pointers to stack objects cannot be stored in the heap - 指向栈对象的指针不能存储在堆中
pointers to a stack object cannot outlive that object - 指向栈对象的指针不能超过该对象的生命周期
知道了以上知识点,我们再来看几个实际的例子,通过实例来说明如何避免Go内存逃逸?
1、避免返回函数内部的局部指针变量,比如下面这段代码就会产生内存逃逸:
func Sum(a int, b int) *int { total := a + b return &total // 返回了指向局部变量的指针 } func main() { Sum(200, 50) } 这段 Go 代码会产生内存逃逸的原因是由于函数 Sum 返回的是指向局部变量 total 的指针,而这个局部变量会在函数退出时被销毁,因此 Go 会将 total 的内存分配(“迁移”)到堆上,从而避免访问已销毁的内存,这种现象就是内存逃逸(如果返回的指针指向了已经销毁的内存,这将导致访问非法内存,为了避免这种问题,Go 会将这个局部变量 total 从栈上“逃逸”到堆上,确保它在 main 函数结束前仍然有效。)。
1、Go语言中的Map是有序的还是无序的,为什么? 在Go语言中,字典Map是无序的。
因为 Go 中的 map 是基于 哈希表(hash table)实现的。哈希表的核心思想是通过哈希函数将键映射到特定的位置,然后存储值。由于哈希表是通过散列来决定每个元素的存储位置,元素的存储顺序是 由哈希值和碰撞解决策略 决定的,而不是按插入的顺序排列的。
Go 语言设计者故意没有为 map 提供顺序保证。Go 认为在多数应用场景中,顺序并不重要,尤其是在查找、插入和删除操作的效率比顺序更重要。因此,Go 选择使用无序的哈希表来实现 map,这使得 map 的查找和操作在大多数情况下是 常数时间复杂度 O(1)。
因此,如果使用 for … range 循环遍历的话每次迭代获得的 键 - 元素 对的顺序都可能是不同的。
另外,在Go的内部实现中是先通过一个叫做fastrand()的函数生成一个伪随机数,然后使用这个随机数作为每次遍历的起始桶位置,从而导致每次遍历的起始位置不是固定的。
2、Map字典的键类型不能是哪些类型,应该优先考虑哪些类型作为字典的键类型呢? 这在官方文档中有详细说明:
The comparison operators == and != must be fully defined for operands of the key type; thus the key type must not be a function, map, or slice. If the key type is an interface type, these comparison operators must be defined for the dynamic key values; failure will cause a run-time panic. 因此,Go 语言Map字典的键类型不可以是函数、字典和切片。因为 Go 语言的字典类型其实是一个哈希表(hash table)的特定实现,因此其中重要的一个步骤就是需要把键值转换为哈希值,所以,从性能的角度来看的话,“把键值转换为哈希值”以及“把要查找的键值与哈希桶中的键值做对比”, 都是比较耗时的操作。
因此,可以说,求哈希和判等操作的速度越快,对应的类型就越适合作为键类型,应该优先考虑。
3、在值为nil的字典上执行读或写操作会成功吗? 当我们仅声明而不初始化这个字典类型的变量时,这个变量的值就会是nil,比如像下面这样声明的变量mp就是nil。
func main() { var mp map[string]int fmt.Println(mp["key"]) // 这行会输出 0,不会 panic } 在这样的nil字典上执行读操作是没有问题的。但是,如果是写操作(插入)的话,Go语言的运行时系统就会立即抛出一个 panic 致命性错误。
这需要从Map的底层结构来看了,在Go中,Map是引用类型,其底层是由一个 hmap 结构体 表示的,其的定义为:
// https://github.com/golang/go/blob/master/src/runtime/map_noswiss.go#L114 // A header for a Go map. type hmap struct { // Note: the format of the hmap is also encoded in cmd/compile/internal/reflectdata/reflect.go. // Make sure this stays in sync with the compiler's definition. count int // # live cells == size of map. Must be first (used by len() builtin) flags uint8 B uint8 // log_2 of # of buckets (can hold up to loadFactor * 2^B items) noverflow uint16 // approximate number of overflow buckets; see incrnoverflow for details hash0 uint32 // hash seed buckets unsafe.Pointer // array of 2^B Buckets. may be nil if count==0. oldbuckets unsafe.Pointer // previous bucket array of half the size, non-nil only when growing nevacuate uintptr // progress counter for evacuation (buckets less than this have been evacuated) clearSeq uint64 extra *mapextra // optional fields } 其中,buckets 指向存储键值对的 “桶数组”,所以,当map声明但未初始化时(即 var mp map[string]int),其值是nil,它的内部结构核心字段值如下:
1、MySQL索引的最左前缀原则是什么意思?如果有查询条件为 a > 1 and b = 1 and c = 1,请问这个查询能命中MySQL索引吗? 索引的最左前缀原则是指查询语句要使用索引的条件需要满足从关联索引的最左侧开始且需要连续匹配。
比如,如果创建联合索引的顺序是(a, b, c)则上面的查询条件就可以命中索引,但如果创建y索引的顺序是(b,a, c)或者其他顺序,则上面的查询语句则无法使用索引。
2、使用explain工具分析慢查询的时候需要关注哪些信息? 首先关注“type”项,如改项显示为“ALL”则意味着查询使用了全标扫描,这是最差的情况,必须进行优化。如果改项的值为“index”或“range”等则相对较好。
还要注意“row”这项,它代表预计扫描的行数,如果改值较大,即使查询使用到了索引也需要进行优化。
此外,“possible_keys”和“key”这两项也非常重要,前者表示可能使用到的索引,后者表示查询实际使用的索引。如果改项值为空则意味着未使用索引,需要立即优化。