JVM性能监控可视化工具JConsole

JConsole

JConsole(Java Monitoring and Management Console)是一种基于JMX的可视化监视、管理工具。管理的是什么?管理的是监控信息、永久代的使用信息、类加载等等。怎么用?我们先来启动JConsole,进入我们的bin目录下,会弹出是本地进程还是远程进程的选择,如果你用java的可视化工具连接远程进程,那么参数是需要配置的

  • JConsole连接远程Tomcat
  1. 首先在VM上安装好JDK1.8和Tomcat7/8。把CentOS7的默认OpenJDK换成我们安装好的JDK1.8。安装JDK1.8及替换环境变量:

(1)、编辑   vi /etc/profile

在最后一行加入下面三句内容:

export JAVA_HOME=/usr/local/jdk1.8.0_79  

export CLASSPATH=.:$JAVA_HOME/jre/lib/rt.jar:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar  

export PATH=$PATH:$JAVA_HOME/bin  

保存文件   (如何保存vi命令编辑的文件:一先按以下esc,二直接按shift+:冒号,三按wq,四直接回车)

(2)、保存后输入 source /etc/profile

 

2、找到我们tomcat的所在bin目录下的Catalina.sh,修改Catalina.sh文件,如下图的位置加上(连起来的字符串):

JAVA_OPTS="-Djava.rmi.server.hostname=192.168.189.122 -Dcom.sun.management.jmxremote.port=8099 -Dcom.sun.management.jmxremote.rmi.port=8099 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false"

3、netstat -tunlp|grep 8099查看8099端口是否被占用,如果占用另外换一个

4、防火墙端口的添加,/etc/firewalld/zones/public.xml 路径

5、防火墙重新加载,firewall-cmd --reload

6、启动我们的JConsole,然后进行远程连接,如图所示:

7、大功告成,如图所示:

1、内存

“内存”页签相当于可视化的jstat命令,用于监视受收集器管理的虚拟机内存(Java堆和永久代)的变化趋势。如图所示:

我们可以看到他可以切换显示堆内存使用量,非堆内存使用量,老年代, 新生代,元数据空间等等。code cache是指我们的JAVA语言是解释行的,但是同时也有我们的JIT即时编译,即时编译(热点代码)把字节码即时编译成类似于汇编这种语言后,也需要空间去存储,这些存放的地方就是code cache。性能优化内容中会告诉大家如何去调整这个code cache的大小。Compressed Class Space指的就是在64位的平台上为了压缩JVM中对象指针的大小就引入了类指针压缩空间,Java堆中对象指针会被压缩成32位,对象先指向这个32位的指针。大家了解有这个东西就行了。

右上角的执行GC的意思就是向当前的应用程序发起强制执行一次System.gc()的操作,也就是强迫性GC。我们来看代码MonitorTest,这段代码的作用是以64KB/50毫秒的速度往Java堆中填充数据,一共填充1000次,使用JConsole的“内存”页签进行监视,观察曲线和柱状指示图的变化。执行代码的VM参数为:-Xmx100M -Xms100M  -XX:+UseSerialGC。

程序运行后,在“内存”页签中可以看到内存池Eden区的运行趋势呈现折线状,如图所示。而监视范围扩大至整个堆后,会发现曲线是一条向上增长的平滑曲线。并且从柱状图

可以看出,在1000次循环执行结束,运行了System.gc()后,虽然整个新生代Eden和

Survivor区都基本被清空了,但是代表老年代的柱状图仍然保持峰值状态,说明被填充进堆中的数据在System.gc()方法执行之后仍然存活。笔者的分析到此为止,现提两个小问题供大家思考一下,答案我们一一来揭晓。

1)虚拟机启动参数只限制了Java堆为100MB,没有指定-Xmn参数,能否从监控图中估计出新生代有多大?

2)为何执行了System.gc()之后,图中代表老年代的柱状图仍然显示峰值状态,代

码需要如何调整才能让System.gc()回收掉填充到堆中的对象?

问题1答案:JConsole监控图图显示Eden空间为27 328KB,因为没有设置-XX:SurvivorRadio参数,所以Eden与Survivor空间比例为默认值8:1,整个新生代空间大约为27 328KB×125%=34160KB。

问题2答案:执行完System.gc()之后,空间未能回收是因为List<OOMObject>list对象仍然存活,fillHeap()方法仍然没有退出,因此list对象在System.gc()执行时仍然处于作用域之内。如果把System.gc()移动到fillHeap()方法外调用就可以回收掉全部内存。

2、线程

如果上面的“内存”页签相当于可视化的jstat命令的话,“线程”页签的功能相当于可视化的jstack命令,遇到线程停顿时可以使用这个页签进行监控分析。前面讲解jstack命令的时候提到过线程长时间停顿的主要原因主要有:等待外部资源(数据库连接、网络资源、设备资源等)、死循环、锁等待(活锁和死锁)。

执行代码LockThreadWaitTest,开始执行此时只有main线程,且该线程状态为Runable,main线程阻塞因为正在等待用户输入(sc.next();),如下图所示:

Runnable状态的线程会被分配运行时间,但readBytes方法检查到流没有更新时会立刻归还执行令牌,这种等待只消耗很小的CPU资源。在控制台输入abc后,发现多了一个mythread01线程,且该线程也为Runable状态,查看代码发现该线程是死循环所以阻塞。停留在while(true)这一行。这时候线程为Runnable状态,而且没有归还线程执行令牌的动作,会在空循环上用尽全部执行时间直到线程切换,这种等待会消耗较多的CPU资源。

如下图所示:

再在控制台输入一次ss,结果如下,main线程已经执行完毕了,所以消失了。mythread01线程还在执行(死循环),多了mythread02线程,该线程为WATING状态,

在被唤醒前不会被分配执行时间。mythread02线程正在处于正常的活锁等待,只要lock对象的notify()或notifyAll()方法被调用,这个线程便能激活以继续执行。如下图所示:

死锁等待示例:执行代码DeadLockTest,这段代码开了200个线程去分别计算1+2以及2+1的值,其实for循环是可省略的,两个线程也可能会导致死锁,不过那样概率太小,需要尝试运行很多次才能看到效果。一般的话,带for循环的版本最多运行2~3次就会遇到线程死锁,程序无法结束。造成死锁的原因是Integer.valueOf()方法基于减少对象创建次数和节省内存的考虑,[-128,127]之间的数字会被缓存,当valueOf()方法传入参数在这个范围之内,将直接返回缓存中的对象。也就是说,代码中调用了200次Integer.valueOf()方法一共就只返回了两个不同的对象。假如在某个线程的两个synchronized块之间发生了一次线程切换,那就会出现线程A等着被线程B持有的Integer.valueOf(1),线程B又等着被线程A持有的Integer.valueOf(2),结果出现大家都跑不下去的情景。

出现线程死锁之后,点击JConsole线程面板的“检测到死锁”按钮,将出现一个新的“死锁”页签,很清晰地显示了线程Thread-11在等待一个被线程Thread-12持有Integer对象,而点击线程Thread-1则显示它也在等待一个Integer对象,被线程Thread-4持有,这样两个线程就互相卡住,都不存在等到锁释放的希望了。如下图所示:

3、类页签

当前线程加载过哪些类以及卸载过哪些类都会在这里进行显示。

   

4、VM的概要

 虚拟机的概要显示,运行时间、虚拟机的版本,当前线程的进程ID,还有我们的VM的参数,在命令行中同样可以用jps看到。

   

MBean:没有太多实际用处,可以不用管他。

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值