Java性能调优工具

1、JDK命令行工具                                                                       

1.1、jps命令

      jps用于列出Java的进程,jps可以增加参数,-m用于输出传递给Java进程的参数,-l用于输出主函数的完整路径,-v可以用于显示传递给jvm的参数。

1
2
jps -l -m -v
31427  sun.tools.jps.Jps -l -m -v -Dapplication.home=/Library/Java/JavaVirtualMachines/jdk1. 7 .0_55.jdk/Contents/Home -Xms8m

1.2、jstat命令

      jstat是一个可以用于观察Java应用程序运行时信息的工具,它的功能非常强大,可以通过它查看堆信息的详细情况,它的基本使用方法为:

1
jstat -<option> [-t] [-h<lines>] <vmid> [<interval> [<count>]]

  选项option可以由以下值组成:

1
2
3
4
5
6
7
8
9
10
11
12
jstat - class  pid:显示加载 class 的数量,及所占空间等信息。 
jstat -compiler pid:显示VM实时编译的数量等信息。 
jstat -gc pid:可以显示gc的信息,查看gc的次数,及时间。其中最后五项,分别是young gc的次数,young gc的时间,full gc的次数,full gc的时间,gc的总时间。 
jstat -gccapacity:可以显示,VM内存中三代(young,old,perm)对象的使用和占用大小,如:PGCMN显示的是最小perm的内存使用量,PGCMX显示的是perm的内存最大使用量,PGC是当前新生成的perm内存占用量,PC是但前perm内存占用量。其他的可以根据这个类推, OC是old内纯的占用量。 
jstat -gcnew pid: new 对象的信息。 
jstat -gcnewcapacity pid: new 对象的信息及其占用量。 
jstat -gcold pid:old对象的信息。 
jstat -gcoldcapacity pid:old对象的信息及其占用量。 
jstat -gcpermcapacity pid: perm对象的信息及其占用量。 
jstat -gcutil pid:统计gc信息统计。 
jstat -printcompilation pid:当前VM执行的信息。 
除了以上一个参数外,还可以同时加上 两个数字,如:jstat -printcompilation 3024  250  6 是每 250 毫秒打印一次,一共打印 6 次。

      这些参数中最常用的参数是gcutil,下面是该参数的输出介绍以及一个简单例子:  

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
S0  — Heap上的 Survivor space 0  区已使用空间的百分比 
S1  — Heap上的 Survivor space 1  区已使用空间的百分比 
E   — Heap上的 Eden space 区已使用空间的百分比 
O   — Heap上的 Old space 区已使用空间的百分比 
P   — Perm space 区已使用空间的百分比 
YGC — 从应用程序启动到采样时发生 Young GC 的次数 
YGCT– 从应用程序启动到采样时 Young GC 所用的时间(单位秒) 
FGC — 从应用程序启动到采样时发生 Full GC 的次数 
FGCT– 从应用程序启动到采样时 Full GC 所用的时间(单位秒) 
GCT — 从应用程序启动到采样时用于垃圾回收的总时间(单位秒) 
   
实例使用 1 : 
   
[root @localhost  bin]# jstat -gcutil 25444 
   
   S0     S1     E      O      P     YGC     YGCT    FGC    FGCT     GCT 
   
  11.63    0.00    56.46   66.92   98.49  162     0.248     6       0.331     0.579 

1.3、jinfo命令

      jinfo可以用来查看正在运行的Java应用程序的扩展参数,甚至在运行时修改部分参数,它的基本语法为:

1
jinfo  <option>  <pid>

  jinfo可以查看运行时参数:

1
2
jinfo -flag MaxTenuringThreshold 31518
-XX:MaxTenuringThreshold= 15

  jinfo还可以在运行时修改参数值:

1
2
3
4
5
> jinfo -flag PrintGCDetails 31518
-XX:-PrintGCDetails
> jinfo -flag +PrintGCDetails 31518
> jinfo -flag PrintGCDetails 31518
-XX:+PrintGCDetails

1.4、jmap命令

      jmap命令主要用于生成堆快照文件,它的使用方法如下:

1
2
3
> jmap -dump:format=b,file=heap.hprof 31531
Dumping heap to /Users/caojie/heap.hprof ...
Heap dump file created

  获得堆快照文件之后,我们可以使用多种工具对文件进行分析,例如jhat,visual vm等。

1.5、jhat命令

      使用jhat工具可以分析Java应用程序的堆快照文件,使用命令如下:

1
2
3
4
5
6
7
8
9
10
> jhat heap.hprof
Reading from heap.hprof...
Dump file created Tue Nov 11  06 : 02 : 05  CST 2014
Snapshot read, resolving...
Resolving 8781  objects...
Chasing references, expect 1  dots.
Eliminating duplicate references.
Snapshot resolved.
Started HTTP server on port 7000
Server is ready.

  jhat在分析完成之后,使用HTTP服务器展示其分析结果,在浏览器中访问http://127.0.0.1:7000/即可得到分析结果。

1.6、jstack命令

  jstack可用于导出Java应用程序的线程堆栈信息,语法为:

1
jstack -l <pid>

  jstack可以检测死锁,下例通过一个简单例子演示jstack检测死锁的功能。java代码如下:

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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
public  class  DeadLock extends  Thread {
     protected  Object myDirect;
     static  ReentrantLock south = new  ReentrantLock();
     static  ReentrantLock north = new  ReentrantLock();
 
     public  DeadLock(Object obj) {
         this .myDirect = obj;
         if  (myDirect == south) {
             this .setName( "south" );
         }
         if  (myDirect == north) {
             this .setName( "north" );
         }
     }
 
     @Override
     public  void  run() {
         if  (myDirect == south) {
             try  {
                 north.lockInterruptibly();
                 try  {
                     Thread.sleep( 500 );
                 } catch  (Exception e) {
                     e.printStackTrace();
                 }
                 south.lockInterruptibly();
                 System.out.println( "car to south has passed" );
             } catch  (InterruptedException e1) {
                 System.out.println( "car to south is killed" );
             } finally  {
                 if  (north.isHeldByCurrentThread())
                     north.unlock();
                 if  (south.isHeldByCurrentThread())
                     south.unlock();
             }
 
         }
         if  (myDirect == north) {
             try  {
                 south.lockInterruptibly();
                 try  {
                     Thread.sleep( 500 );
                 } catch  (Exception e) {
                     e.printStackTrace();
                 }
                 north.lockInterruptibly();
                 System.out.println( "car to north has passed" );
             } catch  (InterruptedException e1) {
                 System.out.println( "car to north is killed" );
             } finally  {
                 if  (north.isHeldByCurrentThread())
                     north.unlock();
                 if  (south.isHeldByCurrentThread())
                     south.unlock();
             }
 
         }
     }
 
     public  static  void  main(String[] args) throws  InterruptedException {
         DeadLock car2south = new  DeadLock(south);
         DeadLock car2north = new  DeadLock(north);
         car2south.start();
         car2north.start();
         Thread.sleep( 1000 );
     }
}

  使用jps命令查看进程号为32627,然后使用jstack -l 32637 > a.txt命令把堆栈信息打印到文件中,该文件内容如下:

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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
2014 - 11 - 11  21 : 33 : 12
Full thread dump Java HotSpot(TM) 64 -Bit Server VM ( 24.55 -b03 mixed mode):
 
"Attach Listener"  daemon prio= 5  tid= 0x00007f8d0c803000  nid= 0x3307  waiting on condition [ 0x0000000000000000 ]
    java.lang.Thread.State: RUNNABLE
 
    Locked ownable synchronizers:
     - None
 
"DestroyJavaVM"  prio= 5  tid= 0x00007f8d0b80b000  nid= 0x1903  waiting on condition [ 0x0000000000000000 ]
    java.lang.Thread.State: RUNNABLE
 
    Locked ownable synchronizers:
     - None
 
"north"  prio= 5  tid= 0x00007f8d0c075000  nid= 0x5103  waiting on condition [ 0x0000000115b06000 ]
    java.lang.Thread.State: WAITING (parking)
     at sun.misc.Unsafe.park(Native Method)
     - parking to wait for   < 0x00000007d55ab600 > (a java.util.concurrent.locks.ReentrantLock$NonfairSync)
     at java.util.concurrent.locks.LockSupport.park(LockSupport.java: 186 )
     at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java: 834 )
     at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireInterruptibly(AbstractQueuedSynchronizer.java: 894 )
     at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchronizer.java: 1221 )
     at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java: 340 )
     at DeadLock.run(DeadLock.java: 48 )
 
    Locked ownable synchronizers:
     - < 0x00000007d55ab5d0 > (a java.util.concurrent.locks.ReentrantLock$NonfairSync)
 
"south"  prio= 5  tid= 0x00007f8d0c074800  nid= 0x4f03  waiting on condition [ 0x0000000115a03000 ]
    java.lang.Thread.State: WAITING (parking)
     at sun.misc.Unsafe.park(Native Method)
     - parking to wait for   < 0x00000007d55ab5d0 > (a java.util.concurrent.locks.ReentrantLock$NonfairSync)
     at java.util.concurrent.locks.LockSupport.park(LockSupport.java: 186 )
     at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java: 834 )
     at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireInterruptibly(AbstractQueuedSynchronizer.java: 894 )
     at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchronizer.java: 1221 )
     at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java: 340 )
     at DeadLock.run(DeadLock.java: 28 )
 
    Locked ownable synchronizers:
     - < 0x00000007d55ab600 > (a java.util.concurrent.locks.ReentrantLock$NonfairSync)
 
"Service Thread"  daemon prio= 5  tid= 0x00007f8d0c025800  nid= 0x4b03  runnable [ 0x0000000000000000 ]
    java.lang.Thread.State: RUNNABLE
 
    Locked ownable synchronizers:
     - None
 
"C2 CompilerThread1"  daemon prio= 5  tid= 0x00007f8d0c025000  nid= 0x4903  waiting on condition [ 0x0000000000000000 ]
    java.lang.Thread.State: RUNNABLE
 
    Locked ownable synchronizers:
     - None
 
"C2 CompilerThread0"  daemon prio= 5  tid= 0x00007f8d0d01b000  nid= 0x4703  waiting on condition [ 0x0000000000000000 ]
    java.lang.Thread.State: RUNNABLE
 
    Locked ownable synchronizers:
     - None
 
"Signal Dispatcher"  daemon prio= 5  tid= 0x00007f8d0c022000  nid= 0x4503  runnable [ 0x0000000000000000 ]
    java.lang.Thread.State: RUNNABLE
 
    Locked ownable synchronizers:
     - None
 
"Finalizer"  daemon prio= 5  tid= 0x00007f8d0d004000  nid= 0x3103  in Object.wait() [ 0x000000011526a000 ]
    java.lang.Thread.State: WAITING (on object monitor)
     at java.lang.Object.wait(Native Method)
     - waiting on < 0x00000007d5505568 > (a java.lang.ref.ReferenceQueue$Lock)
     at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java: 135 )
     - locked < 0x00000007d5505568 > (a java.lang.ref.ReferenceQueue$Lock)
     at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java: 151 )
     at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java: 189 )
 
    Locked ownable synchronizers:
     - None
 
"Reference Handler"  daemon prio= 5  tid= 0x00007f8d0d001800  nid= 0x2f03  in Object.wait() [ 0x0000000115167000 ]
    java.lang.Thread.State: WAITING (on object monitor)
     at java.lang.Object.wait(Native Method)
     - waiting on < 0x00000007d55050f0 > (a java.lang.ref.Reference$Lock)
     at java.lang.Object.wait(Object.java: 503 )
     at java.lang.ref.Reference$ReferenceHandler.run(Reference.java: 133 )
     - locked < 0x00000007d55050f0 > (a java.lang.ref.Reference$Lock)
 
    Locked ownable synchronizers:
     - None
 
"VM Thread"  prio= 5  tid= 0x00007f8d0b83b000  nid= 0x2d03  runnable
 
"GC task thread#0 (ParallelGC)"  prio= 5  tid= 0x00007f8d0b818000  nid= 0x2503  runnable
 
"GC task thread#1 (ParallelGC)"  prio= 5  tid= 0x00007f8d0b819000  nid= 0x2703  runnable
 
"GC task thread#2 (ParallelGC)"  prio= 5  tid= 0x00007f8d0d000000  nid= 0x2903  runnable
 
"GC task thread#3 (ParallelGC)"  prio= 5  tid= 0x00007f8d0d001000  nid= 0x2b03  runnable
 
"VM Periodic Task Thread"  prio= 5  tid= 0x00007f8d0c02e800  nid= 0x4d03  waiting on condition
 
JNI global references: 109
 
 
Found one Java-level deadlock:
=============================
"north" :
   waiting for  ownable synchronizer 0x00000007d55ab600 , (a java.util.concurrent.locks.ReentrantLock$NonfairSync),
   which is held by "south"
"south" :
   waiting for  ownable synchronizer 0x00000007d55ab5d0 , (a java.util.concurrent.locks.ReentrantLock$NonfairSync),
   which is held by "north"
 
Java stack information for  the threads listed above:
===================================================
"north" :
     at sun.misc.Unsafe.park(Native Method)
     - parking to wait for   < 0x00000007d55ab600 > (a java.util.concurrent.locks.ReentrantLock$NonfairSync)
     at java.util.concurrent.locks.LockSupport.park(LockSupport.java: 186 )
     at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java: 834 )
     at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireInterruptibly(AbstractQueuedSynchronizer.java: 894 )
     at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchronizer.java: 1221 )
     at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java: 340 )
     at DeadLock.run(DeadLock.java: 48 )
"south" :
     at sun.misc.Unsafe.park(Native Method)
     - parking to wait for   < 0x00000007d55ab5d0 > (a java.util.concurrent.locks.ReentrantLock$NonfairSync)
     at java.util.concurrent.locks.LockSupport.park(LockSupport.java: 186 )
     at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java: 834 )
     at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireInterruptibly(AbstractQueuedSynchronizer.java: 894 )
     at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchronizer.java: 1221 )
     at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java: 340 )
     at DeadLock.run(DeadLock.java: 28 )
 
Found 1  deadlock.

  从这个输出可以知道:

      1、在输出的最后一段,有明确的"Found one Java-level deadlock"输出,所以通过jstack命令我们可以检测死锁;

      2、输出中包含了所有线程,除了我们的north,sorth线程外,还有"Attach Listener", "C2 CompilerThread0", "C2 CompilerThread1"等等;

      3、每个线程下面都会输出当前状态,以及这个线程当前持有锁以及等待锁,当持有与等待造成循环等待时,将导致死锁。

1.7、jstatd命令

       jstatd命令是一个RMI服务器程序,它的作用相当于代理服务器,建立本地计算机与远程监控工具的通信,jstatd服务器能够将本机的Java应用程序信息传递到远程计算机,由于需要多台计算机做演示,此处略。

1.8、hprof工具  

       hprof工具可以用于监控Java应用程序在运行时的CPU信息和堆信息,关于hprof的官方文档如下:https://docs.oracle.com/javase/7/docs/technotes/samples/hprof.html

 

2、Visual VM工具                                                                       

      Visual VM是一个功能强大的多合一故障诊断和性能监控的可视化工具,它集成了多种性能统计工具的功能,使用Visual VM可以替代jstat、jmap、jhat、jstack等工具。在命令行输入jvisualvm即可启动visualvm。

      打开Visual VM之后,左边导航栏会显示出当前机器所有Java进程:

      点击你想监控的程序即可对该程序进行监控,Visual VM的性能监控页一共有以下几个tab页:

      概述页会显示程序的基本使用情况,比如,进程ID,系统属性,启动参数等。

      通过监视页面,可以监视应用程序的CPU、堆、永久区、类加载器和线程数的整体情况,通过页面上的Perform GC和Heap Dump按钮还可以手动执行Full GC和生成堆快照。

      线程页面会提供详细的线程信息,单击Thread Dump按钮可以导出当前所有线程的堆栈信息,如果Visual VM在当前线程中找到死锁,则会以十分显眼的方式在Threads页面给予提示。

      抽样器可以对CPU和内存两个性能进行抽样,用于实时地监控程序。CPU采样器可以将CPU占用时间定位到方法,内存采样器可以查看当前程序的堆信息。下面是一个频繁调用的Java程序,我们会对改程序进行采样:

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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
public  class  MethodTime {
     static  java.util.Random r= new  java.util.Random();
     static  Map<String,String> map= null ;
     static {
         map= new  HashMap<String,String>();
         map.put( "1" , "Java" );
         map.put( "2" , "C++" );
         map.put( "3" , "Delphi" );
         map.put( "4" , "C" );
         map.put( "5" , "Phython" );
     }
     public  String getNameById(String id){
         try  {
             Thread.sleep( 1 );
         } catch  (InterruptedException e) {
             e.printStackTrace();
         }
         return  map.get(id);
     }
     
     public  List<String> getNamesByIds(String ids){
         List<String> re= new  ArrayList<String>();
         String[] strs=ids.split( "," );
         for (String id:strs){
             re.add(getNameById(id));
         }
         return  re;
     }
     
     public  List<String> getNamesByIdsBad(String ids){
         List<String> re= new  ArrayList<String>();
         String[] strs=ids.split( "," );
         for (String id:strs){
             //A bad code
             getNameById(id);
             re.add(getNameById(id));
         }
         return  re;
     }
     
     public  class  NamesByIdsThread implements  Runnable{
         @Override
         public  void  run() {
             try {
                 while ( true ){
                     int  c=r.nextInt( 4 );
                     String ids= "" ;
                     for ( int  i= 0 ;i<c;i++)
                         ids=Integer.toString((r.nextInt( 4 )+ 1 ))+ "," ;
                     getNamesByIds(ids);
                 }
             } catch (Exception e){
             }
         }
     }
     
     public  class  NamesByIdsBadThread implements  Runnable{
         @Override
         public  void  run() {
             try {
                 while ( true ){
                     int  c=r.nextInt( 4 );
                     String ids= "" ;
                     for ( int  i= 0 ;i<c;i++)
                         ids=Integer.toString((r.nextInt( 4 )+ 1 ))+ "," ;
                     getNamesByIdsBad(ids);
                 }
             } catch (Exception e){
             }
         }
     }
     
     public  static  void  main(String args[]){
         MethodTime instance= new  MethodTime();
         new  Thread(instance. new  NamesByIdsThread()).start();
         new  Thread(instance. new  NamesByIdsBadThread()).start();
     }
}

  通过Visual VM的采样功能,可以找到改程序中占用CPU时间最长的方法:

      默认Visual VM不统计内置对象的函数调用,比如java.*包中的类,如果要统计这些内置对象,单机右上角的设置进行调配。Visual VM虽然可以统计方法的调用时间,但是无法给出方法调用堆栈,Jprofile不仅可以给出方法调用时间,还可以给出方法调用堆栈,较Visual VM更强大。

      右击左导航的应用程序,会出现以下菜单:

      单机应用程序快照,可以分析当前应用程序的快照,单击堆Dump能够对当前的堆信息进行分析。Visual VM的更多使用方法,可以查看Oracle的官方文档https://docs.oracle.com/javase/7/docs/technotes/guides/visualvm/index.html

BTrace插件

      BTrace是一款功能强大的性能检测工具,它可以在不停机的情况下,通过字节码注入,动态监控系统的运行情况,它可以跟踪指定的方法调用、构造函数调用和系统内存等信息,本部分打算举一个例子,讲解一下BTrace的使用。要在Visual VM中使用Btrace,首先需要安装Btrace插件,点击工具->插件即可在线安装,安装后右键应用程序,就会出现如下选项:

      点击Trace application,即可进入BTrace插件界面。使用BTrace可以监控指定函数的耗时,以下脚本通过正则表达式,监控所有类的getNameById方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import  com.sun.btrace.annotations.*;
import  static  com.sun.btrace.BTraceUtils.*;
 
@BTrace
public  class  TracingScript {
     @TLS
     private  static  long  startTime = 0 ;
     
     @OnMethod (clazz= "/.+/" , method= "/getNameById/" ) //监控任意类的getNameById方法
     public  static  void  startMethod() {
         startTime=timeMillis();
     }
     
     @OnMethod (clazz= "/.+/" , method= "/getNameById/" ,
     location= @Location (Kind.RETURN)) //方法返回时触发
     public  static  void  endMethod() {
         print(strcat(strcat(name(probeClass()), "." ), probeMethod()));
         print( " [" );
         print(strcat( "Time taken : " , str(timeMillis() - startTime)));
         println( "]" );
     }
}

  点击运行,部分输出如下:

1
2
3
4
MethodTime.getNameById [Time taken : 5 ]
MethodTime.getNameById [Time taken : 4 ]
MethodTime.getNameById [Time taken : 7 ]
MethodTime.getNameById [Time taken : 7 ]

  BTrace除了可以监控函数耗时外,还可以指定程序运行到某一行代码触发某一行为,定时触发行为,监控函数参数等等。

3、MAT内存分析工具                                                                    

     MAT是一款功能强大的Java堆内存分析器,可以用于查找内存泄露以及查看内存消耗情况,MAT的官方文档如下:http://help.eclipse.org/luna/index.jsp?topic=/org.eclipse.mat.ui.help/welcome.html

    在MAT中有浅堆和深堆的概念,浅堆是指一个对象结构所占用的内存大小,深堆是指一个对象被GC回收后可以真正释放的内存大小。

    通过MAT,可以列出所有垃圾回收的根对象,Java系统的根对象可能是以下类:系统类,线程,Java局部变量,本地栈等等。在MAT中还可以很清楚的看到根对象到当前对象的引用关系链。

    MAT还可以自动检测内存泄露,单击菜单上的Leak Suspects命令,MAT会自动生成一份报告,这份报告罗列了系统内可能存在内存泄露的问题点。

    在MAT中,还可以自动查找并显示消耗内存最多的几个对象,这些消耗大量内存的大对象往往是解决系统性能问题的关键所在。

    具体例子,略,网速太慢,至今还未下好。。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值