OOM,不管是菜鸟程序猿还是资深砖家,在开发进程中都经常会遇到的问题。
下面围绕几点,来“浅入浅出”了解下OOM。
what OOM (什么是OOM)?
why OOM(为什么会产生OOM)?
where OOM(哪里会产生OOM)?
when OOM(什么时分会产生OOM)?
what OOM ?
OOM,全称“Out Of Memory”,内存溢出,通俗理解便是内存不行啦。
why OOM ?
- 服务的正常运转需要的内存过多,而JVM设置的内存过小,导致服务跑不起来或者运转一段时刻后挂掉。
- GC收回内存的速度赶不上程序运转内存消耗内存的速度。一般是大目标、大数组导致。
- 内存走漏问题,长期内存泄会导致内存溢出。比如翻开文件不开释、创立链接不开释、许多不再运用的目标但未断开引证联系等。
内存走漏:是指程序在请求内存后,无法开释已请求的内存空间,一次内存走漏似乎不会有大的影响,但内存走漏堆积后的后果便是内存溢出。
内存溢出:指程序请求内存时,没有满足的内存供请求者运用。
where OOM ?
JVM运转时数据区五个区域中,除了程序计数器不会产生OOM,其他所有区域都有可能。
除了虚拟机栈、本地办法栈、堆、办法区/元空间以外,还有直接内存也会产生OOM。
直接内存:直接内存虽然不是虚拟机运转时数据区的一部分,但既然是内存,就会遭到物理内存的约束。
OOM一共有9个,加上栈溢出共10个,下面是几个常见的。
① java.lang.OutOfMemoryError:Javaheap space
产生在堆空间,没有满足空间存放新创立的目标。
造成原因
- 可能创立了超大目标,一般是大数组。
- 事务高峰期。
- 内存走漏,许多目标引证没开释,JVM无法对其收回。
处理方案
- 运用 -Xmx 参数调整堆大小。
- 针对事务峰值,能够考虑添加机器,或者限流降级。
- 针对内存走漏,需要找到相关目标,修改代码。
② java.lang.OutOfMemoryError:GC overhead limit exceeded
当 Java 进程花费 98% 以上的时刻履行 GC,但只恢复了不到 2% 的内存,且该动作接连重复了 5 次,会抛出java.lang.OutOfMemoryError:GC overhead limit exceeded 过错。
③ java.lang.OutOfMemoryError:Permgen space 和 Metaspace
Permgen space 产生在永久代,但在JDK1.8已废弃,表明永久代(Permanent Generation)已用满,一般是因为加载的 class 数目太多或体积太大。
Metaspace 是 JDK 1.8 运用 Metaspace 替换了永久代(Permanent Generation),该过错表明 Metaspace 已被用满,一般是因为加载的 class 数目太多或体积太大。
④ java.lang.StackOverflowError
栈内存溢出,一般呈现这个问题是因为程序里有死循环或递归调用所产生的。
when OOM ?
最简而易见的便是有报错,呈现 java.lang.OutOfMemoryError ,这便是产生了OOM。还有便是频频GC事件产生,当发现Full GC,Young GC产生频频时,很有可能便是在OOM的边缘疯狂试探。
一个线程OOM后,其他线程还能运转吗?
答案:能。产生OOM后的线程一般状况下会死亡,也便是会被终结掉,该线程持有的目标占用的heap都会被gc了,开释内存。因为产生OOM之前要进行GC,就算其他线程能够正常作业,也会因为频频gc产生较大的影响,比如像咱们的服务在OOM前频频GC的进程中,会呈现请求超时的现象。
一次线上OutOfMemoryError记录
布景
在经过JDBC读取数据库数据的时分,因为在没有约束条数的状况下,将200w条数据写入List中,引发 java.lang.OutOfMemoryError:Javaheap space 反常。
模拟
List<DataBean> datas = new ArrayList<>();
while (true){
datas.add(new DataBean("123"));
}
- 服务监控报警
首要能够经过服务监控告警,会有服务节点堆内存运用过载反常告诉,然后经过服务监控页面检查堆改变状况。
- OOM主动导出dump文件
其实,翻一翻服务日志,也是能定位到OOM过错日志的。
不过咱们在所有服务发动的时分已装备了OOM主动打印二进制dump文件:-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/app/logs/heapdump.hprof 。
假如没有装备,能够经过指令打印:jmap -dump:format=b,file=/app/logs/heapdump/logs/xxxxx.hprof pid 。(推荐oom主动导出dump文件,手动导出的方法在某些状况下已失去快照的含义了吧)
- 运用jvisualvm分析dump文件
能够经过jdk自带的 jvisualvm.exe 来分析 heapdump.hprof 文件。
到此,现已精确地定位到了相关代码。
OOM触发前,肯定也会频频触发full gc,能够从full gc的频次和时刻来评价服务运转状况。
- JVM发动参数设置
所以平常布置服务的时分,在JVM发动参数里最好加以下指令:
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/home/admin/logs
-Xloggc:/home/admin/logs/gc.log
-XX:+PrintGCDetails
-XX:+PrintGCDateStamps
假如服务器dump在linux服务器上怎样分析呢?
能够用 jhat 指令来分析,办法很简略,jhat -J-Xmx1024m -port <自定义端口> xxxx.hprof 。
一次线上StackOverflowError记录
布景
运用 split(String regex, int limit) 切割超大json字符串时,引发 java.lang.StackOverflowError 反常。
处理方案
在处理堆内存溢出的时分,能够经过GC日志,dump快照文件来分析处理,可是在gc日志并没有看到因为栈溢出引发的gc。并且在栈溢出的时分,也没有生成dump快照文件,所以这些对栈溢出的问题处理没有太大效果。
其实栈溢出处理,非常简略,只需要经过服务日志来排查就行了,直接能够定位到问题的相关代码了。
本次栈溢出的主要问题是,在运用正则表达式的时分,因为我的字符串是个超级大的 json 字符串,pattern底层是经过递归方法调用履行的,每一层的递归都会在栈线程的大小中占必定内存,假如递归的层次许多,就会报出stackOverFlowError反常。