对性能瓶颈问题来源定位的可视化工具
OutOfMemoryError
内存泄漏
线程死锁
锁争用(Lock Contention)
Java进程消耗CPU过高
在JDK6u7后,VisualVM作为Sun的JDK发行版的标准部分,可以替代例如jinfo
、jmap
、jstack
、jstat
和jConsole
。
介绍 版本:JDK1.5+
Java Monitoring and Management Console,基于JMX的可视化监视、管理工具。
JMX (Java Management Extensions,即管理Java的扩展),这种机制可以方便的管理正在运行中的Java程序。常用于管理线程、内存、日志级别、服务重启、系统环境等。一个JMX管理资源可以是一个Java应用、一个服务或一个设备,它们可以用Java开发,或者至少能用Java进行包装,并且能被置入JMX框架中,从而成为JMX的一个管理构件(Managed Bean),简称MBean。
使用 命令行中输入jconsole
回车,将自动搜索出本机运行的所有进程,双击选择要监控的进程即可,也可以远程连接服务器。
概述
堆内存使用量、线程、类、CPU使用情况
对着图右击可以保存数据到CVS文件
内存
可视化的jstat
,用于监视受收集器管理的虚拟机内存的变化趋势
查看不同内存(堆内存、非堆内存),使用的GC算法及回收次数和时间
前提要学习好Java的内存模型
线程
可以输入字符串过滤关键线程
查看某一线程的名称、状态、阻塞和等待的次数、堆栈的信息
红色:线程数目的峰值;蓝色:当前活动的线程
检测死锁(D),有时很有用
类
红线:总共加载的类(包括后来卸载的)
蓝线:当前的类加载
VM摘要
MBean
介绍 版本:JDK6u7+
不需要被监视的程序基于特殊Agent运行,对程序的实际性能影响很小,可以直接应用在生产环境中。
通过插件拓展的特性,可以做到:
显示JVM进程和进程的配置、环境信息(jps、jinfo)
监视应用程序的CPU、GC、堆、方法区及线程的信息(stat、jstack)
dump及分析堆转储快照(jmap、jhat)
方法级的程序运行性能分析,找出被调用最多、运行时间最长的方法
离线程序快照:收集程序的运行时配置、线程dump、内存dump等信息建立一个快照
使用
安装
生成堆dmp文件
Aplications
窗口右击节点,选择Heap Dump
Monitor
,点击Heap Dump
Profiler
中可以对运行期间方法级的CPU和内存进行分析,但会影响程序运行性能,不建议在生产环境使用。
CPU:统计每个方法的执行次数、执行耗时
Memory:统计每个方法关联的对象数及对象所占空间
JDK1.5后,如果使用Client模式,在进行Profiler之前,最好在监视程序中加入-Xshare:off
来关闭类共享优化。
Client模式下的虚拟机加入并自动开启了类共享,在多虚拟机进程中共享rt.jar
中类数据以提高加载速度和节省内存的优化,但这可能会导致被监视的应用程序崩溃。
BTrace
动态日志跟踪:在不停止程序运行的前提下,通过热部署技术加入原本并不存在的调试代码,以实现对程序的动态调试
打印调用堆栈、参数、返回值
性能监控、定位连接泄漏和内存泄漏、解决多线程竞争问题
Threads
线程长时间停顿的原因
等待外部资源(数据库连接、网络资源、设备资源等)
死循环
锁等待(活锁和死锁)
插件
插件中心
菜单 > Tools > Plugins > Available Plugins > Install
下载插件分发文件(.nbm)
菜单 > Tools > Plugins > Downloaded > Add Plugins > 选择.nbm文件 > Install
实例-填充内存
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import java.util.ArrayList;import java.util.List;public class OOMObject { private byte [] placeHolder=new byte [64 *1024 ]; public static void fillHeap (int num) throws InterruptedException { List<OOMObject> list=new ArrayList<>(); for (int i=0 ;i<num;i++){ Thread.sleep(50 ); list.add(new OOMObject()); } System.gc(); } public static void main (String[] args) throws Exception { fillHeap(1000 ); } }
1 -Xms100m -Xmx100m -XX:+UseSerialGC
运行后可看到,内存Eden区呈折线状,整个堆的曲线是平滑向上增长。
实例-线程死循环&等待
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 import java.io.BufferedReader;import java.io.InputStreamReader;public class Lock { public static void createBusyThread () { Thread thread =new Thread(new Runnable() { @Override public void run () { while (true ); } },"testBusyThread" ); thread.start(); } public static void createLockThread (final Object lock) { Thread thread =new Thread(new Runnable() { @Override public void run () { synchronized (lock) { try { lock.wait(); }catch (InterruptedException e) { e.printStackTrace(); } } } },"testLockThread" ); thread.start(); } public static void main (String[] args) throws Exception { BufferedReader bufferedReader=new BufferedReader(new InputStreamReader(System.in)); bufferedReader.readLine(); createBusyThread(); bufferedReader.readLine(); Object object=new Object(); createLockThread(object); } }
运行后,查看VisualVM中的Threads
中的main
线程,会发现一直在readBytes
等待System.in
的键盘输入,线程状态为Runnable
,线程会被分配运行时间,但readBytes
检查到流没有更新时会立刻归还执行令牌,这种等待只消耗很小的CPU资源。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 "main" - Thread t@1 java.lang.Thread.State: RUNNABLE at java.io.FileInputStream.readBytes(Native Method) at java.io.FileInputStream.read(FileInputStream.java:255 ) at java.io.BufferedInputStream.read1(BufferedInputStream.java:284 ) at java.io.BufferedInputStream.read(BufferedInputStream.java:345 ) at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:284 ) at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:326 ) at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:178 ) at java.io.InputStreamReader.read(InputStreamReader.java:184 ) at java.io.BufferedReader.fill(BufferedReader.java:161 ) at java.io.BufferedReader.readLine(BufferedReader.java:324 ) at java.io.BufferedReader.readLine(BufferedReader.java:389 ) at com.super2bai.jvm.Lock.main(Lock.java:45 )
随便在控制台输入点内容回车后,刷新VisualVM中的Threads
,会看到testBusyThread
线程,因为一直在执行空循环,所以停留在Lock
的15行,也就是while(true);
,这时候线程状态为RUNNABLE
,而且没有归还线程执行令牌的动作,会在空循环上用尽全部执行时间直到线程切换,这种等待会消耗较多的CPU资源。
1 2 3 4 "testBusyThread" - Thread t@17 java.lang.Thread.State: RUNNABLE at com.super2bai.jvm.Lock$1 .run(Lock.java:15 ) at java.lang.Thread.run(Thread.java:748 )
继续在控制台输入内容后回车,刷新VisualVM中的Threads
,会看到testLockThread
线程,停留在Lock
的33行,也就是lock.wait();
,正在等待着lock对象的notify()
或notifyAll()
方法的出现,线程处于WAITING
状态,也就是正常的活锁等待,在被唤醒前不会被分配执行时间,只有lock对象的notify()
或notifyALL()
方法被调用,便能激活以继续执行。
1 2 3 4 5 6 7 "testBusyThread" - Thread t@9 java.lang.Thread.State: WAITING at java.lang.Object.wait(Native Method) - waiting on <753 b0264> (a java.lang.Object) at java.lang.Object.wait(Object.java:502 ) at com.super2bai.jvm.Lock$2 .run(Lock.java:33 ) at java.lang.Thread.run(Thread.java:748 )
实例-线程死锁
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 public class SynAddRunnable implements Runnable { int a, b; public SynAddRunnable (int a, int b) { this .a = a; this .b = b; } @Override public void run () { synchronized (Integer.valueOf(a)) { synchronized (Integer.valueOf(b)) { System.out.println(a + b); } } } public static void main (String[] args) { for (int i = 0 ; i < 100 ; i++) { new Thread(new SynAddRunnable(1 , 2 )).start(); new Thread(new SynAddRunnable(2 , 1 )).start(); } } }
运行后,查看JConsole内的线程,点击下方检测死锁 按钮,会出现新的面板死锁 ,点开任意一个线程,即可看到两个线程互相等待。
1 2 3 4 5 6 7 名称: Thread-157 状态: java.lang.Integer@248 beb75上的BLOCKED, 拥有者: Thread-158 总阻止数: 1 , 总等待数: 0 堆栈跟踪: com.super2bai.jvm.thread.SynAddRunnable.run(SynAddRunnable.java:28 ) java.lang.Thread.run(Thread.java:748 )
和
1 2 3 4 5 6 7 名称: Thread-158 状态: java.lang.Integer@47 cf143上的BLOCKED, 拥有者: Thread-157 总阻止数: 1 , 总等待数: 0 堆栈跟踪: com.super2bai.jvm.thread.SynAddRunnable.run(SynAddRunnable.java:28 ) java.lang.Thread.run(Thread.java:748 )
Thread-157等待Thread-158持有的Integer对象,而Thread-158也在等待Thread-157持有的Integer对象,这样两个线程就互相卡住,都不存在等到锁释放的希望了。
对比
VisualVM功能稍强于Jconsole,VisualVM的高级功能是通过安装插件来拓展
VisualVM可以在Applications窗口右击程序节点启用“Disable Heap Dump on OOME”,VisualVM将自动生成一个堆转储文件
参考 JMX
IBM Java开发