OOM简记
notice: 下面说的比如10M老年代空间,在10M分配完毕的时候进行FullGC都是简化的说法,其实应该是有个空间分配担保机制的存在,不会出现在10M全部使用的情况下才进行FullGC的情况。
1. OOME出现的区域
heap java堆
vm stack 虚拟机栈
native method stack 本地方法栈
method area 方法区
direct memory 直接内存
ps. 除了program counter register 程序计数器外的其他内存区域都有可能发生oome
2. 怎么判断OOME出现的区域
查看异常堆栈信息,一般在OOME后会进一步跟着提示具体的区域,比如
Exception in thread "Thread-0" java.lang.OutOfMemoryError: Java heap space
at com.cqx.oom.OOMDemo.lambda$main$0(OOMDemo.java:23)
at com.cqx.oom.OOMDemo$Lambda$1/668386784.run(Unknown Source)
at java.lang.Thread.run(Thread.java:748)
3. 什么时候会抛出OOME
当创建对象时jvm检测到内存不够本次内存分配,于是会进行一次FullGC,如果本次GC后还是内存不够分配,那就抛出OOME,当前的线程就会死亡。
OOME Thrown when the Java Virtual Machine cannot allocate an object because it is out of memory, and no more memory could be made available by the garbage collector. ----API文档
The JVM will run the GC when it's on edge of the OutOfMemoryError. If the GC didn't help at all, then the JVM will throw OOME.
具体情况可能发生OOME的情况如下
假设jvm的参数设置现在是 -Xmx20M -Xmx20M -Xmn10M -XX:MaxDirectMemorySize=1M, 即分配堆大小为20M,其中老年代和新生代各10M
public static final int _1MB = 1024 * 1024;
1. 持续不断创建对象,并不释放他的引用
for(i = 0; i < 100; i++) {
byte[] M1 = new byte[_1MB];
}
随着对象不断创建,tenured generation到达了10MB,即已经装满了,然后继续创建对象,随着新生代中Eden区再次被填满,触发一次Minor GC,之前Survivor0中部分对象由于各种原因,比如年龄大了,或者本次MinorGC中发现S0不够存放本次存活的对象,所以会有对象晋升到Old Generation。此时JVM发现老年代的内存也不够进行此次分配,于是就进行一次FullGC(Major GC),根据可达性分析,从GC ROOTS开始分析对象的引用关系,发现任然存活,就不进行回收,所有本次FullGC并没有任何卵用,所有GC后还是分配不了,那么此时当前线程就会抛出一个OOME,然后死亡。
2. byte[] _12MB = new byte[12 * _1MB];
大对象的创建会根据JVM参数的设置直接分配到老年代中,跟1类似,FULLGC后发现内存不够,抛出OOME
3. ByteBuffer.allocateDirect(2 * _1MB);
由于之前指定了最大直接内存为1MB 这边分配了2MB就抛出了OOME
java.lang.OutOfMemoryError: Direct buffer memory
4. 方法区OOME
之前在第一次接 Jenkins发布平台时就出现了这个问题。当时发布www?指定的jdk版本是7,可能是因为www大量的jsp文件(感觉也不多),在编译生成class文件时导致方法区不够用发生了OOME。看当时打印的异常日志确认是方法区的OOME。
原因: jdk8之前版本不通过-XX:PermSize和-XX:MaxPermSize显示指定方法区大小,应该就是64MB。当时的jenkins中配置只指定了堆大小,没有指定方法区的大小
解决: 1. -XX:PermSize和-XX:MaxPermSize 2.改用jdk8(移除了使用永久代来当做方法区的策略,使用了metaspace元数据区,不暂用jvm指定的堆内存,而是使用机器的native memory,不受jvm堆的限制)
5. 调用外部服务,外部服务故障或者处理缓慢导致OOME
比如调用中交兴路查询位置接口,网络或者ZJXL服务原因请求一直得不到反馈,两边处理速度不对等,导致越来越多的请求积压,OOME。
思路:
a. 使用hystrix等熔断功能的工具来管理外部服务调用
b. 复用tcp连接。可以考虑加上keepalive:true的请求头,避免每次调用都重新发起tcp连接,
c. httpclient这种应该是要单例的,不需要每次请求都单独创建
d. 生产者消费者模式来处理
6. 内存泄漏
a. 监听器和一些回调注册上来但是没有显示的取消,服务端一直持有这个监听器的引用。
b. 缓存泄漏,比如用HashMap实现的缓存,吧引用扔到进去后忘了,一直扔一直扔就炸了,GC的时候发现对象引用仍然在缓存里,就不回收就炸了。可以用WeakHashMap来实现,key是WeakReference,GC可以回收掉。
</pre>
4. OOME会导致JVM shutdown吗
不会。OOME只会把抛出这个异常的线程给杀掉。 之所以发生OOME的时候应用程序经常出现假死,是因为OOM了,其他正常运行的线程也无法分配到资源,各种请求都无法得到处理,给人一种应用挂掉的感觉。
如果你想在OOME的时候主动kill掉当前的应用可以
-XX:OnOutOfMemoryError="kill -9 %p" 这里还可以执行你的脚本,这样就可以自动重启。。。
%p is the current Java process PID placeholder.
5. 上面说的OOME会杀死当前的线程,那么GC可以回收到这部分资源,释放空间,那么为什么应用程序还是无法响应请求呢。
因为往往抛出OOME的那个线程,不是大头,他只是压垮骆驼的最后一根稻草。
比如一个应用,堆最大设为10M,一个定时任务的线程读取数据,在内存里生成了上万个对象,进行处理,此时用了9.9MB内存空间。然后一个普通的查询请求进来,OOME了,这个请求线程死亡了,由于定时任务线程还在处理,GC只把请求线程的资源回收。那么在定时任务线程处理期间,还是会一直出现OOME。
6. JVM可以自动从OOME恢复吗
可以的,但是不建议。
例如5中的情况,定时任务线程处理完毕了,那么GC会吧这部分内存释放掉,程序又正常了。 但是发生OOME的时候不建议让JVM自动恢复。因为出现了这个异常一定是你程序上有漏洞,有问题,就算本次恢复了,接下去很可能还是会出现这个问题。而且OOM恢复期间很难熬,不管是对于JVM还是用户来说。具体其他的可以看下面的链接
比如之前遇到的,之前做的司机证件导入功能,就发生过一次OOME。这个功能是处理用户导入的照片文件,服务器接收这些文件,处理并存放到MongoDB。当时用户间隔很短时间导入两次大约1G的照片文件,第一次导入的请求还在执行中,因为照片需要读取到内存,照片文件序列化生成的大对象,直接进入老年代,MinorGC没办法清理,占用了大量的内存。紧接着第二次导入请求进来了,在处理过程中由于内存不够OOME了。但是这并不影响第一次的那个请求线程。当时jmap -dump:live,format=b,file=path pid
dump了堆栈信息,发现内存够用啊怎么会发生OOME呢,觉得很奇怪。然后我让用户再次导入,发现还是OOME。后面就突然发现-dump:live
或者-histo:live
会先触发一次FullGC再进行内存信息收集,所以出现异常的那个线程的内存都被回收掉了,所以dump文件上看来是正常的。 所以我就让用户先别导,过个半个小时在来操作,这样就好了。
live指令的解释
dump only live objects; if not specified, all objects in the heap are dumped.</pre>
7. 怎么应对OOME
考虑在JVM启动参数就设置上
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath
,但是有风险,比如5中例子,一直会触发OOME,一直会生成dump文件,会更加麻烦,所以可以考虑加上-XX:OnOutOfMemoryError="kill -9 %p"
如果1没做,那么可以考虑用
jmap
来生成dump堆栈快照迅速保留现场信息,并重启应用(1 2G可能几分钟就好了????没找到数据,懒得试)。在此之前可以用jstats
查看GC的情况如果JVM的分配内存很大,好几G,jmap可能需要执行很久才生成dump文件,可以考虑用gdb来处理,好像会快很多。不管他不是jdk自带的工具,要自己下载。参考链接
考虑使用softReference、weakReference、weakHashMap等等。
不用jmap,使用编程式方式触发生成当前的堆转储快照,参考链接
8. 堆转储快照分析
工具: jhat/visualVM/JPofiler/MAT 语句: OQL(对象查询语句)
9. spring actuator/JMX Java Management Extensions
actuator 应用监控的东西,暴露了一些http接口,有个事heapdump还有个stackdump,可以通过定时任务之类去访问然后分析,如果有问题就通知之类的。
jmx 公司有平台radar。可以多学习下这方面的东西。
10. 死锁与死循环,CPU飙升相关排查
jstack 看线程堆栈
top查找cpu占用率高的pid top -p pid -H 查看进程内线程的pid
pid转16进制,去jstack中查日志
https://blog.51cto.com/13732225/2347907
. . . . . .
参考的文档链接
https://stackoverflow.com/questions/12096403/java-shutting-down-on-out-of-memory-error
https://www.oracle.com/webfolder/technetwork/tutorials/obe/java/gc01/index.html