一、简介

VisualVM 是一款免费的,集成了多个JDK命令行工具的可视化工具,它能为您提供强大的分析能力,对Java应用程序做 性能分析和调优 。这些功能包括 生成和分析海量数据跟踪内存泄漏监控垃圾回收器执行内存和CPU分析。本文主要介绍如何使用VisualVM进行性能分析及调优。

自从JDK 6 Update 7 以后已经作为Oracle JDK的一部分,位于JDK根目录的bin文件夹下,无需安装,直接运行即可。

JDK路径查看:

MAC查找JDK的路径
Windows查找JDK的路径

二、内存分析

VisualVM通过检测JVM中加载的类和对象信息等帮助我们分析内存使用情况,我们可以通过VisualVM的监视标签对应用程序进行内存分析。

1、Heap堆

首先写一个内存堆占用较大的例子,代码如下:

  1. public class Main {
  2. public final static int OUTOFMEMORY = 200000000;
  3. public static String oom;
  4. public static StringBuffer tempOOM = new StringBuffer();
  5. public static void main(String[] args){
  6. threadHeap(OUTOFMEMORY);
  7. }
  8. public static void threadHeap(final int len){
  9. Thread t = new Thread(new Runnable() {
  10. @Override
  11. public void run() {
  12. int i = 0;
  13. while(i < len){
  14. i++;
  15. try{
  16. tempOOM.append("abcdefghijabcdefghijabcdefghijabcdefghijabcdefghij");
  17. if(i%1000 == 1){
  18. Thread.sleep(10);
  19. }
  20. //Thread.sleep(1);
  21. } catch (Exception e){
  22. e.printStackTrace();
  23. break;
  24. } catch (Error e){
  25. e.printStackTrace();
  26. break;
  27. }
  28. }
  29. oom = tempOOM.toString();
  30. System.out.println("Thread Heap Length : " + oom.length());
  31. }
  32. });
  33. t.setName("ThreadHeap_1");
  34. t.start();
  35. }
  36. }

运行该段代码,然后查看VisualVM Monitor(监视), 堆内存会慢慢变大。

Java监控工具VisualVM - 图1
在程序运行结束之前, 点击 堆Dump 按钮, 等待一会儿,得到dump结果,可以看到一些摘要信息。

点击类, 发现char[]所占用的内存是最大的。
Java监控工具VisualVM - 图2

双击 char[] ,得到如下实例数结果。
Java监控工具VisualVM - 图3
StringBuffer类型的全局变量 tempOOM 占用内存特别大, 注意局部变量是无法通过堆dump来得到分析结果的

另外,对于 堆 dump 来说,在远程监控jvm的时候,VisualVM是没有这个功能的,只有本地监控的时候才有

三、CPU分析

CPU 性能分析的主要目的是统计函数的调用情况及执行时间,或者更简单的情况就是统计应用程序的 CPU 使用情况。

没有程序运行时的 CPU 使用情况如下图:
Java监控工具VisualVM - 图4
运行一段 占用CPU 的小程序,代码如下:

  1. public class Main {
  2. public static void main(String[] args){
  3. cpuFix();
  4. }
  5. public static void cpuFix(){
  6. try{
  7. // 80%的占有率
  8. int busyTime = 8;
  9. // 20%的占有率
  10. int idelTime = 2;
  11. // 开始时间
  12. long startTime = 0;
  13. while (true) {
  14. // 开始时间
  15. startTime = System.currentTimeMillis();
  16. /*
  17. * 运行时间
  18. */
  19. while (System.currentTimeMillis() - startTime < busyTime) {
  20. ;
  21. }
  22. // 休息时间
  23. Thread.sleep(idelTime);
  24. }
  25. }catch(Exception ex){
  26. ex.printStackTrace();
  27. }
  28. }
  29. }

结果如下:
Java监控工具VisualVM - 图5
过高的 CPU 使用率可能是由于我们的项目中存在低效的代码;

在我们对程序施压的时候,过低的 CPU 使用率也有可能是程序的问题。

点击【抽样器】, 点击【CPU】按钮, 启动CPU性能分析会话,VisualVM 会检测应用程序所有的被调用的方法,在【CPU 样例】下可以看到我们的方法cpuFix() 的自用时间最长, 如下图:
Java监控工具VisualVM - 图6
切换到【线程 CPU 时间】页面下,我们的 main 函数这个进程占用CPU时间最长, 如下图:
Java监控工具VisualVM - 图7

四、线程分析

Java 语言能够很好的实现多线程应用程序。当我们对一个多线程应用程序进行调试或者开发后期做性能调优的时候,往往需要了解当前程序中所有线程的运行状态,是否有死锁、热锁等情况的发生,从而分析系统可能存在的问题。

在 VisualVM 的监视标签内,我们可以查看当前应用程序中所有实时线程(Live threads)和守护线程(Daemon threads)的数量等实时信息。

运行一段小程序,代码如下:

  1. public class Main {
  2. public static void main(String[] args){
  3. startThread("Thread_1");
  4. startThread("Thread_2");
  5. }
  6. public static void startThread(String threadName){
  7. Thread thread = new Thread(new Runnable() {
  8. @Override
  9. public void run() {
  10. while (true){
  11. }
  12. }
  13. });
  14. thread.setName(threadName);
  15. thread.start();
  16. }
  17. }

Java监控工具VisualVM - 图8
VisualVM 的线程标签提供了三种视图,默认会以时间线的方式展现, 如下图:

可以看到两个我们run的程序里启的线程:Thread_1 和 Thread_2。

Java监控工具VisualVM - 图9
再来一段死锁的程序,看VisualVM 能否分析出来:

  1. public class Main {
  2. public static void main(String[] args){
  3. diethread();
  4. }
  5. public static void diethread(){
  6. DieThread d1=new DieThread(true);
  7. DieThread d2=new DieThread(false);
  8. final Thread t1 = new Thread(d1);
  9. final Thread t2 = new Thread(d2);
  10. t1.setName("DieThread_1");
  11. t2.setName("DieThread_2");
  12. t1.start();
  13. t2.start();
  14. }
  15. }
  16. package com.javaagent.thread;
  17. public class DieThread implements Runnable {
  18. public static Object obj1=new Object();
  19. public static Object obj2=new Object();
  20. private boolean flag;
  21. public DieThread(boolean bl){
  22. flag = bl;
  23. }
  24. @Override
  25. public void run() {
  26. if(flag) {
  27. while(true) {
  28. synchronized(obj1) {
  29. try {
  30. Thread.sleep(1000);
  31. }catch (InterruptedException e){
  32. e.printStackTrace();
  33. }
  34. System.out.println("线程" + Thread.currentThread().getName() + "获取obj1锁对象,等待获取obj2锁对象...");
  35. synchronized(obj2) {
  36. System.out.println(Thread.currentThread().getName() + " ---- obj2.");
  37. }
  38. }
  39. }
  40. }
  41. else {
  42. while(true){
  43. synchronized(obj2) {
  44. System.out.println("线程" + Thread.currentThread().getName() + "获取obj2锁对象,等待获取obj1锁对象...");
  45. synchronized(obj1) {
  46. System.out.println(Thread.currentThread().getName() + " ---- obj1.");
  47. }
  48. }
  49. }
  50. }
  51. }
  52. }

打开VisualVM检测到的JVM进程,我们可以看到这个tab在闪,VisualVM已经检测到死锁。

另外可以点击【线程 Dump】线程转储,进一步分析。
Java监控工具VisualVM - 图10
Java监控工具VisualVM - 图11