详解使用 jstack 跟踪 java 异常代码

/ Java / 没有评论 / 1572浏览

详解使用 jstack 跟踪 java 异常代码

最近公司有一项业务在做活动,流量一下子大增。数据暴涨了80,系统无法支撑,导致了程序内存溢出,系统宕机。查看日志发现是有内存溢出的异常,今天就为大家分享一下如何使用 jstack 命令排查定位java程序中的异常代码。

自造 java 内存溢出代码

为了讲清楚 jstack 的使用,我们先自造一个java 内存溢出的程序。程序代码如下:

class KeylessEntry {
    static class Key {
        Integer id;

        Key(Integer id) {
            this.id = id;
        }

        @Override
        public int hashCode() {
            return id.hashCode();
        }
    }

    public static void main(String[] args) {
        Map m = new HashMap();
        while (true){
            for (int i = 0; i < 10000; i++){
                if (!m.containsKey(i)){
                    m.put(new Key(i), "Number:" + i);
                }
            }
        }
    }
}

当你运行上面的代码时,你可能会期望它运行起来永远不会出问题,毕竟内置的缓存方案只会增加到10,000个元素,然后就不会再增加了,所有的key都已经出现在 HashMap中。然而,事情并非如此。元素将会一直增长, 因为Key这个类没有在hashCode()后实现一个合适的equals()方法。

运行这个程序后,我们发现不需要太长的实际,程序就报内存溢出了。打开任务管理,此例中,找出java进程ID。 任务管理器中查看java进程id

会了方便,大家也可以使用使用ProcessExplorer找到ID号为7064的java进程。进程ID为7064的属性信息在Thread标签找到CPU利用率的线程信息,TID为6120(10进制) ProcessExplorer 查看进场的线程信息

将CPU利用率高的线程ID 6120(10进制)转换为0x17E8(16进制) 10进制转16进制

使用jstack查看进程7064的线程信息。找到线程号为0x17E8的线程。命令:

jstack -l  7064

jstack 定位异常代码 根据提示的行号,我们定位到相关的代码。通过分析发现,是map造成的内存溢出。

解决方法很简单,只要和下面的示例一样添加一个equals方法就可以了。但是在找到问题所在之前,你肯定已经花费了不少宝贵的脑细胞。

@Override
public boolean equals(Object o) {
    boolean response = false;
    if (o instanceof Key) {
        response = (((Key)o).id).equals(this.id);
    }
    return response;
}

jstack 命令很实用,是java虚拟机自带的一种堆栈跟踪工具。学会它对你的职业生涯有很大的帮助!