程序oom,常用的一些检查命令

514 阅读5分钟

当Java程序发生OOM时,可以通过以下命令检查程序运行情况:

常用命令

  1. jstack:用于打印出Java进程中各个线程当前的调用栈信息,以及各个线程阻塞的原因等信息。可以通过jstack命令查看是否有线程阻塞、死循环等情况。
  2. jmap:用于打印Java进程中各个对象的内存使用情况,可以查看对象的数量、类型、占用空间等信息。可以通过jmap命令查看是否有内存泄漏的情况。
  3. jstat:用于查看Java进程中各个内存区域的使用情况,例如堆、非堆等。可以通过jstat命令查看内存使用是否正常、GC情况等信息。
  4. top:用于查看系统整体的CPU、内存等使用情况,可以查看Java进程所占用的资源情况。可以通过top命令查看系统负载情况是否正常。
  5. ps:用于查看Java进程的基本信息,例如进程号、进程占用的CPU、内存等。可以通过ps命令查看Java进程是否存在异常情况。
  6. lsof:用于查看Java进程占用的文件句柄、网络端口等信息。可以通过lsof命令查看是否有文件句柄泄漏、网络端口占用等情况。

以上是常用的一些命令,可以帮助开发者进行程序调试和故障排查。

案例

以下是一个查询用户接口,导致服务OOM的案例。

首先,假设我们有一个接口是查询所有用户的信息,用户信息保存在MySQL数据库中。我们使用Spring Boot框架开发,使用MyBatis框架进行数据库操作。接口代码如下:

javaCopy code@RestController
public class UserController {
    @Autowired
    private UserMapper userMapper;
    
    @GetMapping("/users")
    public List<User> getUsers() {
        return userMapper.selectAll();
    }
}

其中,UserMapper是MyBatis生成的Mapper接口,selectAll方法是查询所有用户信息的方法。

由于用户数据较多,每个用户的信息比较复杂,所以在查询所有用户信息的接口中,我们使用了较大的Java内存对象来存储用户信息:

javaCopy code
public class User {
    private Long id;
    private String name;
    private Integer age;
    private String email;
    // 省略其他属性
    
    // getter/setter方法
}

随着用户数据的增长,当并发查询量较大时,内存中存储用户信息的对象数量将大量增加,最终导致内存溢出。

d5990ae6f85f4f13952cf4c3f80e95a8_noop.jpg

如何排查

假设我们的服务在使用中出现了oom的问题,我们可以通过以下命令来查看该服务的进程状态和内存占用情况:

  1. top -p 命令可以查看该进程的占用资源情况,包括 CPU 占用率、内存占用情况等。
  2. jstat -gcutil 命令可以查看该进程的 JVM 堆内存使用情况,包括 Eden 区、Survivor 区、老年代、永久代等。
  3. jmap -dump:format=b,file= 命令可以生成当前进程的 heap dump 文件,这个文件可以使用一些工具进行分析,比如 Eclipse Memory Analyzer(MAT)。

在这个例子中,假设我们通过上述命令发现了我们的服务进程的内存占用率很高,并且 heap dump 文件中发现了大量的内存对象被占用。通过对 heap dump 文件的分析,我们发现了以下信息:

  1. 内存对象中有一个大的 ArrayList,里面存储了大量的查询数据。
  2. 该 ArrayList 是在某个接口方法中创建的,而且在每次请求该接口时都会重新创建一个新的 ArrayList,导致大量的内存占用。

    定位到问题后,优化方案

有了这些信息后,我们就可以对这个接口进行优化了。具体的优化方案可能包括:

  1. 使用缓存来存储查询数据,减少每次请求时的查询次数。
  2. 使用分页查询来控制查询数据的数量。
  3. 对于无法避免的大数据量查询,可以考虑使用流式查询,避免将所有数据都加载到内存中。
  4. 对于无法避免的大数据量查询,可以考虑使用分布式计算框架,将查询分散到多个节点上进行,减少单个节点的内存占用。

为了解决这个问题,我们可以考虑优化如下:

  1. 优化查询语句:使用分页查询的方式,每次查询指定数量的用户信息。这样可以避免一次性查询全部用户信息导致的内存占用过高问题。
javaCopy code
public interface UserMapper {
    List<User> selectAll(@Param("offset") int offset, @Param("limit") int limit);
}
  1. 优化内存对象:将存储用户信息的Java对象中,只保留必要的属性,减少对象大小。
javaCopy codepublic class User {
    private Long id;
    private String name;
    
    // getter/setter方法
}
  1. 优化应用服务器内存:调整应用服务器的JVM内存参数,以适应当前的应用负载。

优化完毕后,我们可以使用如下的命令检查应用运行情况:

  1. 查看JVM堆内存使用情况:使用jstat -gc 命令查看JVM堆内存使用情况,根据内存使用情况确定是否需要调整JVM内存参数。
  2. 查看操作系统内存使用情况:使用top或free命令查看操作系统内存使用情况,根据内存使用情况确定是否需要升级服务器或优化应用程序。
  3. 查看Java进程的GC日志:使用-XX:+PrintGCDetails参数打印GC日志,通过GC日志可以了解内存分配情况和垃圾回收情况,进而确定是否需要优化应用程序或调整JVM内存参数。

最后,我们需要重新部署这个服务,并验证我们的优化方案是否生效。如果生效了,我们就可以解决这个服务的 oom 问题了。

扫码_搜索联合传播样式-标准色版.png