现象
用C#异步方式实现的网络底层协议,开发的服务器。上线运行一段时间后,发现一开始内存非常稳定,但是过了一定时间后,内存使用量会开始不停的上涨。直到内存耗尽。
排查
遇到这一问题可以明确的是内存发生了泄漏。由于.Net中,托管对象的内存是由垃圾回收机制负责回收的。所以存在内存增长的情况,往往不是因为没有释放。而是有几种原因
- 分配的内存,比垃圾回收的还要快
- 对象存在引用,没有办法被垃圾回收机制回收。
对于第一种情况,我仔细排查了代码中使用new分配内存的位置,往往这种情况,是因为在一个逻辑循环中不断分配内存,并且该循环占用了大量的CPU,导致其他线程(GC)没有空隙执行释放造成的。常见的是在一些线程函数中的,无限循环(业务逻辑的帧循环)中执行了new。
第二种情况则是我们以为我们分配的对象在生命周期结束后,不会再被其他对象引用,而应该被GC回收,但实际情况是因为某些原因(比如,函数返回,输出参数,将对应的引用返回到其生命周期外部),对象实际被其他对象使用了,导致GC不认为该对象是死对象,从而无法回收。这种情况比较多见,往往是编写代码的疏忽。
但针对以上两种情况进行排查后,我发现我们的代码都没有这种问题,因为对这个问题的排查陷入一个无解的情况。
由于在进程中,执行new的地方,除了业务逻辑,只剩下网络底层协议。因为把目光转向了网络底层实现。在我们的网络底层实现中,使用new的只有对网络消息的封包,和网络异步操作时的SocketAsyncEventArgs的使用。根据内存增长的速度,我们得出大概每秒会上涨接近200KB的内存,而我们测试时的峰值人数才100人,也就是平均每人每秒,在基本和网络只保持心跳的情况下,都要发出去2000字节,而我们的心跳包大概也就2字节。所以基本排除了是消息封包的内存出现问题。
那么接下来就是SocketAsyncEventArgs的分配了,仔细检查代码,这个东西我们是作为局部变量使用的,每次都是重新分配,理应不会产生上述两种情况。所以看似又进