jps
显示当前所有java进程pid
的命令,我们可以通过这个命令来查看到底启动了几个java
进程(因为每个java程序都会独占一个java虚拟机实例),不过jps
有个缺点是只显示当前用户的进程id,要显示其它用户的还是只能用linux的ps命令。
常用
jps -l:输出应用程序main.class的完整package名或者应用程序jar文件完整路径名
jps -v:输出传递给JVM的参数
jps失效
我们在定位问题过程会遇到这样一种情况,用jps
查看不到进程id
,用ps -ef|grep java
却能看到启动的java进程。要解释这种现象,先来了解下jps的实现机制:
java程序启动后,会在目录/tmp/hsperfdata_{userName}/
下生成几个文件,文件名就是java
进程的pid
,因此jps列出进程id就是把这个目录下的文件名列一下而已,至于系统参数,则是读取文件中的内容。
如果jps命令失效,可以使用linux的命令获取java进程id
jstack
主要用于生成指定进程当前时刻的线程快照,线程快照是当前java虚拟机每一条线程正在执行的方法堆栈的集合,生成线程快照的主要目的是用于定位线程出现长时间停顿的原因,如线程间死锁、死循环、请求外部资源导致长时间等待。
用法
常用
jstack <pid> 输出结果
2021-07-22 15:02:19
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.291-b10 mixed mode):
"parallel-3" #26 daemon prio=5 os_prio=0 tid=0x000001c3f6788800 nid=0x4cb8 waiting on condition [0x000000bc1a6fe000]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x00000000f2ca0c48> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
at java.util.concurrent.locks.LockSupport.park(Unknown Source)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(Unknown Source)
at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(Unknown Source)
at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(Unknown Source)
at java.util.concurrent.ThreadPoolExecutor.getTask(Unknown Source)
at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
at java.lang.Thread.run(Unknown Source)
"elastic-evictor-1" #24 daemon prio=5 os_prio=0 tid=0x000001c3f8303800 nid=0x4470 waiting on condition [0x000000bc1c7ff000]
java.lang.Thread.State: TIMED_WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x00000000edee01f8> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
at java.util.concurrent.locks.LockSupport.parkNanos(Unknown Source)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awaitNanos(Unknown Source)
at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(Unknown Source)
at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(Unknown Source)
at java.util.concurrent.ThreadPoolExecutor.getTask(Unknown Source)
at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
at java.lang.Thread.run(Unknown Source)
"parallel-2" #23 daemon prio=5 os_prio=0 tid=0x000001c3f9d65800 nid=0x3f74 waiting on condition [0x000000bc1c4ff000]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x00000000f2ca0de8> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
at java.util.concurrent.locks.LockSupport.park(Unknown Source)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(Unknown Source)
at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(Unknown Source)
at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(Unknown Source)
at java.util.concurrent.ThreadPoolExecutor.getTask(Unknown Source)
at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
at java.lang.Thread.run(Unknown Source)
"pool-2-thread-1" #22 prio=5 os_prio=0 tid=0x000001c3f94db000 nid=0x1ba4 waiting on condition [0x000000bc1c3ff000]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x00000000f2c92ac0> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
at java.util.concurrent.locks.LockSupport.park(Unknown Source)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(Unknown Source)
at java.util.concurrent.LinkedBlockingQueue.take(Unknown Source)
at java.util.concurrent.ThreadPoolExecutor.getTask(Unknown Source)
at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
at java.lang.Thread.run(Unknown Source)
"Reconciler-1" #21 daemon prio=5 os_prio=0 tid=0x000001c3f94cc800 nid=0x3c20 waiting on condition [0x000000bc1c2fe000]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x00000000c00af268> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
at java.util.concurrent.locks.LockSupport.park(Unknown Source)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(Unknown Source)
at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(Unknown Source)
at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(Unknown Source)
at java.util.concurrent.ThreadPoolExecutor.getTask(Unknown Source)
at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
at java.lang.Thread.run(Unknown Source)
"parallel-1" #20 daemon prio=5 os_prio=0 tid=0x000001c3f94aa800 nid=0x51e0 waiting on condition [0x000000bc1c1fe000]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x00000000f2ca11d8> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
at java.util.concurrent.locks.LockSupport.park(Unknown Source)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(Unknown Source)
at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(Unknown Source)
at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(Unknown Source)
at java.util.concurrent.ThreadPoolExecutor.getTask(Unknown Source)
at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
at java.lang.Thread.run(Unknown Source)
"pool-5-thread-1" #19 prio=5 os_prio=0 tid=0x000001c3f9473000 nid=0x4d0c waiting on condition [0x000000bc1c0fe000]
java.lang.Thread.State: TIMED_WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x00000000f2d7b7f8> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
at java.util.concurrent.locks.LockSupport.parkNanos(Unknown Source)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awaitNanos(Unknown Source)
at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(Unknown Source)
at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(Unknown Source)
at java.util.concurrent.ThreadPoolExecutor.getTask(Unknown Source)
at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
at java.lang.Thread.run(Unknown Source)
"pool-4-thread-1" #18 prio=5 os_prio=0 tid=0x000001c3f943b000 nid=0x5378 runnable [0x000000bc1bffe000]
java.lang.Thread.State: RUNNABLE
at java.io.FileInputStream.readBytes(Native Method)
at java.io.FileInputStream.read(Unknown Source)
at java.io.BufferedInputStream.fill(Unknown Source)
at java.io.BufferedInputStream.read(Unknown Source)
- locked <0x00000000c005af80> (a java.io.BufferedInputStream)
at org.eclipse.lsp4j.jsonrpc.json.StreamMessageProducer.listen(StreamMessageProducer.java:79)
at org.eclipse.lsp4j.jsonrpc.json.ConcurrentMessageProcessor.run(ConcurrentMessageProcessor.java:113)
at java.util.concurrent.Executors$RunnableAdapter.call(Unknown Source)
at java.util.concurrent.FutureTask.run(Unknown Source)
at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
at java.lang.Thread.run(Unknown Source)
"DestroyJavaVM" #17 prio=5 os_prio=0 tid=0x000001c3e1e6e000 nid=0x4c7c waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"LanguageServerApp-lifecycle" #16 prio=5 os_prio=0 tid=0x000001c3f7ba0000 nid=0x5320 waiting on condition [0x000000bc1befe000]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x00000000f2d7c200> (a java.util.concurrent.FutureTask)
at java.util.concurrent.locks.LockSupport.park(Unknown Source)
at java.util.concurrent.FutureTask.awaitDone(Unknown Source)
at java.util.concurrent.FutureTask.get(Unknown Source)
at org.eclipse.lsp4j.jsonrpc.json.ConcurrentMessageProcessor$1.get(ConcurrentMessageProcessor.java:54)
at org.eclipse.lsp4j.jsonrpc.json.ConcurrentMessageProcessor$1.get(ConcurrentMessageProcessor.java:50)
at org.springframework.ide.vscode.commons.languageserver.LanguageServerRunner.startAsClient(LanguageServerRunner.java:138)
at org.springframework.ide.vscode.commons.languageserver.LanguageServerRunner.start(LanguageServerRunner.java:93)
at org.springframework.ide.vscode.commons.languageserver.LanguageServerRunner.lambda$run$0(LanguageServerRunner.java:63)
at org.springframework.ide.vscode.commons.languageserver.LanguageServerRunner$$Lambda$305/1313799195.run(Unknown Source)
at java.lang.Thread.run(Unknown Source)
"Simple-Language-Server main thread" #14 daemon prio=5 os_prio=0 tid=0x000001c3f9305800 nid=0x3520 waiting on condition [0x000000bc1bdfe000]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x00000000c0365c98> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
at java.util.concurrent.locks.LockSupport.park(Unknown Source)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(Unknown Source)
at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(Unknown Source)
at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(Unknown Source)
at java.util.concurrent.ThreadPoolExecutor.getTask(Unknown Source)
at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
at java.lang.Thread.run(Unknown Source)
"Service Thread" #10 daemon prio=9 os_prio=0 tid=0x000001c3f7a1e800 nid=0x1854 runnable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"C1 CompilerThread3" #9 daemon prio=9 os_prio=2 tid=0x000001c3f6784000 nid=0x3544 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"C2 CompilerThread2" #8 daemon prio=9 os_prio=2 tid=0x000001c3f6777000 nid=0x4ce4 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"C2 CompilerThread1" #7 daemon prio=9 os_prio=2 tid=0x000001c3f6775000 nid=0x1bec waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"C2 CompilerThread0" #6 daemon prio=9 os_prio=2 tid=0x000001c3f6772800 nid=0x18a0 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"Attach Listener" #5 daemon prio=5 os_prio=2 tid=0x000001c3f676e000 nid=0x6c waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"Signal Dispatcher" #4 daemon prio=9 os_prio=2 tid=0x000001c3f676d800 nid=0x52bc runnable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"Finalizer" #3 daemon prio=8 os_prio=1 tid=0x000001c3f66f3800 nid=0x4864 in Object.wait() [0x000000bc1b4fe000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x00000000c0029d58> (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(Unknown Source)
- locked <0x00000000c0029d58> (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(Unknown Source)
at java.lang.ref.Finalizer$FinalizerThread.run(Unknown Source)
"Reference Handler" #2 daemon prio=10 os_prio=2 tid=0x000001c3f66cb800 nid=0x3c6c in Object.wait() [0x000000bc1b3fe000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x00000000c0031778> (a java.lang.ref.Reference$Lock)
at java.lang.Object.wait(Unknown Source)
at java.lang.ref.Reference.tryHandlePending(Unknown Source)
- locked <0x00000000c0031778> (a java.lang.ref.Reference$Lock)
at java.lang.ref.Reference$ReferenceHandler.run(Unknown Source)
"VM Thread" os_prio=2 tid=0x000001c3f66c0800 nid=0x48e0 runnable
"GC task thread#0 (ParallelGC)" os_prio=0 tid=0x000001c3e1e84000 nid=0x30dc runnable
"GC task thread#1 (ParallelGC)" os_prio=0 tid=0x000001c3e1e85800 nid=0x50a4 runnable
"GC task thread#2 (ParallelGC)" os_prio=0 tid=0x000001c3e1e86800 nid=0x41a8 runnable
"GC task thread#3 (ParallelGC)" os_prio=0 tid=0x000001c3e1e89000 nid=0x1310 runnable
"GC task thread#4 (ParallelGC)" os_prio=0 tid=0x000001c3e1e8a000 nid=0x53d0 runnable
"GC task thread#5 (ParallelGC)" os_prio=0 tid=0x000001c3e1e8b000 nid=0x50dc runnable
"GC task thread#6 (ParallelGC)" os_prio=0 tid=0x000001c3e1e8e000 nid=0x2e84 runnable
"GC task thread#7 (ParallelGC)" os_prio=0 tid=0x000001c3e1e8f000 nid=0x2c14 runnable
"VM Periodic Task Thread" os_prio=2 tid=0x000001c3f7a46000 nid=0x1d74 waiting on condition
JNI global references: 1087
jmap
主要用于打印指定java进程的共享对象内存映射或堆内存细节。
堆Dump是反映堆使用情况的内存镜像,期中主要包括系统信息、虚拟机属性、完整的线程Dump、所有类和对象的状态等。一般在内存不足、GC异常等情况下,我们回去怀疑内存泄露,这个时候就会去打印堆Dump。
用法
常用
jmap <pid>:输出结果
打印的信息分别为:共享对象的起始地址、映射大小、共享对象路径的全称。
jmap -heap :查看堆使用情况,输出结果
Attaching to process ID 6892, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.291-b10
using thread-local object allocation.
Parallel GC with 13 thread(s)
Heap Configuration: //堆内存初始化配置
MinHeapFreeRatio = 0 //堆最小空闲比率
MaxHeapFreeRatio = 100 //堆最大空闲比率
MaxHeapSize = 16823353344 (16044.0MB) //堆最大大小
NewSize = 350748672 (334.5MB) //堆新生代默认大小
MaxNewSize = 5607784448 (5348.0MB) //堆新生代最大大小
OldSize = 702021632 (669.5MB) //老年代大小
NewRatio = 2 //新生代和老年代的大小比率
SurvivorRatio = 8 //新生代中Eden区与Survivor区的大小比率
MetaspaceSize = 21807104 (20.796875MB) //元数据区的初始大小
CompressedClassSpaceSize = 1073741824 (1024.0MB) //默认1G,这个参数主要是设置Klass Metaspace的大小,不过这个参数设置了也不一定起作用,前提是能开启压缩指针,假如-Xmx超过了32G,压缩指针是开启不来的。如果有Klass Metaspace,那这块内存是和Heap连着的。
MaxMetaspaceSize = 17592186044415 MB //元数据区的最大大小
G1HeapRegionSize = 0 (0.0MB)
Heap Usage: //堆内存使用情况
PS Young Generation
Eden Space: //Eden区的内存分布
capacity = 317194240 (302.5MB)
used = 158127136 (150.80178833007812MB)
free = 159067104 (151.69821166992188MB)
49.85183085291839% used
From Space: //其中一个Survivor区的内存分布
capacity = 11534336 (11.0MB)
used = 0 (0.0MB)
free = 11534336 (11.0MB)
0.0% used
To Space: //另一个Survivor区的内存分布
capacity = 43515904 (41.5MB)
used = 0 (0.0MB)
free = 43515904 (41.5MB)
0.0% used
PS Old Generation //老年代内存分布
capacity = 749207552 (714.5MB)
used = 14988656 (14.294296264648438MB)
free = 734218896 (700.2057037353516MB)
2.0006012966617828% used
16736 interned Strings occupying 1486352 bytes.
jmap -histo <pid>:查看堆中对象数量和大小,输出结果
打印的信息分别是:序列号、对象的数量、这些对象的内存占用大小、这些对象所属的类的全限定名。如果是内部类,类名的开头会加上*,如果加上live子参数的话,如,这个命令会触发一次FULL GC,只统计存活对象.
:将内存使用的详细情况输出到文件 然后使用jhat命令查看该文件:,在浏览器中访问http://localhost:4000/
总结:该命令适用的场景是程序内存不足或者GC频繁,这时候很可能是内存泄露。通过用以上命令查看堆使用情况、大量对象被持续引用等情况。
jstat
主要是对java应用程序的资源和性能进行实时的命令行监控,包括了对heap size
和垃圾回收状况的监控。
用法
常用
- S0C:年轻代第一个
Survivor
的容量(字节) - S1C:年轻代第二个
Survivor
的容量(字节) - S0U:年轻代第一个
Survivor
已使用的容量(字节) - S1U:年轻代第二个
Survivor
已使用的容量(字节) - EC:年轻代中Eden区的空间(字节)
- EU:年轻代中Eden区已使用的空间(字节)
- OC:老年代的容量(字节)
- OU:老年代已使用的容量(字节)
- MC:Metaspace的容量大小(字节)
- MU:Metaspace已使用的容量大小(字节)
- CCSC:压缩类空间的容量大小(字节)
- CCSU:压缩类空间已使用的容量大小(字节)
- YGC:从应用程序启动到采样时年轻代中GC的次数
- YGCT:从应用程序启动到采样时年轻代中GC所使用的时间(单位:S)
- FGC:从应用程序启动到采样时FULL GC的次数
- FGCT:从应用程序启动到采样时FULL GC所使用的时间(单位:S)
- GC:垃圾回收消耗总时间
jstat -gcutil <pid> 2000 10
- S0:年轻代第一个
Survivor
已使用的占当前容量百分比 - S1:年轻代第二个
Survivor
已使用的占当前容量百分比 - E:年轻代中
Eden
区已使用的占当前容量百分比 - O:老年代中已使用的占当前容量百分比
- M:
Metaspace
已使用的占当前容量百分比(这里有个说法就是说这个Metaspace
我们初始设置的很大之后,看到的百分比仍然很高,是因为Metaspace
设置的是最大能申请的空间,但是并不会直接申请这么大,这个不太确定) - CCS:压缩类空间已使用的占比当前容量百分比
jhat
主要用来解析java
堆dump
并启动一个web服务器,然后就可以在浏览器中查看堆的dump
文件了。
生成dump文件的方法前面已经介绍过了,这边主要介绍如何解析java
堆转储文件,并启动一个web server
。
jhatheapdump
访问结果
这个命令将
heapdump
文件转换成html
格式,并且启动一个http
服务,默认端口为7000。
如果端口冲突,可以使用以下命令指定端口:
jinfo
jinfo可以用来查看正在运行的java运用程序的扩展参数,甚至支持在运行时动态地更改部分参数。
用法
jinfo -<option> <pid>,其中option
可以为一下信息:
- flag<name>:打印指定java虚拟机的参数值
- flag[+|-]<name>:设置或取消指定java虚拟机参数的布尔值
- flag <name>=<value>:设置指定java虚拟机的参数的值
使用示例:
jinfo -flag MaxTenuringThreshold <pid>:命令显示了新生代对象晋升到老年代对象的最大年龄。在运行程序时并没有指定这个参数,但是通过jinfo
,可以查看这个参数的当前的值。
jinfo -flag PrintGCDetails <pid>:命令显示是否打印gc详细信息:
jinfo -flag +PrintGCDetails <pid>:命令在应用程序运行时动态打开打印详细gc信息开关:
注意事项:jinfo
虽然可以在java
程序运行时动态地修改虚拟机参数,但并不是所有的参数都支持动态修改。
jcmd
在JDK1.7之后,新增了一个命令行工具jcmd
。它是一个多功能工具,可以用例导出堆,查看java
进程,导出线程信息,执行GC
等。jcmd
拥有jmap的大部分功能,Oracle官方建议使用jcmd
代替jmap
。
jcmd -l:命令列出当前运行的所有虚拟机,示例:
针对每个虚拟机,可以使用help命令列出该虚拟机支持的所有命令,示例:
子命令含义:
- JFR.stop
- JFR.start
- JFR.dump
- JFR.check
- VM.native_memory
- VM.check_commercial_features
- VM.unlock_commercial_features
- ManagementAgent.stop
- ManagementAgent.start_local
- ManagementAgent.start
- VM.classloader_stats
- GC.rotate_log
- Thread.print 打印线程栈信息
- GC.class_stats
- GC.class_histogram 查看系统中类统计信息
- GC.heap_dump 导出堆信息,与jmap -dump功能一样
- GC.finalizer_info 触发finalize()
- GC.heap_info
- GC.run_finalization
- GC.run 触发gc()
- VM.uptime VM启动时间
- VM.dynlibs
- VM.flags 获取JVM启动参数
- VM.system_properties 获取系统Properties
- VM.command_line 启动时命令行指定的参数
- VM.version
- help
示例:
可视化监控工具(JConsole、JVisualVM)
集上面之大成,并提供了可视化的界面;还可以监控远程java
服务;支持监控JVisualVM
。JVisualVM
比JConsole
更强大:支持对CPU、内存运行进行采样、配置。推荐使用JVisualVM
。