当你的线上应用遇到 java.lang.OutOfMemoryError:
Java heap space 异常时,你的应用到底处于什么状态,到底该不该重启?什么情况下应该重启,什么情况下可以不重启,结论是看情况,今天我通过实践向你证明。
情况一、
如果是局部对象过大导致的,那么内存溢出,相应的线程栈会被销毁,相应的局部对象也会被GC回收,jvm安然无恙可以继续处理其他请求。这也说明了局部对象过大导致的溢出只会影响当前线程,当线程被销毁后,内存很快被GC,不会影响其他线程。
#
#
如上图可以看出,3次溢出后内存都能及时释放,不影响其他线程,内存状态完全正常,JVM可以流畅运行。
情况二、
如果是静态对象,或者共享对象,发生内存溢出后,不会得到释放(GC不能回收根可达对象),所有线程基本上处于静止状态,运行非常慢,因为没有内存啊,GC非常频繁,不能处理任何请求(报 out of memory error ),dubbo与注册中心也会失去联系,用arthas大概率是attach失败,用VisualVM能艰难的连上去,用jmap可以很轻松连上去。下面是第二种情况出现后的报错情况,这说明内存不能释放影响了其它线程的正常运行。影响了dubbo与注册中心的联系,注册中心会将其删除。
#
下面使用VisualVM艰难的连上去了,可以直观的观察下内存情况
看到dubbo相关的线程也受到影响,不能正常和注册中心联系了
#
内存居高不下,无法释放,线程反应迟钝,因为GC在大量频繁执行,这就会导致大量的STW
#
看到基本上每时每刻都在GC,这时候你的业务程序基本上处于停滞状态。
这时用arthas根本连不上去,因为监听线程都无法正常工作
#
#
总结
当遇到情况1时,就不需要进行重启,但是要捕获异常,发送告警信息,开发人员通过观察OOM线程栈日志,基本上能找到问题根源。
当遇到情况2时,需要进行重启才能恢复正常,在这种情况下,由于频繁的GC导致大量STW,后续指令的执行会非常慢,非常困难,告警信息都不一定能发出去,可能还是需要通过对日志的监控来发送告警信息。比较欣慰的是这种情况下,也会导致dubbo服务会和注册中心失联(任何线程执行都基本处于停滞状态),从而相当于自动隔离了故障容器。开发人员介入后,在重启之前使用jmap命令保留下现场证据,便于排查问题(arthas和VisualVM基本上已经很难连接上去了)。