golang的系统监控
golang Runtime 三⼤组件,内存分配器、垃圾回收器、Goroutine调度,实际上除此之外还有系统监控这样⼀个任务再后台⼀直在跑,它做些什么事呢?
强制垃圾回收
确保垃圾回收肯定被执⾏,因为垃圾回收器可能会因为某种原因没办法触发,最简单的例⼦在⾼峰期的时候分配很多对象,这些对象都是活着,垃圾回收完了以后这些⿊⾊对象是1GB,按照Go语⾔理论,回收完了以后存活的对象是1GB,下次回收这预值设置成2GB,可是低峰期的时候存活的对象可能就是100MB,很难达到 2GB,就⼀直触发不了垃圾回收,这就会造成有⼀⼤堆对象不被回收,因为1GB-100MB之间⼀⼤堆对象不会被回收,就会导致内存开销⼀直居⾼不下,⽽垃圾回收器⼜触发不了。系统监控程序会定期的检查上次垃圾回收器什么时候执⾏的,如果发现已经有很⻓时间没有执⾏垃圾回收操作了,它就强制执⾏⼀次回收。确保当这个预值触发不了的时候总有其他的机制来保障垃圾回收得以正常运⾏。这是⼀种保障,也称之为守护
释放物理内存
内存分配以后, Heap 中存在⼤量的⾃由内存块,这些内存块在⾼峰时候在低峰时候全部不使⽤了,这些不使⽤的⾃由内存块有可能做了物理映射,所以系统监控程序会定期扫描这些内存块,它会去检查如果这些⾃由块有 5 分钟预值都没有使⽤过,闲置时间⽐较⻓,它就会建议操作系统去解除物理映射,这样就有机会把物理内存释放掉。注意内存分配器并不关⼼内存释放操作,它只关⼼内存复⽤,真正的存释放操作实际上是后台监控程序来完成的。它来定期扫描⾃由内存块,如果⻓时间不⽤,它就会建议操作系
统把物理内存收回去,⾄于操作系统收不收跟它⽆关,它只是向操作系统发出建议。
抢占调度
抢占调度是什么意思呢? Goroutine实际上是在⽤户空间实现的,⽤户空间实现肯定没有时间⽚的概念。假设我们执⾏⼀个 G 任务的时候,内部有⼀个很⼤的循环,这个循环可能会执⾏很⻓时间,它内部没有做系统调⽤就是做普通操作,这个P、M⼀直会被G绑死掉,这种状态有可能导致其他的任务饿死掉,因为它⼀直结束不了,假如有 4个P都在干这个事,G任务结束不了⽐如⼀个死循环。所以必须有⼀种抢占机制,就是执⾏⼀段时间以后得让出 P、让出 M,确保其他的任务也要执⾏,这样才能保证公平。
Go 语⾔做了⼀种很简单的调度操作。内部调⽤任何⼀个函数都得去call那个函数,它就在函数头部插⼊⼀段汇编指令,sub栈帧分配之前我们会发现有些指令,这些指令其实是由编译器插⼊的,它做两件事。第⼀件事⽤来判断栈帧空间够不够⽤,不够⽤进⾏扩张,第⼆件事⽤来检查当前的G执⾏多⻓时间了,如果时间很⻓了⽽且系统调度发出了抢占通知,它会⽴即中⽌这个G任务执⾏,把那些状态打包,打包以后把这个G任务放⼊队列,这样这个P、M就有机会去执⾏其他的 G任务。
这样就说⽩了向函数头部插⼊指令只要G执⾏其他函数那么就有机会执⾏我们插⼊的指令,那些指令判断两件事,第⼀件事栈空间够不够⽤,不够⽤就扩张,第⼆件事判断⼀下是不是要做抢占调度,如果发现要做抢占调度的话就把当前这个 G 状态保存下来,然后把这个 G 丢回到任务队列去,然后让当前 P、 M 去执⾏其他 G,这样来确保通过抢占调度实现相对公平
处理系统调⽤
处理系统调⽤就是系统监控会把耗时很⻓时间系统调⽤的 G 抢回来
I/O 事件
Go 的⼀些 I/O 事件严格意义上来说都是属于异步的,I/O事件处理后得返回,返回以后得去检查是不是执⾏结束了,所以这也是系统监控的责任