怎么避免服务内存溢出?
在高并发、高吞吐的场景下,很多简单的事情,会变得非常复杂,而很多程序并没有在设计时针对高并发高吞吐量的情况做好内存管理。
自动内存管理机制的实现原理
做内存管理,主要考虑申请内存和内存回收两部分。
申请内存的步骤:
- 计算要创建的对象需要占用多少内存
- 在内存中找一块连续并且是空闲的内存空间,标记为已占用
内存回收需要主要做2件事情。
- 找出所有可以回收的对象,将其标记为空闲
- 整理内存碎片
现代GC算法大部分采用“标记-清除”算法或者他的变种算法,这种算法分为标记和清除两个阶段:
- 标记阶段:从GC Root开始,可以简单把GC Root理解为程序入口的那个对象,标记所有可达的对象,因为程序中所有在用的对象一定会被这个GC Root直接引用或者间接引用。
- 清除阶段:遍历所有对象,找出所有没有标记的对象,这些没有标记的对象都是可以被回收的,清除这些对象,释放相应的内存。
“标记-清除”算法的一个最大问题,是在标记和清除过程中,必须先把进程暂停,否则计算的结果就是不准确的。这也是为什么发生垃圾回收的时候,我们的程序会卡死的原因。
需要注意,垃圾回收完成后,我们还需要进行内存碎片整理,将不连续的空闲内存移到到一起,以便空出足够的连续内存空间供后需用。
虽然自动内存管理机制有效地解决了内存泄露问题,带来的代价是执行垃圾回收时会暂停进程,如果暂停的时间过长,程序看起来就像“卡死了”一样。
服务为什么会在高并发时忽然“卡死”?
在高并发、高吞吐量场景下,我们的程序会非常忙,短时间内会创建大量的对象,这些对象会迅速占满内存,由于没有内存可用,垃圾回收被迫开始启动,并且,这次被迫执行的垃圾回收面临的是占满整个内存的海量对象,它执行的时间也会增加,相应的,这个回收过程会导致进程长时间暂停。
进程长时间暂停,又会导致大量的请求积压等待结果,垃圾回收刚刚结束,更多的请求立刻涌来,迅速占满内存,再次被迫执行垃圾回收,进入一个恶性循环,如果垃圾回收的速度跟不上创建对象的速度,还可能会产生内存溢出的现象。
高并发下的内存管理技巧
对于开发者来说,垃圾回收是不可控的,而且是无法避免的,但是,我们可以通过一些方法来降低垃圾回收的频率,减少进程暂停的时长。
我们需要考虑在处理大量请求的同时,尽量少的产生一次性对象,特别是占用内存比较大的对象。我们可以按照这个思路来优化对象的业务代码。
对于需要频繁使用、占用内存较大的一次性对象,我们可以考虑自行回收并重用这些对象,例如我们可以为这些对象创建一个对象池,收到请求后,在对象池里面申请一个对象,使用完后再放回到对象池中,这样就可以反复地重复使用这些对象,非常有效地避免频繁触发垃圾回收。