“这是我参与更文挑战的第9天,活动详情查看: 更文挑战”
上篇介绍了Java GC日志详解,接下来介绍OOM常见各种场景及解决方案。
堆溢出
报错信息
java.lang.OutOfMemoryError:Java heap space
JVM参数配置
参数配置:初始-Xms30M -Xmx30M
-XX:+PrintGCDetails -XX:MetaspaceSize=64m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=heap/heapdump.hprof -XX:+PrintGCDateStamps -Xms200M -Xmx200M -Xloggc:log/gc-oomHeap.log
复制代码
原因及解决方案
原因
- 代码中可能存在大对象分配
- 可能存在内存泄漏,导致在多次GC之后,还是无法找到一块足够大的内存容纳当前对象。
解决方法
- 检查是否存在大对象的分配,最有可能的是大数组分配
- 通过jmap命令,把堆内存dump下来,使用MAT等工具分析以下,检查是否存在内存泄漏的问题
- 如果没有找到明显的内存泄漏,使用-Xmx加大堆内存
- 还有一点容易被忽略,检查是否有大量的自定义的Finalizable对象,也有可能是框架内存提供的,考虑其存在的必要性
dump文件分析
- jvisualvm分析
- MAT分析
gc日志文件
元空间溢出
方法区(Method Area)与Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、即时编译器编译后的代码等数据。虽然Java虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫做Non-Heap(非堆),目的应该是与Java堆区分开来。
元空间存储数据类型
报错信息
java.lang.OutOfMemoryError:Metaspace
JVM参数配置
-XX:+PrintGCDetails -XX:MetaspaceSize=60m -XX:MaxMetaspaceSize=60m -Xss512k -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=heap/heapdumpMeta.hprof -XX:SurivorRatio=8 -XX:+TraceClassLoading -XX:+TraceClassUnloading -XX:+PrintGCDateStamps -Xms60M -Xmx60M -Xloggc:log/gc-oomMeta.log
复制代码
原因及解决方案
JDK8后,元空间替换了永久代,元空间使用的是本地内存
原因
- 运行期生成了大量的代理类,导致方法区被撑爆,无法卸载
- 应用长时间运行,没有重启
- 元空间内存设置过小
解决方法
因为该OOM原因比较简单,解决方法有如下几种
- 检查是否永久代空间或者元空间设置的过小
- 检查代码中是否存在大量的反射操作
- dump之后通过mat检查是否存在大量由于反射生成的代理类
GC overhead limit exceeded
案例模拟
示例代码1
JVM配置
-XX:+PrintGCDetails -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=heap/dumpExceeded.hprof -XX:+PrintGCDateStamps -Xms10M -Xmx10M -Xloggc:log/gc-oomExceeded.log
复制代码
报错信息
示例代码2
JVM配置
-XX:+PrintGCDetails -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=heap/dumpExceeded1.hprof -XX:+PrintGCDateStamps -Xms10M -Xmx10M -Xloggc:log/gc-oomExceeded1.log
复制代码
代码解析
第一段代码:运行期间将内容放入常量池的典型案例
intern()方法
- 如果字符串常量池里面已经包含了等于字符串X的字符串,那么就返回常量池中这个字符串的引用
- 如果常量池中不存在,那么就会把当前字符串添加到常量池并返回这个字符串的引用
第二段代码:不停的追加字符串str
- Java heap space的demo每次都能回收大部分的对象(中间产生的UUID),只不过有一个对象是无法回收的,慢慢长大,直到内存溢出
- GC overhead limit exceeded的demo由于每个字符串都在被list引用,所以无法回收,很快就用完内存,触发不断回收的机制。
分析及解决
原因
这个是JDK6新加的错误类型,一般都是堆大小导致的。Sun官方对此的定义:超过98%的时间用来做GC并且回收了不到2%的堆内存时会抛出此异常。本质是一个预判性的异常,抛出该异常时系统没有真正的内存溢出。
解决方法
- 检查项目中是否有大量的死循环或有使用大内存的代码,优化代码。
- 添加参数-XX:-UseGCOverheadLimit禁用这个检查,其实这个参数解决不了内存问题,只是把错误的信息延后,最终出现java.lang.OutOfMemoryError: java heap space。
- dump内存,检查是否存在内存泄漏,如果没有,加大内存。
线程溢出
报错信息
java.lang.OutOfMemoryError: unable to create new native Thread
问题原因
出现这种异常,基本上都是创建了大量的线程导致的
分析及解决
原因
是否必须创建大量的线程,能创建的线程数的具体计算公式如下:
(MaxProcessMemory – JVMMemory -ReserverdOsMemory) / ThreadStackSize = Number of Threads
- MaxProcessMemory 指的是进程可寻址的最大空间
- JVMMemory JVM内存
- ReserverdOsMemory 保留的操作系统内存
- ThreadStackSize 线程栈的大小
解决方向
- 如果程序中有bug,导致创建大量不需要的线程或者线程没有及时回收,那么必须解决这个bug,修改参数是不能解决问题的。
- 如果程序确实需要大量的线程,现有的设置不能达到要求,那么可以通过修改MaxProcessMemory,JVMMemory,ThreadStackSize这三个因素,来增加能创建的线程数。
总结
本节介绍了Java中OOM常见的各种场景及解决方案,具体问题具体分析,不可能覆盖所有业务场景,也没有万能的定律和规律可循。下一篇给大家介绍性能优化方案。
欢迎大家关注公众号(MarkZoe)互相学习、互相交流。