《it运维面试八股文.docx》由会员分享,可在线阅读,更多相关《it运维面试八股文.docx(13页珍藏版)》请在课桌文档上搜索。
1、面试八股文深入剖析1. 垃圾回收垃圾回收、三色标记原理垃圾回收就是对程序中不再使用的内存资源进行自动回收的操作。1.1 常见的垃圾回收算法: 引用计数:每个对象维护一个引用计数,当被引用对象被创建或被赋值给其他对象时引用计数自动加+1:如果这个对象被销毁,则计数-1,当计数为O时,回收该对象。o优点:对象可以很快被回收,不会出现内存耗尽或到达阀值才回收。o缺点:不能很好的处理循环引用 标记清除:从根变量开始遍历所有引用的对象,引用的对象标记被引用,没有被标记的则进行回收。o优点:解决了引用计数的缺点。o缺点:需要STW(StoptheworId),暂时停止程序运行。 分代收集:按照对象生命周期
2、长短划分不同的代空间,生命周期长的放入老年代,短的放入新生代,不同代有不同的回收算法和回收频率。o优点:回收性能好o缺点:算法复杂1.2 三色标记法 初始状态下所有对象都是白色的。 从根节点开始遍历所有对象,把遍历到的对象变成灰色对象 遍历灰色对象,将灰色对象引用的对象也变成灰色对象,然后将遍历过的灰色对象变成黑色对象。 循环步骤3,直到灰色对象全部变黑色。 通过写屏障(Write-barrier)检测对象有变化,重复以上操作 收集所有白色对象(垃圾)。1.3 STW(StopTheWorld)为了避免在GC的过程中,对象之间的引用关系发生新的变更,使得GC的结果发生错误(如GC过程中新增了一
3、个引用,但是由于未扫描到该引用导致将被引用的对象清除了),停止所有正在运行的协程。STW对性能有一些影响,Golang目前已经可以做到ImS以下的STWo1.4 写屏障(WriteBarrier) 为了避免GC的过程中新修改的引用关系到GC的结果发生错误,我们需要进行STWo但是STW会影响程序的性能,所以我们要通过写屏障技术尽可能地缩短STW的时间。造成引用对象丢失的条件:一个黑色的节点A新增了指向白色节点C的引用,并且白色节点C没有除了A之外的其他灰色节点的引用,或者存在但是在Gc过程中被删除了。以上两个条件需要同时满足:满足条件1时说明节点A已扫描完毕,A指向C的引用无法再被扫描到;满足
4、条件2时说明白色节点C无其他灰色节点的引用了,即扫描结束后会被忽略。写屏障破坏两个条件其一即可 破坏条件LDijiStra写屏障满足强三色不变性:黑色节点不允许引用白色节点当黑色节点新增了白色节点的引用时,将对应的白色节点改为灰色 破坏条件2:YUaSa写屏障满足弱三色不变性:黑色节点允许引用白色节点,但是该白色节点有其他灰色节点间接的引用(确保不会被遗漏)当白色节点被删除了一个引用时,悲观地认为它一定会被一个黑色节点新增引用,所以将它置为灰色2. GPM调度和CSP模型协程的深入剖析2.1 CSP模型?CSP模型是以通信的方式来共享内存,不同于传统的多线程通过共享内存来通信。用于描述两个独立
5、的并发实体通过共享的通讯ChanneI(管道)进行通信的并发模型。2.2 GPM分别是什么、分别有多少数量? G(Coroutine):即Go协程,每个go关键字都会创建一个协程。 M(Machine):工作线程,在GO中称为MaChine,数量对应真实的CPU数(真正干活的对象)。 P(Processor):处理器(GO中定义的一个撕念,非CPU),包含运行Go代码的必要资源,用来调度G和M之间的关联关系,其数量可通过Gomaxprocso来设置,默认为核心数。M必须拥有P才可以执行G中的代码,P含有一个包含多个G的队列,P可以调度G交由M执行。2.3 Coroutine调度策略 队列轮转:
6、P会周期性的将G调度到M中执行,执行一段时间后,保存上下文,将G放到队列尾部,然后从队列中再取出一个G进行调度。除此之外,P还会周期性的查看全局队列是否有G等待调度到M中执行。 系统调用:当Go即将进入系统调用时,MO将释放P,进而某个空闲的Ml获取P,继续执行P队列中剩下的G。Ml的来源有可能是M的缓存池,也可能是新建的。 当GO系统调用结束后,如果有空闲的P,则获取一个P,继续执行GO。如果没有,则将GO放入全局队列,等待被其他的P调度。然后MO将进入缓存池睡眠。GOroUline调度器基本单元Coroutine逻辑Processor物理PrOCeSSorbP的LoCaI队列I LJLlL
7、1 - I I . r )IIIIIIIII P I I P I I P I I P I , GOMAXPi1R0CS 个,瓜 z Az A AGIObal队列(User-levelThread)操作系统调度器KernelThread物理多核CPUCPl-2CPU 3物理机器3. CHAN原理chan实现原理3.1 结构体typehchanstructqcountuint/队列中的总元素个数dataqsizuint/环形队列大小,即可存放兀素的个数bufunsafe.Pointer/环形队列指针elemsizeuintl6/每个元素的大小closeduint32/标识关闭状态elemtype*
8、_type/元素类型sendxuint/送索5,兀素写入时存放到队列中的位置recvxuint/接收索引,元素从队列的该位置读出recvqwaitq/等待读消息的gor。Utine队列sendqwaitq/等待写消息的goroutine队列lockmutex/互斥锁,chan不允许并发读写)3.2 读写流程向channel写数据:若等待接收队列recvq不为空,则缓冲区中无数据或无缓冲区,将直接从recvq取出G,并把数据写入,最后把该G唤醒,结束发送过程。若缓冲区中有空余位置,则将数据写入缓冲区,结束发送过程。若缓冲区中没有空余位置,则将发送数据写入G,将当前G加入sendq,进入睡眠,等待
9、被读goroutine唤醒。从channel读数据若等待发送队列sendq不为空,且没有缓冲区,直接从sendq中取出G,把G中数据读出,最后把G唤醒,结束读取过程。如果等待发送队列Sendq不为空,说明缓冲区已满,从缓冲区中首部读出数据,把G中数据写入缓冲区尾部,把G唤醒,结束读取过程。如果缓冲区中有数据,则从缓冲区取出数据,结束读取过程。将当前goroutine加入recvq,进入睡眠,等待被写goroutine唤醒。关闭channel1.关闭channel时会将recvq中的G全部唤醒,本该写入G的数据位置为nil。将sendq中的G全部唤醒,但是这些G会panic。panic出现的场景
10、还有: 关闭值为nil的channel 关闭己经关闭的channel 向已经关闭的channel中写数据3.2无缓冲Chan的发送和接收是否同步?/无缓冲的ChanneI由于没有缓冲发送和接收需要同步Ch:=make(chanint)/WChannel不要求发送和接收操作同步ch:=make(chanint,2)channel无缓冲时,发送阻塞直到数据被接收,接收阻塞直到读到数据;Channel有缓冲时,当缓冲满时发送阻塞,当缓冲空时接收阻塞。4.context结构原理4.1 用途构的goroutie,每个goroutine拥有相同的上下文。 控制多个协程之间的协作、取消操作。CZ)- *(Z
11、).Context是并发安全的,主要是用于VL 5ComeXt (上下文)是G。Iang应用开发常用的并发控制技术,它可以控制一组呈树状结4.2 数据结构Context只定义了接口,凡是实现该接口的类都可称为是一种Contexto并发控制神器之ContexttypeContextinterfaceDeadline()(deadlinetime.Time,okbool)Done()gorungotest命令时,添加-race标识可以检查代码中是否存在资源竞争。解决这个问题,我们可以给资源进行加锁,让其在同一时刻只能被一个协程来操作。 sync.Mutex sync.RWMutex5.2 逃逸分析
12、面试官问我go逃逸场景有哪些,我?逃逸分析就是程序运行时内存的分配位置(栈或堆),是由编译器来确定的。堆适合不可预知大小的内存分配。但是为此付出的代价是分配速度较慢,而且会形成内存碎片。逃逸场景:指针逃逸栈空间不足逃逸动态类型逃逸闭包引用对象逃逸快问快答6. go中除了加Mutex锁以外还有哪些方式安全读写共享变量?Go中Coroutine可以通过Channel进行安全读写共享变量。7. golang中new和make的区别?用new还是make?到底该如何选择? make仅用来分配及初始化类型为slice、m叩、chan的数据。 new可分配任意类型的数据,根据传入的类型申请一块内存,返回指
13、向这块内存的指针,即类型*Type0 make返回引用,即Type,new分配的空间被清零,make分配空间后,会进行初始。8. Go中对nil的Slice和空Slice的处理是一致的吗?首先Go的JSON标准库对nilslice和空slice的处理是不一致。 slice:=make(int,O):SliCe不为nil,但是SIiCe没有值,SliCe的底层的空间是空的。 slice:=int:SIiCe的值是nil,可用于需要返回SliCe的函数,当函数出现异常的时候,保证函数依然会有nil的返回值。9. 协程和线程和进程的区别?并发掌握,goroutine和Channel声明与使用!进程:
14、进程是具有一定独立功能的程序,进程是系统资源分配和调度的最小单位。每个进程都有自己的独立内存空间,不同进程通过进程间通信来通信。由于进程比较重量,占据独立的内存,所以上下文进程间的切换开销(栈、寄存器、虚拟内存、文件句柄等)比较大,但相对比较稳定安全。线程:线程是进程的一个实体,线程是内核态,而且是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。线程间通信主要通过共享内存,上下文切换很快,资源开销较少,但相比进程不够稳定容易丢失数据。*协程:协程是一种用户态的轻量级线程,协程的调度完全是由用户来控制的。协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其
15、他地方,在切回来的时候,恢匏先前保存的寄存器上下文和栈,直接操作栈则基本没有内核切换的开销,可以不加锁的访问全局变量,所以上下文的切换非常快。10. Golang的内存模型中为什么小对象多了会造成GC压力?通常小对象过多会导致GC三色法消耗过多的GPU。优化思路是,减少对象分配。11. channel为什么它可以做到线程安全?Channel可以理解是一个先进先出的队列,通过管道进行通信,发送一个数据到ChanneI和从Channel接收一个数据都是原子性的。不要通过共享内存来通信,而是通过通信来共享内存,前者就是传统的加锁,后者就是ChanneI。设计Channel的主要目的就是在多任务间传递
16、数据的,本身就是安全的。12. GC的触发条件?主动触发(手动触发),通过调用runtime.GC来触发GC,此调用阻塞式地等待当前GC运行完毕。被动触发,分为两种方式:使用步调(Pacing)算法,其核心思想是控制内存增长的比例,每次内存分配时检查当前内存分配量是否己达到阈值(环境变量Gc)GC):默认100%,即当内存扩大一倍时启用GCo使用系统监控,当超过两分钟没有产生任何GC时,强制触发GC。13. 怎么查看Goroutine的数量?怎么限制Goroutine的数量?在Golang中,GOMAXPROCS中控制的是未被阻塞的所有GOroUtine,可以被Multiplex到多少个线程上
17、运行,通过GOMAXPROCS可以查看Coroutine的数量。使用通道。每次执行的g。之前向通道写入值,直到通道满的时候就阻塞了。14. Channel是同步的还是异步的?Channel是异步进行的,channel存在3种状态: nil,未初始化的状态,只进行了声明,或者手动赋值为nil active,正常的ChanneI,可读或者可写 closed,己关闭,千万不要误认为关闭Channel后,Channel的值是nil操作一个零值nil通道一个非零值但已关闭的通道关闭产生恐慌产生恐慌发送数据永久阻塞产生恐慌接收数据永久阻塞永不阻塞15. Coroutine和线程的区别? 一个线程可以有多个
18、协程 线程、进程都是同步机制,而协程是异步 协程可以保留上一次调用时的状态,当过程重入时,相当于进入了上一次的调用状态 协程是需要线程来承载运行的,所以协程并不能取代线程,一线程是被分割的CPU资源,协程是组织好的代码流程16. Go的Struct能不能比较?相同StrUCt类型的可以比较不同struct类型的不可以比较,编译都不过,类型不匹配17. Go主协程如何等其余协程完再操作?使用SyncWaitGroupoWaitGroup,就是用来等待一“组操作完成的。WaitGroup内部实现了一个计数器,用来记录未完成的操作个数。Add()用来添加计数;DoneO用来在操作结束时调用,使计数减
19、一;Wait()用来等待所有的操作结束,即计数变为0,该函数会在计数不为0时等待,在计数为0时立即返回。18. Go的Slice如何扩容?slice实现原理在使用append向slice追加元素时,若slice空间不足则会发生扩容,扩容会重新分配一块更大的内存,将原slice拷贝到新slice,然后返回新sliceo扩容后再将数据追加进去。扩容操作只对容量,扩容后的slice长度不变,容量变化规则如下: 若slice容量小于1024个元素,那么扩容的时候slice的CaP就翻番,乘以2;一旦元素个数超过1024个元素,增长因子就变成1.25,即每次增加原来容量的四分之一。 若slice容量够用
20、,则将新元素追加进去,slice.len+,返回原slice 若slice容量不够用,将slice先扩容,扩容得到新slice,将新元素追加进新slice,slice.len+,返回新slice.19. G。中的map如何实现顺序读取?Go中m叩如果要实现顺序读取的话,可以先把m叩中的key,通过sort包排序。20. Go值接收者和指针接收者的区别?究竟在什么情况下才使用指针?参数传递中,值、引用及指针之间的区别!方法的接收者: 值类型,既可以调用值接收者的方法,也可以调用指针接收者的方法; 指针类型,既可以调用指针接收者的方法,也可以调用值接收者的方法。但是接口的实现,值类型接收者和指针类
21、型接收者不一样: 以值类型接收者实现接口,类型本身和该类型的指针类型,都实现了该接口; 以指针类型接收者实现接口,只有对应的指针类型才被认为实现了接口。通常我们使用指针作为方法的接收者的理由: 使用指针方法能够修改接收者指向的值。 可以避免在每次调用方法时复制该值,在值的类型为大型结构体时,这样做会更加高效。21. 在G。函数中为什么会发生内存泄露?Goroutine需要维护执行用户代码的上下文信息,在运行过程中需要消耗一定的内存来保存这类信息,如果一个程序持续不断地产生新的goroutine,且不结束已经创建的goroutine并复用这部分内存,就会造成内存泄漏的现象。22. Gorouti
22、ne发生了泄漏如何检测?可以通过Go自带的工具pprof或者使用Gops去检测诊断当前在系统上运行的Go进程的占用的资源。23. Go中两个Nil可能不相等吗?G。中两个Nil可能不相等。接口(interface)是对非接口值(例如指针,StrUCt等)的封装,内部实现包含2个字段,类型T和值V。一个接口等于nil,当且仅当T和V处于unset状态(T=nil,Visunset)o两个接口值比较时,会先比较T,再比较Vo接口值与非接口值比较时,会先将非接口值尝试转换为接口值,再比较。例子中,将一个nil非接口值P赋值给接口i,此时,i的内部字段为(T=*int,V=nil),i与P作比较时,将
23、p转换为接口后再比较,因此i=p,P与nil比较,直接比较值,所以p=nib但是当i与nil比较时,会将nil转换为接口(T=nil,V=nil),i(T=*int,V=nil)不相等,因此i!=nilo因此V为nil,但T不为nil的接口不等于nil,24. Go语言函数传参是值类型还是引用类型?在G。语言中只存在值传递,要么是值的副本,要么是指针的副本。无论是值类型的变量还是引用类型的变量亦或是指针类型的变量作为参数传递都会发生值拷贝,开辟新的内存空间。另外值传递、引用传递和值类型、引用类型是两个不同的概念,不要混淆了。引用类型作为变量传递可以影响到函数外部是因为发生值拷贝后新旧变量指向了
24、相同的内存地址。25. Go语言中的内存对齐了解吗?CPU访问内存时,并不是逐个字节访问,而是以字长(WOrdSiZe)为单位访问。比如32位的CPU,字长为4字节,那么CPU访问内存的单位也是4字节。CPU始终以字长访问内存,如果不进行内存对齐,很可能增加CPU访问内存的次数,例如:O1234567非内存对齐;I/tJab内存对齐,r,_、ab内存访II)变量a、b各占据3字节的空间,内存对齐后,a、b占据4字节空间,CPU读取b变量的值只需要进行一次内存访问。如果不进行内存对齐,CPU读取b变量的值需要进行2次内存访问。第一次访问得到b变量的第1个字节,第二次访问得到b变量的后两个字节。也
25、可以看到,内存对齐对实现变量的原子性操作也是有好处的,每次内存访问是原子的,如果变量的大小不超过字长,那么内存对齐后,对该变量的访问就是原子的,这个特性在并发场景下至关重要。简言之:合理的内存对齐可以提高内存读写的性能,并且便于实现变量操作的原子性。26. 两个interface可以比较吗? 判断类型是否一样reflect.TypeOf(a).Kind()=reflect.TypeOf(b).Kind() 判断两个interface。是否相等reflect.DeepEqual(a,binterfaced) 将一个interface。赋值给另一个interface。reflect.ValueOf
26、(a).EIem().Set(reflect.ValueOf(b)27. go打印时%v%+v%#v的区别?%v只输出所有的值;%+v先输出字段名字,再输出该字段的值;%#v先输出结构体名字值,再输出结构体(字段名字十字段的值);28. 什么是rune类型?G。语言的字符有以下两种: uint8类型,或者叫byte型,代表了ASC11码的一个字符。 rune类型,代表一个UTF-8字符,当需要处理中文、日文或者其他复合字符时,则需要用到rune类型。rune类型等价于int32类型。29. 空struct占用空间么?可以使用UnsafeSizeof计算出一个数据类型实例需要占用的字节数:空结构体struct实例不占据任何的内存空间。30. 空struct的用途?因为空结构体不占据内存空间,因此被广泛作为各种场景下的占位符使用。1 .将m叩作为集合(Set)使用时,可以将值类型定义为空结构体,仅作为占位符使用即可。2 .不发送数据的信道(ChanneI)使用channel不需要发送任何的数据,只用来通知子协程(goroutine)执行任务,或只用来控制协程并发度。结构体只包含方法,不包含任何的字段。