OOM 意味着程序存在着破绽,可能是代码或者 JVM 参数配置引起的。这篇文章和读者聊聊,Java 过程触发了 OOM 后如何排查
常说对生产环境放弃敬畏之心,疾速解决问题也是一种敬畏的体现
欢送关注我的公众号:
龙台的技术笔记
,继续输入代码设计和性能调优原创文章
为什么会 OOM
OOM 全称“Out Of Memory”,示意内存耗尽。当 JVM 因为没有足够的内存来为对象调配空间,并且垃圾回收器也曾经没有空间可回收时,就会抛出这个谬误
为什么会呈现 OOM,个别由这些问题引起
- 调配过少:JVM 初始化内存小,业务应用了大量内存;或者不同 JVM 区域分配内存不合理
- 代码破绽:某一个对象被频繁申请,不必了之后却没有被开释,导致内存耗尽
内存透露:申请应用完的内存没有开释,导致虚拟机不能再次应用该内存,此时这段内存就泄露了。因为申请者不必了,而又不能被虚拟机调配给他人用
内存溢出:申请的内存超出了 JVM 能提供的内存大小,此时称之为溢出
内存透露继续存在,最初肯定会溢出,两者是因果关系
常见的 OOM
比拟常见的 OOM 类型有以下几种
java.lang.OutOfMemoryError: PermGen space
Java7 永恒代(办法区)溢出,它用于存储已被虚拟机加载的类信息、常量、动态变量、即时编译器编译后的代码等数据。每当一个类首次加载的时候,元数据都会寄存到永恒代
个别呈现于大量 Class 对象或者 JSP 页面,或者采纳 CgLib 动静代理技术导致
咱们能够通过 -XX:PermSize
和 -XX:MaxPermSize
批改办法区大小
Java8 将永恒代变更为元空间,报错:java.lang.OutOfMemoryError: Metadata space,元空间内存不足默认进行动静扩大
java.lang.StackOverflowError
虚拟机栈溢出 ,个别是因为程序中存在 死循环或者深度递归调用 造成的。如果栈大小设置过小也会呈现溢出,能够通过 -Xss
设置栈的大小
虚拟机抛出栈溢出谬误,能够在日志中定位到谬误的类、办法
java.lang.OutOfMemoryError: Java heap space
Java 堆内存溢出,溢出的起因个别因为 JVM 堆内存设置不合理或者内存透露导致
如果是内存透露,能够通过工具查看透露对象到 GC Roots 的援用链。把握了透露对象的类型信息以及 GC Roots 援用链信息,就能够精准地定位出透露代码的地位
如果不存在内存透露,就是内存中的对象的确都还必须存活着,那就应该查看虚拟机的堆参数(-Xmx 与 -Xms),查看是否能够将虚拟机的内存调大些
小结:办法区和虚拟机栈的溢出场景不在本篇过多探讨,上面次要解说常见的 Java 堆空间的 OOM 排查思路
查看 JVM 内存散布
假如咱们 Java 利用 PID 为 15162,输出命令查看 JVM 内存散布 jmap -heap 15162
[xxx@xxx ~]# jmap -heap 15162
Attaching to process ID 15162, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.161-b12
using thread-local object allocation.
Mark Sweep Compact GC
Heap Configuration:
MinHeapFreeRatio = 40 # 最小堆应用比例
MaxHeapFreeRatio = 70 # 最大堆可用比例
MaxHeapSize = 482344960 (460.0MB) # 最大堆空间大小
NewSize = 10485760 (10.0MB) # 新生代调配大小
MaxNewSize = 160759808 (153.3125MB) # 最大新生代可调配大小
OldSize = 20971520 (20.0MB) # 老年代大小
NewRatio = 2 # 新生代比例
SurvivorRatio = 8 # 新生代与 Survivor 比例
MetaspaceSize = 21807104 (20.796875MB) # 元空间大小
CompressedClassSpaceSize = 1073741824 (1024.0MB) # Compressed Class Space 空间大小限度
MaxMetaspaceSize = 17592186044415 MB # 最大元空间大小
G1HeapRegionSize = 0 (0.0MB) # G1 单个 Region 大小
Heap Usage: # 堆应用状况
New Generation (Eden + 1 Survivor Space): # 新生代
capacity = 9502720 (9.0625MB) # 新生代总容量
used = 4995320 (4.763908386230469MB) # 新生代已应用
free = 4507400 (4.298591613769531MB) # 新生代残余容量
52.56726495150862% used # 新生代应用占比
Eden Space:
capacity = 8454144 (8.0625MB) # Eden 区总容量
used = 4029752 (3.8430709838867188MB) # Eden 区已应用
free = 4424392 (4.219429016113281MB) # Eden 区残余容量
47.665996699370154% used # Eden 区应用占比
From Space: # 其中一个 Survivor 区的内存散布
capacity = 1048576 (1.0MB)
used = 965568 (0.92083740234375MB)
free = 83008 (0.07916259765625MB)
92.083740234375% used
To Space: # 另一个 Survivor 区的内存散布
capacity = 1048576 (1.0MB)
used = 0 (0.0MB)
free = 1048576 (1.0MB)
0.0% used
tenured generation: # 老年代
capacity = 20971520 (20.0MB)
used = 10611384 (10.119804382324219MB)
free = 10360136 (9.880195617675781MB)
50.599021911621094% used
10730 interned Strings occupying 906232 bytes.
通过查看 JVM 内存调配以及运行时应用状况,能够判断内存调配是否正当
另外,能够在 JVM 运行时查看最消耗资源的对象,jmap -histo:live 15162 | more
JVM 内存对象列表依照对象所占内存大小排序
- instances:实例数
- bytes:单位 byte
- class name:类名
显著看到 CustomObjTest
对象实例以及占用内存过多
惋惜的是,计划存在局限性,因为它只能排查对象占用内存过高问题
其中 “[” 代表数组,例如 “[C” 代表 Char 数组,”[B” 代表 Byte 数组。如果数组内存占用过多,咱们不晓得哪些对象持有它,所以就须要 Dump 内存进行离线剖析
jmap -histo:live
执行此命令,JVM 会先触发 GC,再统计信息
Dump 文件剖析
Dump 文件是 Java 过程的内存镜像,其中次要包含 零碎信息 、 虚拟机属性 、 残缺的线程 Dump、所有类和对象的状态 等信息
当程序产生内存溢出或 GC 异常情况时,狐疑 JVM 产生了 内存透露,这时咱们就能够导出 Dump 文件剖析
JVM 启动参数配置增加以下参数
- -XX:+HeapDumpOnOutOfMemoryError
- -XX:HeapDumpPath=./(参数为 Dump 文件生成门路)
当 JVM 产生 OOM 异样主动导出 Dump 文件,文件名称默认格局:
java_pid{pid}.hprof
下面配置是在利用抛出 OOM 后主动导出 Dump,或者能够在 JVM 运行时导出 Dump 文件
jmap -dump:file=[文件门路] [pid]
# 示例
jmap -dump:file=./jvmdump.hprof 15162
在本地写一个测试代码,验证下 OOM 以及剖析 Dump 文件
设置 VM 参数:-Xms3m -Xmx3m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=./
public static void main(String[] args) {List<Object> oomList = Lists.newArrayList();
// 有限循环创建对象
while (true) {oomList.add(new Object());
}
}
通过报错信息得悉,java heap space
示意 OOM 产生在堆区,并生成了 hprof 二进制文件在以后文件夹下
JvisualVM 剖析
Dump 剖析工具有很多,相对而言 JvisualVM、JProfiler、Eclipse Mat,应用人群更多一些。上面以 JvisualVM 举例剖析 Dump 文件
列举两个罕用的性能,第一个是能看到触发 OOM 的线程堆栈,清晰得悉程序溢出的起因
第二个就是能够查看 JVM 内存里保留大小最大的对象,能够自由选择排查个数
点击对象还能够跳转具体的对象援用详情页面
文中 Dump 文件较为简单,而正式环境出错的起因形形色色,所以不对该 Dump 文件做深度解析
留神:JvisualVM 如果剖析大 Dump 文件,可能会因为内存不足打不开,须要调整默认的内存
总结回顾
线上如遇到 JVM 内存溢出,能够分以下几步排查
jmap -heap
查看是否内存调配过小jmap -histo
查看是否有显著的对象调配过多且没有开释状况jmap -dump
导出 JVM 以后内存快照,应用 JDK 自带或 MAT 等工具剖析快照
如果下面还不能定位问题,那么须要排查利用是否在一直创立资源,比方网络连接或者线程,都可能会导致系统资源耗尽
👍 轻量级动静线程池才是“王道”?
👍 我的开源之路:耗时 6 个月公布线程池框架,GitHub 1.7k Star!
本文由博客一文多发平台 OpenWrite 公布!