头图

相信很多初学java的同学都有这种感觉:jvm为java开发者节省了很多内存相关的思考,我们不需要分配内存和手动释放对象回收内存,堆内存与开发者的距离变得太远了。其结果就是,不太讲究的内存使用使得java开发者在面对高内存或者线上环境栈溢出时容易一头雾水。
如果你也曾经想过扒开jvm的底裤看看到底是什么对象填满了整个堆内存,那么绝不应该错过Jprofiler。这是一个非常好用的可视化jvm监视工具,可以连接运行中的jvm,也可以读取线上OOM时的dump文件,从而可视化、直观地快速定位到问题。

适用范围

适用情况:
1.数量较少的对象、单例或由单例持有的对象
这种类型的对象能够以类型为依据快速定位到,从而易于找到异常对象。如果是单例或单例的属性则更加容易发现。
2.业务与类型强相关的,比如某个类的对象只在某个业务中使用
3.问题发生在类的静态属性中
类对象(Class对象)在同一个类加载器家族中也是单例的,所以也容易定位。但是如果是一个tomcat服务器中加载大量war包的情况,可能不太容易发现。

不适用情况:
1.有大量字节流处理的业务
这种情况下堆内存中往往有大量的byte[]对象,容易干扰判断
2.类的适用范围太广
同样是不太容易定位到具体产生问题的对象属于哪个业务
3.对性能要求高、没法dump的业务
连接JProfiler后程序性能会下降很多。不过都已经发生内存泄漏了,也许内存才是当前最关注的问题。

示例程序

import java.util.HashMap;

public class FindObjectsInHeap {

    public static void main(String[] args) {
        MyObject myTestBean = new MyObject(new HashMap<>());
        while (true) {
            myTestBean.doMyThing();
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
    public static class MyObject {
        private HashMap<Integer, Integer> myMap;
        private int myNum = 0;

        public MyObject(HashMap<Integer, Integer> map) {
            this.myMap = map;
        }

        public void doMyThing() {
            myMap.put(myNum, ++myNum);
        }
    }
}

用上述程序运行时,程序会持续地向myTestBean中的map添加entry,直至内存溢出。可以通过Jprofiler观察到该全过程。

使用步骤

1.使用JProfiler连接运行中的JVM

2.使用JProfiler的HeapWalker创建一个堆内存的SnapShot
image-20201127192403483.png

3.在对象列表中找到想要查看的对象

对于单例、数量较少的对象,比较容易找到

比较适合的是被SpringBoot容器管理起来的对象
image-20201127192630814.png

4.双击对象 选择Reference为Outgoing references,这样可以查看指定对象持有的成员变量(Outgoing references表示查看指定对象引用了哪些对象,Incoming references表示查看指定对象被哪些对象引用)
image.png

5.使用脚本对对象进行查看

假设想要查看myMap中某个key的value

展开想要查看的对象后发现对象很大,没办法手动查看或者筛选
image-20201127193539084.png

选中map后点击图中按钮 打开脚本编辑器
image-20201127194444373.png

脚本编辑器可以将选中的对象作为参数传入。使用对象的一些成员方法来操作对象,筛选结果

if(hashMap.containsKey(65535)){
    return "it exists.";
}else{
    return "not exist.";
}

点击ok来执行脚本
image-20201127200549830.png

通过这种方式便可简便地访问内存中的对象了。

虽然通过debug打断点的方式也能通过计算表达式对暂停中的堆内存对象进行筛选和遍历,但会中断线上业务,不太方便 使用JProfiler比较安全.

总之,在我的职业生涯初期,jProfiler在帮助直观理解内存、线程模型上起了很大作用,也帮我解决过一些实际的内存泄露问题。虽然在使用上稍显麻烦,但直观的UI界面对新手会有很大帮助。如果你也遇到了类似问题,希望这篇文章能帮到你。


风觅椒塘考曲棋
210 声望41 粉丝

爱学习的小白一枚呀