VisualVM使用与调优案例

1.JVM构成

  先看两张图,第一张是1.8版本之前的JVM,第二张图是JVM1.8版本

 

 

                                                                  图一

 

 

                                                                 图二

  对比两张图可以发现:JDK1.8之后移除了堆内存中的永久代(Permanent),改为了存储在本地内存的元空间(MetaSpace)

  为何要移除永久代

  永久代启动时大小是固定的,不方便对其进行调优,增加了垃圾收集的复杂度以及降低回收的效率

  类及方法的大小很难确定,因此永久代到底要设置多大并不好把握,如果设置过小那么就容易导致永久代溢出,设置过大则容易导致老年代溢出 

2.VisualVM远程监控的配置连接

  参考博客:https://www.cnblogs.com/sunshine-2015/p/5547128.html

  连接远程Tomcat

    hostname -i 查阅自己的IP是否是公网IP/内网IP,如果外网使用,请用外网的IP

    

    通过jstatd启动RMI

    远程访问jstatd需要授权,在JAVA_HOME/bin路径下创建jstatd.all.policy文件,内容:grant codebase “flie:${java.home}/../lib/tools.jar” {

    Permission java.security.AllPermission;};

    然后敲命令

          jstatd -J-Djava.security.policy=jstatd.all.policy &

  配置连接jstatd方式

    配置参数,输入IP,端口默认1099

    

 

 

     

 

 

     

  调优完毕,关闭授权

    由于jstatd.all.policy授权访问,因为安全问题,调优之后关闭(删除该文件/关闭提供远程连接的进程[pid:10824])

  案例分析

    

  1. isualVM案例分析

  ①    堆内存分析

  写了一段代码,这段代码主要的逻辑是:

  1. 有四次循环且每次循环的次数是递增的
  2. 每个循环里面都是new一个对象,将new出来的对象按key,value形式存入HashMap当中

 

如图5.1:黑色标明的1/2/3/4分别对应了代码里的四次循环,每次循环次数是增加的,因此耗用的系统资源会更多,图5.1-1表明,CPU耗用量逐次增加,堆内存逐次增加,并且你看到堆内存没有被进行回收,没有进行GC,正常情况如图5.1-2表明内存分配以及回收

 

 

 

 

 

 

如图5.2:内存中占用量最大的类型是:char[],hashMap和String以及TestMemory。根据这张图内存占用量的排序,你应该可以推测出,应该是TestMemory对象过多,应该又是存到hashMap当中的,这时候就大概可以知道代码位置了

 

 

 贴上代码:

package com.test;

import java.util.HashMap;
import java.util.Map;
public class CyclicDependencies {
    private static final Map map = new HashMap();
    public static void main(String args[]){
        try {
            Thread.sleep(10000); 
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        for(int i=0; i<1000000;i++){
            TestMemory t = new TestMemory();
            map.put("key"+i,t);
        }
        System.out.println("first");   
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        for(int i=0; i<1000000;i++){
            TestMemory t = new TestMemory();
            map.put("key"+i,t);
        }
        System.out.println("second");
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        for(int i=0; i<3000000;i++){
            TestMemory t = new TestMemory();
            map.put("key"+i,t);
        }
        System.out.println("third");
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        for(int i=0; i<4000000;i++){
            TestMemory t = new TestMemory();
            map.put("key"+i,t);
        }
        System.out.println("forth");
        try {
            Thread.sleep(Integer.MAX_VALUE);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("qqqq");
    }
}

 

②线程死锁分析

  写了一段死锁代码,在eclipse里运行

  VisualVM会自动监测到死锁发生,并弹出红色警告文字“检测到死锁!生成一个线程Dump以获取更多信息

 

 

 

生成的Dump文件可以看到deadlock信息 Thead-1和Thead-2以及引发死锁的代码位置

 

 

 然后我们回看一下代码,Thead - 1想要锁住b,但b被Thead - 2锁住了,同样Thead - 2想要锁住a,但a被Thead - 1锁住了,这就形成了死锁

请看代码:

public class DeadLock {

    public static void main(String[] args) {
        final Object a = new Object();
        final Object b = new Object();

        new Thread() {
            @Override
            public void run() {
                try {
                    synchronized (a) {
                        System.out.println("Thread 1 got the lock of a");
                        Thread.sleep(1000);
                        System.out.println("Thread 1 was trying to get the lock of b");
                        synchronized (b) {
                            System.out.println("Thread 1 win");
                        }
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }.start();
        new Thread() {
            @Override
            public void run() {
                try {
                    synchronized (b) {
                        System.out.println("Thread 2 got the lock of b");
                        Thread.sleep(1000);
                        System.out.println("Thread 2 was trying to get the lock of a");
                        synchronized (a) {
                            System.out.println("Thread 2 win");
                        }
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }.start();
    }
}

3.调优建议

  JVM调优没有固定的模板配置,它需要根据应用实际情况区别对待

  但是会有以下的通用建议

  a.Xms的值与Xmx的值要尽量保持一致

  在很多情况下,-Xms和-Xmx设置成一样的。这么设置,是因为当Heap不够用时,会发生内存抖动,影响程序运行稳定性

  b.最大内存设置不能超过物理内存

  机器物理内存16G,给堆分配的内存肯定不可以设置成16G或者更多。因为除了堆内存还有非堆内存,应当视具体情况合理分配内存

  c.尽量减少Minor GC/Full GC的频率以及持续的时间

  理想的结果是:YGC发生次数较少且每次时间较快&&很长时间不发生FGC

  Eden区分配空间过少,GC的频率会较高

  Eden区分配空间过多,GC的时候持续时间会较长

  尽量做到让对象在Minor GC阶段被回收、让对象在新生代多存活一段时间

  尽量不要创建过大的对象及数组

posted @ 2020-03-18 10:08  Mosicol  Views(478)  Comments(0Edit  收藏  举报