OOM,不管是菜鸟程序猿还是资深砖家,在开发进程中都经常会遇到的问题。

下面围绕几点,来“浅入浅出”了解下OOM。

what OOM (什么是OOM)?

why OOM(为什么会产生OOM)?

where OOM(哪里会产生OOM)?

when OOM(什么时分会产生OOM)?

what OOM ?

OOM,全称“Out Of Memory”,内存溢出,通俗理解便是内存不行啦。

oom!该懂的都懂

why OOM ?

  • 服务的正常运转需要的内存过多,而JVM设置的内存过小,导致服务跑不起来或者运转一段时刻后挂掉。
  • GC收回内存的速度赶不上程序运转内存消耗内存的速度。一般是大目标、大数组导致。
  • 内存走漏问题,长期内存泄会导致内存溢出。比如翻开文件不开释、创立链接不开释、许多不再运用的目标但未断开引证联系等。

内存走漏:是指程序在请求内存后,无法开释已请求的内存空间,一次内存走漏似乎不会有大的影响,但内存走漏堆积后的后果便是内存溢出。

内存溢出:指程序请求内存时,没有满足的内存供请求者运用。

where OOM ?

JVM运转时数据区五个区域中,除了程序计数器不会产生OOM,其他所有区域都有可能。

除了虚拟机栈、本地办法栈、堆、办法区/元空间以外,还有直接内存也会产生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!该懂的都懂

  • 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!该懂的都懂

oom!该懂的都懂

oom!该懂的都懂

到此,现已精确地定位到了相关代码。

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 。

oom!该懂的都懂

oom!该懂的都懂

oom!该懂的都懂

一次线上StackOverflowError记录

布景

运用 split(String regex, int limit) 切割超大json字符串时,引发 java.lang.StackOverflowError 反常。

处理方案

在处理堆内存溢出的时分,能够经过GC日志,dump快照文件来分析处理,可是在gc日志并没有看到因为栈溢出引发的gc。并且在栈溢出的时分,也没有生成dump快照文件,所以这些对栈溢出的问题处理没有太大效果。

其实栈溢出处理,非常简略,只需要经过服务日志来排查就行了,直接能够定位到问题的相关代码了。

本次栈溢出的主要问题是,在运用正则表达式的时分,因为我的字符串是个超级大的 json 字符串,pattern底层是经过递归方法调用履行的,每一层的递归都会在栈线程的大小中占必定内存,假如递归的层次许多,就会报出stackOverFlowError反常。