关于变量到底分配到堆上还是栈上?
变量的具体分配位置是由编译器决定的,当编译器无法判断变量作用域(比如返回变量地址)和变量大小(make([]int, 0, n))的时候,通常会把变量分配到堆上。理论上,编译器会优先考虑将变量分配到堆上,因为堆上的变量,会在函数出栈后自行释放,而不需要gc的处理,缓解gc的压力。
关于垃圾回收?
常见的垃圾回收的实现方式
追踪式GC
从跟对象触发,根据对象之间的引用信息,一步步推进直到扫描完毕整个堆并确定需要保留的对象,从而回收所有可回收的对象。Go、Java、V8等实现均为追送式GC
引用计数式
每个对象自身包含一个被引用的计数器,当计数器归零时自动得到回收。因此此方法缺陷较多,在追求高性能时通常不被应用
三色标记法
理解三色标记法的关键是理解对象的三色抽象以及波面推进这两个概念。 三色抽象是一种描述追踪式回收器的方法,在实践中并没有实际含义,它的重要作用在于从逻辑上严密推导标记请了这种垃圾回收方法的正确性。也就是说,当谈及三色标记法时,通常指标记清扫的垃圾回收。 从垃圾回收器的视角来看,三色抽象规定了三种不同类型的对象,并用不同的颜色相称:
- 白色对象(可能死亡):未被回收器访问到的对象。在回收开始阶段,所有对象均为白色,当回收结束后,白色对象均不可达。
- 灰色对象(波面):已被回收器访问到的对象,但回收器需要对其中的一个或多个指针进行扫描,因为它们可能还指向白色对象;
- 黑色对象(确定存活):已被回收器访问到的对象,其中所有字段都已被扫描,黑色对象中任何一个指针都不可能直接指向白色对象; 这样三种不变性所定义的回收过程其实是一个波面不断前进的过程,这个波面同时也是黑色对象和白色对象的边界,灰色对象就是这个波面。
关于内存优化?
在使用map,slice的时候,由于他们会进行动态扩容,所以在使用的时候,如果能预知分配大小,尽量初始化大小,可以避免扩容。
当我们频繁的使用make([]int,0,n)的方式来分配内存的时候,内存分配会在堆上进行分配,虽然mCansh已经根据size class对内部的内存进行了分类管理,但是频繁和大小不定的内存申请,必然造成内存碎片,同时,当我们再次申请时,运行时会不断的遍历内存管理链表,以求获得相应的内存块,如果内存不够,还要向上级申请内存。如此,一个简单的内存分配动作,却会消耗相当长的世界,拖慢运行时间。
new 和 make 的区别?
- golang中new和make是两个内置函数,主要用来创建并分配类型的内存。
- make只能用来分配及初始化类型为slice、make、channel的数据,并且不返回指针。
- new分配返回的是指针,即类型*Type。make 返回引用。
- new分配的空间被清零。make分配空间后,会进行初始化。
context概述?
Context通常被译作上下文,它是一个比较抽象的概念。一般理解为程序单元的一个运行状态、现场,上下上下则是存在上下层的传递,上会把内容传递给下。在go语言中,程序单元也就指的是Goroutine。
每个Goroutine在执行之前,都要先知道程序的当前的执行状态,通常将这些执行状态封装在一个Context的变量中,传递给要执行的Goroutine中。上下文则几乎已经成为传递与请求同生存周期变量的标准方法。在网络编程下,当接收到一个网络请求的Reuest,在处理request时候,我们可能需要开启不同的Goroutine来获取数据与逻辑处理。即一个请求Request,会在多个Goroutine中处理。而这些Goroutine可能需要共享Request的一些信息;同时当Request被取消或者超时的时候,所有从这个Request创建的所有Goroutine也应该被结束。
Golang值传递和引用传递
go中5个引用类型变量:slice、map、channel、interface、func()
引用类型作为参数时,称为浅拷贝,形参改变,实参跟随变化。因为传递的是地址,形参和实参都指向同一块地址。 值类型作为参数时,称为深拷贝,形参改变,实参不变,因为传递的是值的副本,形参会新开辟一块空间,与实参指向不同。 如果希望值类型数据在修改形参时实参跟随变化,可以把参数设置为指针类型。
Golang中array和slice的区别
数组
固定长度是数组类型的一部分,所以[3]int和[4]是两种不同的数组类型,数组需要指定大小,不指定也会根据初始化对的自动推算出大小。
切片
切片是轻量级的数据结构,可以改变长度,里面具有三个属性:指针、长度、容量。可以通过内置函数make()来初始化,初始化的时候len=cap,然后进行扩容。
golang的defer的作用
只需要在调用普通函数或方法前加上关键字defer,就完成了defer所需要的语法。当defer语句被执行时,跟在defer后面的函数会被延迟执行。直到包含该defer语句的函数执行完毕时,defer后的函数才会被执行,不论包含defer语句的函数是通过return正常结束,还是由于panic导致的异常结束。可以在一个函数中执行多条defer语句,它们的执行顺序与声明顺序相反。
defer的应用场景: defer语句经常被用于处理成对的操作,如:打开、关闭、连接、断开连接、加锁、释放锁等。 通过defer机制,不论函数逻辑多复杂,都能保证在任何执行路径下,资源被释放 释放资源的defer应该直接跟在请求资源的语句后
简单聊聊channel
go语言中,不要通过共享内存来通信,而是通过通信来实现内存共享。Go中的CSP并发模型,中午叫做通信顺序进程,是通过goroutine和channel来实现的。
channel的收发遵循先进先出FIFO,分为有缓存和无缓存。
go语言的channel特性
- 给一个nil channel发送数据,造成永远堵塞
- 给一个nil channel接收数据,造成永远堵塞
- 给一个已经关闭的channel发送数据,造成panic
- 从一个已经关闭的channel接收数据,如果缓存区中为空,则返回一个零值
- 无缓冲的channel是同步的,而有缓冲的channel是非同步的
- 关闭一个nil channel将会发生panic
什么是协程泄漏?
顾名思义就是出现了应该释放而没有被释放的协程,导致系统协程数量一直上升。不像对象回收需要引用计数、三色标记等手段,协程的回收是相对简单的,等待协程将代码从头到尾执行完毕之后,这一块而空间就会自动回收,通常协程泄漏问题都是因为某段代码卡住了,陷入了死循环或者在等待一个不可能的条件等原因。
如何排查协程泄漏问题?
pprof具体在Go语言中的实现是在包:runtime/pprof中,提供了诸如内存分析、CPU分析、锁分析等函数供调用,调用这个库之后会将性能数据以protobuffer这种二进制序列化格式导出。同时考虑到这一个库较为底层,Go在官方runtime/pprof上包装提供了一个更加易用的库:net/http/pprof,提供了一种通过http协议和性能数据交互的能力。除了性能数据的抓取工具,golang也提供了官方的性能数据分析工具:go tool pprof。
map的底层实现原理
Go语言采用的是哈希查找表,并且使用链表法解决哈希冲突