如何快速定位和处理线上OOM
在日常的Java开发中,有效的内存管理是保证应用稳定性和可用性的关键指标之一,如果内存使用不当很容易导致内存的泄露甚至出现OOM。常见的导致OOM的原因有如下:
(1)系统的本身的内存资源不够
(2)内存泄漏(如不使用的资源未及时释放且垃圾收集器也无法回收)
(3)大数据量处理(如做导出需要的时候,一次加载过多的数据到JVM中,导致内存不足)
下面针对系统运行中还未崩溃时的OOM、系统出现OOM且已崩溃的场景做定位和分析,下面写一段会发生OOM的代码:
public class JVMController {
@GetMapping("/add")
public String jvmOutOfMemory() {
List<Byte[]> byteList = new ArrayList<>();
for (;;){
byteList.add(new Byte[1024 * 1024 * 1024]);
}
}
}
1、系统运行中还未崩溃时的 OOM
(1)使用jmap在线分析
# 使用jmap在线分析-----> jmap -histo:live <pid>
jmap -histo:live 3512
此时命令会将占用内存较大的对象打印出来:
通过在线分析我们可以定位到哪些对象占用了很大的内存空间(图中的bytes),我们针对这些对象做重点的分析。
(2)导出运行的dump 文件
#导出文件
jmap -dump:format=b,file=longxia.hprof 3512
这样我们可以导出一份线上运行的日志
此时可以将这份日志数据下载到本地,然后通过分析工具(如VisualVM)做分析。
2、系统发生OOM且已崩溃
如果系统发生了OOM并且已经崩溃的情况下,此时就不能使用上面的方法定位了,需要使用dump文件来定位原因。
(1)系统在启动的时候添加参数
#启动时添加运行参数 -XX:+HeapDumpOnOutOfMemoryError:记录OOM日志 -XX:HeapDumpPath:文件存放的位置
java -jar -Xms512m -Xmx512m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/usr/local test-1.0-SNAPSHOT.jar
如果启动的时候添加HeapDumpOnOutOfMemoryError,那么dump文件会很大,因为它记录了系统运行时的所有日志。
dump文件下载到本地(此处需要运维人员辅助下载这个文件)。
文件下载本地之后使用java自带的工具——VisualVM来分析日志。
在本地启动VisualVM:
# 输入启动VisualVM的命令
jvisualvm
启动的页面:
在VisualVM中打开下载的OOM运行的异常日志文件:
继续分析问题OOM出现的原因:
至此,OOM导致系统崩溃的后原因分析就可以找到了。
4、日常开发中如何避免OOM
(1)合理的使用软引用和弱引用(内存不足的情况会被回收)
(2)及时释放资源(如IO流)
(3)避免创建大对象
(4)优化数据结构
(5)谨慎使用全局变量和静态变量
5、OOM会导致一定会导致进程崩溃吗?
其实OOM不一定会导致进程崩溃,但是会导致线程挂掉。下面来验证一下这个结论。
代码:
@RestController
@RequestMapping("/jvm")
public class JVMController {
@GetMapping("/add")
public String jvmOutOfMemory() {
List<Byte[]> byteList = new ArrayList<>();
for (;;){
byteList.add(new Byte[1024 * 1024 * 1024]);
}
}
@GetMapping("/get")
public String get() {
return "get data from java";
}
}
(1)先执行请求http://ip:8080/jvm/add
通过控制台可以发现最终是出现了OOM:
(2)访问请求http://ip: 8080 /jvm/get
可以发现请求是正常被处理的,原因是请求/jvm/get时候,不需要再去堆上申请新的内存,所以是可以运行的。这里也证明JVM进程没有退出。所以OOM不一定会导致进程挂掉,但是会导致其线程挂掉。