JVM简单的问题排查-内存占用高

7,447 阅读3分钟

文章介绍了关于发现JVM内存过大问题时,的一些简单的排查方法,主要分为3个小方法:

  • 使用jmap -histo查询当前占用内存较大的类
  • dump出堆内存,文件过大时在服务器使用jhat 查看dump文件堆内存状态
  • dump出堆内存,下载堆文件到本地,通过VisualVM查看dump文件堆内存状态

一、模拟JVM占用高内存

模拟项目使用使用jdk8

这里使用了springBoot启动项目是为了更贴近实际项目,如果自己尝试可以直接用@Test启动,启动后线程内使用类会更少,排查堆内存会更清晰

package com.cflong.JVM问题排查;

import lombok.AllArgsConstructor;
import lombok.Data;

/**
 * MemoryBase模拟对象
 *
 * @author cflong
 * @date 2021/05/13 21:24:27
 */
@Data
@AllArgsConstructor
public class MemoryBean {
    
    private String name ;

    private int age;

    private String remark;
}
package com.cflong.JVM问题排查;

import java.util.ArrayList;
import java.util.List;

/**
 * 模拟内存占用高的情况
 *
 * @author cflong
 * @date 2021/05/08 18:06:44
 */
public class MemoryTakeHighTest {

    public static void test() throws InterruptedException {
        List<MemoryBean> list = new ArrayList<>(1000);
        int i = 0;
        while (i < 10000) {
            list.add(new MemoryBean("name",12,new String("测试大内存备注,测试大内存备注," +
                    "测试大内存备注,测试大内存备注,测试大内存备注," +
                    "测试大内存备注,测试大内存备注,测试大内存备注," +
                    "测试大内存备注,测试大内存备注,测试大内存备注,")));
            i++;
        }
        Thread.sleep(1000000000);
    }
}
package com.cflong;

import com.cflong.JVM问题排查.MemoryTakeHighTest;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class TestJvmOomApplication {

    public static void main(String[] args) throws Throwable {
        SpringApplication.run(TestJvmOomApplication.class, args);
        //模拟内存占用过高
        MemoryTakeHighTest.test();
    }
}

二、如何发现内存问题

可以通过使用linux中的top命令查询服务器中占用内存的状态: WX20210515-165535@2x.png 使用top命了后会出现下图内容,默认是CPU排序的服务器情况。我们可以调整为内存排序,整理不同的服务按键不一样。WX20210515-165630@2x.png

  • Linux:直接在窗口点击“m”键,就使用内存排序,如下图:

WX20210515-170000@2x.png

  • macOS:直接在窗口点击o键,会出现输入框,在输入框中输入“mem”回车,就会使用内存排序,如下图:

WX20210515-165746@2x.png 通过上面的命了,我们可以看到类似下图的,发现java程序占用大内存的情况: WX20210515-170110@2x.png 我们只知道这个是java程序,但是不一定是我们自己的程序,可能不是我们目标的java程序,这时候可以通过命了ps -ef|grep pid(进程号)来查询进程是那个程序的,如下图:

ps -ef|grep <pid>

WX20210515-192204.png 在这里可以发现是我们自己开发的程序,这个时候真正进入JVM的内存占用的排查。

如果大家练习可以使用jps命令来快速查到所有java的进程号,如下图

image.png

三、找到高内存的类

jmap -histo

使用jmap -histo查询当前占用内存较大的类 这里使用下面命令查看占用内存高的前20个类,如下图排查了一些基本常用的类,可以看出里面包含了一个10000对象的我们自建的MemoryBean类,这里就定位到问题类了。

当然这里问题也可能出现在类如String、HashMap这些类里面,这个需要更复杂的排查方法,不在这次的研究范围之内,后续我们可以在进行讨论。

jamp -histo <pid> | head -20

WX20210515-183635.png

jhat

dump出堆内存,文件过大时在服务器使用jhat 查看dump文件堆内存状态 在服务区中使用jmap命令dump出服务的堆信息。

jamp -dump:format=b,file=文件名字.hporf <pid>

命令中format=b为固定值,file为dump出来的文件名称,后缀使用hporf

WX20210515-171315@2x.png 当dump出来的文件非常大,不方便导出服务区进行分析,我们可以使用jhat 命令启动分析服务进行内存分析。

jhat 文件名.hporf(刚刚dump出来的文件)

image.png 执行晚命令后会出现如图的,显示分析服务已启动,端口为7000,我们使用浏览器访问ip:700(我是本地使用jhat,所以ip为localhost) image.png

我们下拉到最下面,点击Show heap histogram连接 image.png

我们能看到堆占用类的降序排列,可以看出里面包含了一个10000对象的我们自建的MemoryBean类,这里就定位到问题类了。 image.png

VisualVM

dump出堆内存,下载堆文件到本地,通过VisualVM查看dump文件堆内存状态

VisualVM如何安装这里不详细描述,大家可以自己搜索一下

打开VisualVM,点击【文件】【装入】 image.png

下拉选择【堆DUMP(.hprof,.*】,在选择上一个方法中dump出来的文件 image.png

这里后会出现下图画面,选择【类】,我们能看到堆占用类的降序排列,可以看出里面包含了一个10000对象的我们自建的MemoryBean类,这里就定位到问题类了。 image.png

到这里已经介绍完关于3个简单排查JVM内存占用大的方法了,如有什么错误的地方谢谢指正。觉得写的不错的同学可以关注我,我会更新更多好的干货。