如何快速定位和处理线上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不一定会导致进程挂掉,但是会导致其线程挂掉。

0