当你的线上应用遇到 java.lang.OutOfMemoryError: 

 

Java heap space 异常时,你的应用到底处于什么状态,到底该不该重启?什么情况下应该重启,什么情况下可以不重启,结论是看情况,今天我通过实践向你证明。

情况一、

        如果是局部对象过大导致的,那么内存溢出,相应的线程栈会被销毁,相应的局部对象也会被GC回收,jvm安然无恙可以继续处理其他请求。这也说明了局部对象过大导致的溢出只会影响当前线程,当线程被销毁后,内存很快被GC,不会影响其他线程。

#

java jvm内存占满 jvm占用内存不释放_java

#

java jvm内存占满 jvm占用内存不释放_多线程_02

如上图可以看出,3次溢出后内存都能及时释放,不影响其他线程,内存状态完全正常,JVM可以流畅运行。

情况二、

        如果是静态对象,或者共享对象,发生内存溢出后,不会得到释放(GC不能回收根可达对象),所有线程基本上处于静止状态,运行非常慢,因为没有内存啊,GC非常频繁,不能处理任何请求(报 out of memory error ),dubbo与注册中心也会失去联系,用arthas大概率是attach失败,用VisualVM能艰难的连上去,用jmap可以很轻松连上去。下面是第二种情况出现后的报错情况,这说明内存不能释放影响了其它线程的正常运行。影响了dubbo与注册中心的联系,注册中心会将其删除。

#

java jvm内存占满 jvm占用内存不释放_jvm_03

下面使用VisualVM艰难的连上去了,可以直观的观察下内存情况

看到dubbo相关的线程也受到影响,不能正常和注册中心联系了

#

java jvm内存占满 jvm占用内存不释放_数据库_04

内存居高不下,无法释放,线程反应迟钝,因为GC在大量频繁执行,这就会导致大量的STW

#

java jvm内存占满 jvm占用内存不释放_jvm_05

看到基本上每时每刻都在GC,这时候你的业务程序基本上处于停滞状态。

这时用arthas根本连不上去,因为监听线程都无法正常工作

#

java jvm内存占满 jvm占用内存不释放_jvm_06

java jvm内存占满 jvm占用内存不释放_java jvm内存占满_07

#

总结

       当遇到情况1时,就不需要进行重启,但是要捕获异常,发送告警信息,开发人员通过观察OOM线程栈日志,基本上能找到问题根源。

       当遇到情况2时,需要进行重启才能恢复正常,在这种情况下,由于频繁的GC导致大量STW,后续指令的执行会非常慢,非常困难,告警信息都不一定能发出去,可能还是需要通过对日志的监控来发送告警信息。比较欣慰的是这种情况下,也会导致dubbo服务会和注册中心失联(任何线程执行都基本处于停滞状态),从而相当于自动隔离了故障容器。开发人员介入后,在重启之前使用jmap命令保留下现场证据,便于排查问题(arthas和VisualVM基本上已经很难连接上去了)。