JVM性能可视化监控之jvisualvm篇

348 阅读3分钟

1,下载插件: 进入:visualvm.github.io/pluginscent…

然后再下载:Visual GC插件

2,打卡JDK自带的jvisualvm

[JDK自带的一个用于Java程序性能分析的工具]

cd /Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/bin
执行: ./jvisualvm &

然后选择 工具>插件>已下载 进行插件安装:

3,测试

  • 3.1 堆(heap)的测试
@RestController
public class HeapController {
    List<Person> list=new ArrayList<Person>();
    @GetMapping("/heap")
    public String heap() throws Exception{
        while(true){
            list.add(new Person());
            Thread.sleep(1);
        }
    }
}

测试的时候注意设置下初始和最大内存,这样观察对象的的回收更快速:

-Xms20M:设置堆内存初始值为20M
-Xmx20M:设置堆内存最大值为20M
    这里的ms是memory start的简称,mx是memory max的简称,分别代表最小堆容量和最大堆容量。
    但是别看这里是-X参数,其实这是-XX参数,等价于:-XX:InitialHeapSize,-XX:MaxHeapSize

运行之后,访问:http://localhost:9090/heap

下面把代码改成--------

@GetMapping("/heap")
    public String heap() throws Exception{
        List<Person> list=new ArrayList<Person>();
        int i = 100000000; 
        while(i > 0){ //把while(true)改成while(i > 0)就是为了可以把方法执行完。
            list.add(new Person());
            Thread.sleep(1);
            i--;
            System.out.println(i);
        }
        return "好吧那";
    }

经过测试发现,方法在运行的过程中,堆内存依然快速增加,当方法执行完毕之后,堆内存增加速率明显下降。说明 跟线程绑定的 虚拟机栈 其实也是指向堆内存(即局部变量创建的对象也是在堆内存中),只不过方法结束后会及时释放回收。

  • 3.2 非堆内存测试
/**
 * 负责造类的元信息
 */
public class MetaspaceUtil extends ClassLoader {

    public static List<Class<?>> createClasses() {
        List<Class<?>> classes = new ArrayList<Class<?>>();
        for (int i = 0; i < 10000000; ++i) {
            ClassWriter cw = new ClassWriter(0);
            cw.visit(Opcodes.V1_1, Opcodes.ACC_PUBLIC, "Class" + i, null,
                    "java/lang/Object", null);
            MethodVisitor mw = cw.visitMethod(Opcodes.ACC_PUBLIC, "<init>",
                    "()V", null, null);
            mw.visitVarInsn(Opcodes.ALOAD, 0);
            mw.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object",
                    "<init>", "()V");
            mw.visitInsn(Opcodes.RETURN);
            mw.visitMaxs(1, 1);
            mw.visitEnd();
            MetaspaceUtil test = new MetaspaceUtil();
            byte[] code = cw.toByteArray();
            Class<?> exampleClass = test.defineClass("Class" + i, code, 0, code.length);
            classes.add(exampleClass);
        }
        return classes;
    }
}

@RestController
public class NonHeapController {
    List<Class<?>> list=new ArrayList<Class<?>>();
    @GetMapping("/nonheap")
    public String heap() throws Exception{
        while(true){
            list.addAll(MetaspaceUtil.createClasses());
            Thread.sleep(5);
        }
    }
}
--
-XX:MetaspaceSize=20M --XX:MaxMetaspaceSize=20M

设置VM:

启动后报错:

这个时候说明 方法区也会抛出 OutOfMemoryError异常。另外是因为jdk一些系统类的加载也会放在 方法区中,所以20M太小了,此时调整成50M再启动,成功。 访问:http://localhost:9090/nonheap

  • 3.3 栈溢出

然后看到压栈了6845次后报错:

现在调整一下线程的内存大小为256k: 【-Xss 为jvm启动的每个线程分配的内存大小】

然后可以看到,栈的深度为1412就发生了StackOverflowError


下面是监控远端java进程,自己测试了一下没成功,是因为防火墙可能,不过后面可以继续尝试,方法如下:

(1)在visualvm中选中“远程”,右击“添加” 
(2)主机名上写服务器的ip地址,比如31.100.39.63,然后点击“确定” 
(3)右击该主机“31.100.39.63”,添加“JMX”[也就是通过JMX技术具体监控远端服务器哪个Java进程] 
(4)要想让服务器上的tomcat被连接,需要改一下 bin/catalina.sh 这个文件
	【注意】下面的8998不要和服务器上其他端口冲突
      JAVA_OPTS="$JAVA_OPTS -Dcom.sun.management.jmxremote -
      Djava.rmi.server.hostname=31.100.39.63 -Dcom.sun.management.jmxremote.port=8998
      -Dcom.sun.management.jmxremote.ssl=false -
      Dcom.sun.management.jmxremote.authenticate=true -
      Dcom.sun.management.jmxremote.access.file=../conf/jmxremote.access -
      Dcom.sun.management.jmxremote.password.file=../conf/jmxremote.password"
(5)在 ../conf 文件中添加两个文件jmxremote.access和jmxremote.password
	jmxremote.access 文件
    	guest readonly
		manager readwrite
    jmxremote.password 文件  
    	guest guest
		manager manager
    授予权限:chmod 600 *jmxremot*
(6)将连接服务器地址改为公网ip地址 (--这个地方自己测试的时候失败,以后有机会重新测试一下)
hostname -i 查看输出情况 
	172.26.225.240 172.17.0.1
vim /etc/hosts
    172.26.255.240 31.100.39.63
(7)设置上述端口对应的阿里云安全策略和防火墙策略
(8)启动tomcat,来到bin目录
	./startup.sh
(9)查看tomcat启动日志以及端口监听
    tail -f ../logs/catalina.out
    lsof -i tcp:8080
(10)查看8998监听情况,可以发现多开了几个端口
	lsof -i:8998 得到PID 
    netstat -antup | grep PID
(11)在刚才的JMX中输入8998端口,并且输入用户名和密码则登录成功
	端口:8998 
    用户名:manager 
    密码:manager