读 - 深入理解java虚拟机 - 笔记(八-2) - Java内存分析工具(2章)

JDK提供给我们很多小工具,可供我们使用去分析当前内存的一些数据。

jps(JVM Process Status)

可以列出正在运行的虚拟机进程,以及主类名称和唯一ID。

public class TestOom {
    public static void main(final String[] args) throws Exception {
        while (true){
        }
    }
}

基于上述简单代码,使用命令可以看到:


虽然简单,但是它是我们使用工具时最开始的一步获取PID。一些参数如下:


jstat(JVM Statistics Monitoring Tool)

用来监视虚拟机各种运行状态信息的命令行工具,应该说没有GUI界面的情况下,它是首选工具。

jstat [ option vmid [ interval [ s | ms ] [ count ] ] ]

对于本机而言,vmid和lvmid是一致的,参数inteval和count代表查询间隔和次数,省略代表查询一次,假设每250毫秒查询一次进程2764的垃圾收集状况,一共查询20此:

jstat -gc 2764 250 20

主要参数列表:


接着上次的例子:


E(Eden区)使用了28.21%。

S0和S1(Survivor区),互为备份的。

O(Old区,老年代),没有使用。

P(永久代),使用了32.02%。

YGC(Minor GC)发生0次。

YGCT(Minor GC总耗时)0s。

FGC(Full GC)发生0次。

FGCT(Full GC总耗时)0s。

GCT(所有GC总耗时)0s。

jinfo(Configuration Info for Java)

Java配置信息工具,实时查看和调整虚拟机各项参数。感觉这个用的肯定是非常少的,不去看了,知道下。

jmap(Memory Map for Java)

用来生成堆转储快照(heapdump或者dump文件)。

1.-XX:+HeapDumpOnOutOfMemoryError

-XX:HeapDumpPath=/dump/ 

配置上述两个参数可以在出现OOM时生成dump文件和保存路径。

2.Kill -3命令发送退出信号让虚拟机生成dump文件。


windows平台功能受限制,演示不了。不去关注。

jstack(Stack Trace for Java)

看名字其实就能理解了,生成的是虚拟机当前时刻的线程快照(threaddump或者javacore文件)。线程快照是当前虚拟机内每一条线程正在执行的方法堆栈的集合。

目的:定位线程出现长时间停顿的原因,如死锁,死循环,请求外部资源导致的长时间等待。


使用如下(截取关键部分):


因为程序是个无线循环,所以一直运行到这边。

拿一个喜欢乐见的例子来分析一下看看,死锁。

public class TestOom {

    public static Object lock1 = new Object();

    public static Object lock2 = new Object();

    public static void main(final String[] args) throws Exception {

        new Thread(new Runnable() {
            public void run() {

                synchronized (lock1){
                    try {
                        Thread.sleep(100);
                    }catch (Exception e){

                    }
                    synchronized (lock2){
                        System.out.println("yoh,lock2");
                    }
                }


            }
        }).start();

        new Thread(new Runnable() {
            public void run() {

                synchronized (lock2){
                    try {
                    }catch (Exception e){
                    }
                    synchronized (lock1){
                        System.out.println("yoh,lock1");
                    }
                }


            }
        }).start();

    }
}

结果截图,部分:



很清楚,死锁位置,出错原因都有,神器有木有。

jhat(JVM Heap AnalysisTool)

用来分析生成的dump文件的。但是作者不推荐使用它去分析:

1. 一般不会在服务器内去直接分析dump文件,可以复制到其他环境中去,使用更方便的工具。

2. 比较简陋。

试验一下吧,文件就拿前一篇里生成的dump文件吧。

内存溢出场景

1. jhat 文件


2. 访问 localhost: 7000



的确不好用,很卡,这个文件才27M,一般的dump文件不得上天啊。但是简单的来看还是能看见,因为我们的例子简单。

Visual VM

网上推荐下面一款Visual VM。在jdk1.7.0_17\bin\jvisualvm.exe。应该是这个吧。

打开我们上面的那个dump文件去看看。


非常清晰:jstack是命令行神器,这个工具应该就是GUI下的神器了吧。


摘要里面也清晰的列出了内存溢出的具体代码位置:


另外参考网上一篇博文的分析,很全面:

内存分析步骤

常见例子上手:

代码问题很容易导致内存溢出,如果你没有遇见过,只是你项目的并发太少,经得起访问。

我们项目内有这样一段代码,其实平时没多大问题,但是在我最近开始关注JVM内存溢出和做了一些测试之后,发现这段代码还是存在不少问题的。

一些前提:

1. 项目的架构中针对dao层的方法有一些固定方法,比如selectByPrimaryId,或者deleteByPrimaryId。这都是常见的,你新建的时候只要继承就能直接使用。

但是针对非主键的查询没有固定,这是由于不确定性造成的,也没法统一,因此这种查询还是需要你自己去新建,比如selectByid(Integer id),这个id可能是一个序号,类似于外键。

但是项目里有这个方法List<Model> selectByQueryObject(Model object)。也就是你可以通过构造一个查询model,往里面塞值,比如说塞id,就能查询出一个list。这样你自己就可以偷懒不需要重新写一个查询方法了。

带来的问题:

这就带来了一个问题,涉及到比如说日志表的查询的时候,你本来只想获取某个用户Id的最新的一条数据的,但是你还是通过一个list查询,然后代码里获取get(0)了。

1. 并发少的时候吃得消,毕竟新建的对象又不大。

2.日志表数据量小的时候也无所谓,但是数据量一旦大起来,查询本身就很费力。

并发大+数据量大的时候这种代码立马就会增加开销。基于两个场景问题,我进行了测验。

场景设置:

1. 数据库:


数据库准备了110万数据,查询字段针对的是age字段,类比外键字段。模仿的场景就是age代表一个用户id,而所代表的数据就是它的日志。

2. 服务端:
@Controller
@RequestMapping("/test")
public class TestController {

    @Resource(name = "userServiceImpl")
    private UserService userService;

    @RequestMapping("/testq")
    public void showStudent(Integer id) throws Exception {

        List<User> a = userService.selectList(id);
        if(!a.isEmpty()){
            System.out.println("this name is: "+a.get(0).getName());
        }
    }
}
mapper文件,模仿的就是一个list查询,只不过此处省略了对象的构造。
    <select id="selectList"  resultType="com.system.po.User">
        select * from user where age = #{id} order by create_time desc
    </select>
3. 客户端:
public class TestJava {

    public static int [] arr = {114,11,23,12,42,56,13,14,15,16,17,110,100,1,2,3,4,5,6,7,8,9,10,18,19,20,21,22,24,25};

    public static void main(String[] args) {

        ExecutorService service = Executors.newCachedThreadPool();
        final CountDownLatch cdOrder = new CountDownLatch(1);
        final CountDownLatch cdAnswer = new CountDownLatch(120);
        for(int i=0;i<120;i++){
            Runnable runnable = new Runnable(){
                public void run(){
                    try {
                        cdOrder.await();
                        try {
                            String s = "http://localhost:8080/test/testq.htm?id=";
                            int index=(int)(Math.random()*arr.length);
                            s+=arr[index];
                            URL url = new URL(s);
                            URLConnection URLconnection = url.openConnection();
                            HttpURLConnection httpConnection = (HttpURLConnection) URLconnection;
                            int responseCode = httpConnection.getResponseCode();
                            if (responseCode == HttpURLConnection.HTTP_OK) {
                                System.err.println("成功");
                            } else {
                                System.err.println("失败");
                            }
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    } finally {
                        cdAnswer.countDown();
                    }
                }
            };
            service.execute(runnable);
        }
        try {
            Thread.sleep((long)(Math.random()*10000));
            cdOrder.countDown();
            cdAnswer.await();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {

        }
        service.shutdown();
    }
客户端通过CountDownLatch和线程池来模拟瞬时高并发。

3. 结果:

启动时我设置了服务端内存为512M。

3.1 刚启动并且项目稳定运行时的截图:常见的一些实例和大小,没有特别异常的地方。


3.2 执行瞬时并发时的堆情况:很明显的可以看见实体类和Integer太多了,这还只是查询,没有涉及到具体的业务代码。


4. 优化:
优化可以从两个方面来:

4.1 代码层面:把这种list查询换成精确查询,因为只是获取最新的一个,所以只拿出一个就行,别偷懒用这种list查询。

4.2 数据库层面:加索引,减少查询时间。

猜你喜欢

转载自blog.csdn.net/qq_32924343/article/details/80694246