
在深入工具细节前,我们先明确JVM的核心内存模型和线程模型,这是理解工具工作原理的基础。同时,先搞清楚这五款工具的核心定位,避免使用时混淆场景。
JVM内存分为堆内存、方法区、程序计数器、虚拟机栈、本地方法栈。其中:
工具 | 核心功能 | 适用场景 |
|---|---|---|
Jstat | JVM统计信息监控,实时采集内存、GC、类加载数据 | 实时监控GC状态、判断内存泄漏趋势、类加载效率分析 |
Jinfo | 查看/修改JVM配置参数 | 验证JVM参数是否生效、动态修改部分参数(无需重启) |
Jmap | 生成JVM内存快照(dump文件)、查看内存使用详情 | 分析对象分布、定位内存泄漏、获取堆内存统计 |
Jhat | 分析Jmap生成的dump文件,提供Web可视化界面 | 离线分析堆快照、定位内存泄漏根源(已被JVisualVM替代,但仍需掌握) |
Jstack | 生成线程快照,查看线程状态、调用栈 | 定位线程死锁、线程阻塞、CPU 100%问题 |
所有工具使用前,都需要获取目标Java进程的PID,常用方式有3种:
jps:JDK自带进程查看工具,直接显示Java进程PID和主类名
示例:jps -l(-l显示完整主类名,若为Jar包则显示Jar路径)
[root@node1 ~]# jps -l
12345 com.jam.demo.Application # PID为12345,主类为com.jam.demo.Application
67890 org.apache.catalina.startup.Bootstrap
ps:Linux系统通用进程查看命令
示例:ps -ef | grep java(过滤出所有Java进程)
第三方工具:如VisualVM、JConsole连接后直接查看PID
后续所有工具示例中,均以12345作为目标进程PID进行演示。
Jstat(JVM Statistics Monitoring Tool)是JDK最常用的实时监控工具,能够持续采集JVM的内存使用、GC执行、类加载等统计信息,支持指定采样频率和次数,适合实时观察JVM运行状态。
Jstat通过连接目标JVM进程,读取JVM内部的统计数据(如堆内存各区域大小、GC执行次数/时间、类加载数量等),并以指定格式输出。其底层依赖JVM的Attach API,无需在JVM启动时预先配置,随时可以 attach 到运行中的进程。
jstat [ options vmid [ interval [ s|ms ] [ count ] ] ]
[protocol:]host[:port]);Jstat的Options参数按功能可分为3类:类加载监控、GC监控、编译监控,最常用的是GC监控相关参数。
输出类加载、卸载的统计信息,包括已加载类数量、大小、卸载数量等。 示例:jstat -class 12345 1000 5(每1秒采样1次,共采样5次)
Loaded Bytes Unloaded Bytes Time
1256 2560.3 0 0.0 0.89
1256 2560.3 0 0.0 0.89
1256 2560.3 0 0.0 0.89
1256 2560.3 0 0.0 0.89
1256 2560.3 0 0.0 0.89
字段说明:
适用场景:判断是否存在类加载泄漏(如频繁加载类但不卸载,导致元空间溢出)。
GC监控是Jstat最核心的功能,不同参数侧重点不同:
-gc:显示堆内存各区域的GC统计信息(绝对大小);-gcutil:显示堆内存各区域的使用率(百分比,最常用);-gccapacity:显示堆内存各区域的容量大小(最大/最小/当前);-gcnew:显示年轻代GC统计信息;-gcold:显示老年代GC统计信息。命令:jstat -gcutil 12345 2000(每2秒采样1次,持续采样)
S0 S1 E O M CCS YGC YGCT FGC FGCT GCT
0.00 50.23 89.45 65.78 92.31 88.67 125 6.789 8 15.678 22.467
0.00 50.23 92.10 65.78 92.31 88.67 125 6.789 8 15.678 22.467
0.00 0.00 10.23 66.12 92.31 88.67 126 6.890 8 15.678 22.568
字段说明(核心字段必须牢记):
关键分析点:
命令:jstat -gcnew 12345 1000 3
S0C S1C S0U S1U TT MTT DSS EC EU YGC YGCT
1024.0 1024.0 0.0 514.4 15 15 512.0 8192.0 7330.5 126 6.890
1024.0 1024.0 0.0 514.4 15 15 512.0 8192.0 7560.2 126 6.890
1024.0 1024.0 520.1 0.0 15 15 512.0 8192.0 830.1 127 6.992
字段说明:
适用场景:分析年轻代GC的触发频率、Survivor区的对象流转情况,判断年轻代大小是否合理。
-compiler:显示JIT编译器的统计信息(如编译方法数、失败数);-printcompilation:显示正在编译的方法信息。示例:jstat -compiler 12345
Compiled Failed Invalid Time FailedType FailedMethod
1568 0 0 3.45 0
字段说明:
适用场景:排查JIT编译相关问题(如编译失败导致的性能下降)。
问题现象:线上系统响应缓慢,JPS查看进程正常,但CPU使用率持续偏高(30%-40%)。排查步骤:
jstat -gcutil 12345 1000监控GC状态: 输出显示:YGC每秒2-3次,YGCT累计快速增加,Eden区使用率每秒从0%涨到90%以上,触发Young GC。jstat -gccapacity 12345查看年轻代容量: 输出显示:Eden区容量仅为4MB,Survivor区各1MB,年轻代总容量6MB。-Xmn256m),重启后观察,YGC频率降至每分钟1-2次,CPU使用率恢复正常。Jinfo(JVM Configuration Info)的核心功能是查看和修改JVM的配置参数,包括启动时指定的参数、默认参数,以及部分支持动态修改的参数(无需重启JVM)。对于线上系统,动态修改参数可以避免重启带来的服务中断,非常实用。
Jinfo通过Attach API连接目标JVM进程,读取JVM的参数配置信息(存储在JVM的内存数据结构中),同时支持对部分标记为“可动态修改”的参数进行更新,修改后立即生效。
jinfo [ option ] vmid
命令:jinfo -flags 12345输出示例(关键部分):
Attaching to process ID 12345, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 17.0.8+7-LTS
Non-default VM flags: -XX:CICompilerCount=4 -XX:InitialHeapSize=536870912 -XX:MaxHeapSize=8589934592 -XX:NewSize=178257920 -XX:OldSize=358612992 -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseG1GC
Command line: -jar /data/app/demo.jar -Xms512m -Xmx8g -Xmn170m -XX:+UseG1GC
字段说明:
适用场景:验证JVM参数是否正确生效(如确认是否启用了G1GC,堆大小是否符合预期)。
命令:jinfo -flag MaxHeapSize 12345(查看最大堆内存)
-XX:MaxHeapSize=8589934592 # 单位为字节,即8GB
命令:jinfo -flag UseG1GC 12345(查看是否启用G1GC)
-XX:+UseG1GC # +表示启用,-表示禁用
注意:并非所有JVM参数都支持动态修改,只有标记为“manageable”的参数才能动态调整(可通过java -XX:+PrintFlagsFinal -version | grep manageable查看所有支持动态修改的参数)。
常用可动态修改的参数:
-XX:+PrintGC:启用GC日志输出;-XX:+PrintGCDetails:启用详细GC日志输出;-XX:GCTimeRatio:调整GC时间占比阈值;-XX:MaxGCPauseMillis:调整G1GC的最大暂停时间目标。实战示例1:启用GC详细日志(无需重启) 命令:jinfo -flag +PrintGCDetails 12345验证:jinfo -flag PrintGCDetails 12345,输出-XX:+PrintGCDetails表示已启用。
实战示例2:调整G1GC最大暂停时间为200ms 命令:jinfo -flag MaxGCPauseMillis=200 12345验证:jinfo -flag MaxGCPauseMillis 12345,输出-XX:MaxGCPauseMillis=200。
命令:jinfo -sysprops 12345,输出JVM的系统属性(如java.version、user.home等),等同于System.getProperties()的输出。
PrintFlagsFinal查看manageable属性);-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=8000参数),然后通过jinfo -flag <参数名> 192.168.1.100:8000连接。Jmap(JVM Memory Map)的核心功能是生成JVM的堆内存快照(dump文件),同时可以查看堆内存的使用概况、对象分布、类加载信息等。dump文件是分析内存泄漏、大对象问题的核心数据来源,结合后续的Jhat或VisualVM工具可以精准定位问题。
Jmap通过Attach API连接目标JVM,遍历堆内存中的对象实例,收集对象的类型、大小、引用关系等信息,生成二进制的dump文件(也叫堆转储文件)。生成dump文件时,JVM会暂停应用线程(STW,Stop The World),因此线上系统生成dump时需注意时机,避免影响业务。
jmap [ option ] vmid
核心参数:-dump:[live,]format=b,file=<文件名>.hprof <pid>
实战示例:生成仅包含存活对象的dump文件 命令:jmap -dump:live,format=b,file=/data/dump/demo_heap_dump.hprof 12345输出:Dumping heap to /data/dump/demo_heap_dump.hprof ... Heap dump file created
注意事项:
live参数,会触发Full GC,导致STW,需避开业务高峰期;命令:jmap -heap 12345,输出堆内存的详细配置和使用情况,包括堆大小、GC收集器、各区域使用情况等。
输出示例(关键部分):
Attaching to process ID 12345, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 17.0.8+7-LTS
using thread-local object allocation.
Garbage-First (G1) GC with 4 thread(s)
Heap Configuration:
MinHeapFreeRatio = 40
MaxHeapFreeRatio = 70
MaxHeapSize = 8589934592 (8192.0MB)
NewSize = 178257920 (170.0MB)
MaxNewSize = 5153960448 (4915.2MB)
OldSize = 358612992 (343.0MB)
NewRatio = 2
SurvivorRatio = 8
MetaspaceSize = 21807104 (20.796875MB)
CompressedClassSpaceSize = 1073741824 (1024.0MB)
MaxMetaspaceSize = 17179869184 (16384.0MB)
Heap Usage:
G1 Heap:
regions = 1024
capacity = 8589934592 (8192.0MB)
used = 5662310400 (5400.0MB)
free = 2927624192 (2792.0MB)
65.91796875% used
G1 Young Generation:
Eden Space:
regions = 256
capacity = 2684354560 (2560.0MB)
used = 2684354560 (2560.0MB)
free = 0 (0.0MB)
100.0% used
Survivor Space:
regions = 32
capacity = 335544320 (320.0MB)
used = 167772160 (160.0MB)
free = 167772160 (160.0MB)
50.0% used
G1 Old Generation:
regions = 128
capacity = 5570505728 (5312.0MB)
used = 2809183232 (2680.0MB)
free = 2761322496 (2632.0MB)
50.430232558139535% used
适用场景:快速查看堆内存配置是否符合预期(如最大堆、年轻代大小),各区域使用情况是否正常。
命令:jmap -histo 12345,输出堆内存中各类对象的数量、大小统计(按大小排序)。若添加live参数(jmap -histo:live 12345),则仅统计存活对象(触发Full GC)。
输出示例(关键部分):
num #instances #bytes class name (module)
-------------------------------------------------------
1: 85623 27400960 java.lang.String (java.base)
2: 62345 23262200 java.util.HashMap$Node (java.base)
3: 15678 18246720 com.jam.demo.entity.User (demo)
4: 12345 15609600 java.util.ArrayList (java.base)
5: 8976 10242560 com.jam.demo.service.impl.UserServiceImpl (demo)
字段说明:
[I表示int数组,[Ljava.lang.String;表示String数组)。关键分析点:
com.jam.demo.entity.User)的数量和大小异常多(如几十万实例),可能是该类对象未被正确回收,存在内存泄漏;intern()复用)。实战示例:定位大对象问题 命令:jmap -histo:live 12345 | head -20(查看前20个最大对象) 若输出中发现com.jam.demo.entity.Order有10万实例,总大小达500MB,结合业务逻辑分析,发现是订单查询接口未分页,一次性加载了所有订单数据,导致大对象堆积。
JDK 8及以上版本,永久代被元空间替代,-permstat参数已废弃,若需查看元空间的类加载信息,可使用jmap -histo结合类名过滤(如grep "class")。
问题现象:线上系统运行3天后,老年代使用率从30%涨到90%,频繁触发Full GC,系统响应越来越慢。排查步骤:
jmap -heap 12345确认堆内存使用情况,发现老年代使用率92%,年轻代正常;jmap -dump:live,format=b,file=/data/dump/leak_dump.hprof 12345(避开业务高峰期);com.jam.demo.cache.UserCache类中有一个静态HashMap,存储了所有用户的登录记录,但未设置过期清理机制,随着用户登录次数增加,HashMap中的对象越来越多,无法被GC回收,导致老年代内存泄漏;解决方案:修改UserCache类,使用WeakHashMap替代HashMap,或添加定时任务清理过期的登录记录,部署后观察,老年代使用率稳定在40%左右,Full GC频率恢复正常。Jhat(JVM Heap Analysis Tool)是JDK自带的堆快照分析工具,能够解析Jmap生成的dump文件,生成Web可视化界面,支持查看对象分布、引用关系、查找内存泄漏根源等。虽然JDK 9后Jhat被标记为废弃(推荐使用JVisualVM),但由于其轻量、无需额外安装,仍在部分场景下使用。
Jhat启动一个Web服务器,解析dump文件中的二进制数据,将对象的类型、大小、引用关系等信息转换为HTML页面,用户通过浏览器访问(默认端口7000),即可查看和分析堆内存数据。Jhat还支持自定义查询语句(OQL,Object Query Language),精准查找目标对象。
jhat [ options ] <dump-file>
命令:jhat -J-Xmx2g /data/dump/leak_dump.hprof
-J-Xmx2g:指定Jhat自身的堆内存大小(若dump文件较大,需增大此参数,否则会OOM); 输出示例:Reading from /data/dump/leak_dump.hprof...
Dump file created Wed Oct 11 15:30:22 CST 2024
Snapshot read, resolving...
Resolving 567890 objects...
Chasing references, expect 113 dots...............................................................
Eliminating duplicate references...............................................................
Snapshot resolved.
Started HTTP server on port 7000
Server is ready.
此时,通过浏览器访问http://localhost:7000(若在远程服务器,需替换为服务器IP),即可进入Jhat的Web分析界面。
首页核心入口:
关键分析功能实战:
点击首页“Class instances size”,进入按大小排序的类列表,找到最大的几个类(如com.jam.demo.cache.UserCache),点击类名进入详情页,查看该类的实例数量和引用关系。
在类详情页,点击某个实例的“Reference”链接,可查看该对象被哪些对象引用(传入引用),以及引用了哪些对象(传出引用)。对于内存泄漏问题,重点查看“传入引用”,找到导致对象无法被回收的根引用(如静态变量、线程局部变量等)。
Jhat支持OQL(类似SQL)查询堆中的对象,语法简洁,适合精准定位问题。常用OQL语法:
实战示例:查找com.jam.demo.entity.User类中age > 30的对象 在OQL Query输入框中输入:
select u.id, u.name, u.age from com.jam.demo.entity.User u where u.age > 30
点击“Execute”,即可显示符合条件的User对象列表。
select * from com.jam.demo.entity.User;select * from java.lang.Object o where size(o) > 1024*1024;select * from java.lang.Object o where referrers(o) instanceof java.lang.Class。-J-Xmx参数增大Jhat的堆内存(如-J-Xmx4g);firewall-cmd --add-port=7000/tcp --permanent),否则本地浏览器无法访问。基于4.4节的内存泄漏问题,使用Jhat分析dump文件:
com.jam.demo.cache.UserCache类(大小500MB,实例1个);java.lang.Class(即UserCache类的静态引用)引用;static HashMap<String, UserLoginRecord> loginMap中有10万+条记录,且无过期清理逻辑;Jstack(JVM Stack Trace)的核心功能是生成线程快照(线程堆栈),显示当前所有线程的状态、调用栈信息、锁持有情况等。线程快照是定位线程死锁、线程阻塞、CPU 100%等问题的核心工具,能够精准找到问题线程和对应的代码位置。
Jstack通过Attach API连接目标JVM,遍历所有线程(包括用户线程和JVM内部线程),收集每个线程的状态(如RUNNABLE、BLOCKED、WAITING)、调用栈(方法调用链)、锁信息(持有锁、等待锁)等,生成文本格式的线程快照。生成快照时,JVM会短暂STW(毫秒级,对业务影响极小)。
jstack [ options ] vmid
线程状态在JVM中分为6种,需重点关注以下4种(线程快照中用java.lang.Thread.State标识):
synchronized未获取到锁);Object.wait()、Thread.join()等方法进入,需其他线程唤醒);Object.wait(long)、Thread.sleep(long)等方法进入,超时后自动唤醒)。命令:jstack 12345 > /data/dump/demo_thread_dump.txt(将快照输出到文件,方便分析) 输出文件的核心内容结构(每个线程的信息):
"http-nio-8080-exec-1" #26 daemon prio=5 os_prio=0 cpu=1234.56ms elapsed=12345.67s tid=0x00007f8a12345678 nid=0x1234 runnable [0x00007f8a0abcdef0]
java.lang.Thread.State: RUNNABLE
at com.jam.demo.service.impl.UserServiceImpl.queryUserById(UserServiceImpl.java:45)
at com.jam.demo.controller.UserController.getUser(UserController.java:30)
at jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
at jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:568)
at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:205)
...
"http-nio-8080-exec-2"#27 daemon prio=5 os_prio=0 cpu=987.65ms elapsed=12345.67s tid=0x00007f8a12345680 nid=0x1235 waiting for monitor entry [0x00007f8a0abd0000]
java.lang.Thread.State: BLOCKED (on object monitor)
at com.jam.demo.service.impl.UserServiceImpl.updateUser(UserServiceImpl.java:60)
- waiting to lock <0x000000076ab12345> (a com.jam.demo.service.impl.UserServiceImpl)
at com.jam.demo.controller.UserController.updateUser(UserController.java:45)
...
字段说明:
top -p 12345 -H查看CPU占用高的线程nid);-l参数会在线程快照中额外输出锁的详细信息,包括死锁检测结果。若存在死锁,Jstack会在快照末尾明确标注死锁的线程和持有的锁。
实战示例:检测死锁 命令:jstack -l 12345 > /data/dump/demo_deadlock_dump.txt输出文件末尾的死锁信息:
Found one Java-level deadlock:
=============================
"Thread-A":
waiting to lock monitor 0x00007f8a12345678 (object 0x000000076ab12345, a com.jam.demo.service.impl.OrderServiceImpl),
which is held by "Thread-B"
"Thread-B":
waiting to lock monitor 0x00007f8a12345680 (object 0x000000076ab12350, a com.jam.demo.service.impl.UserServiceImpl),
which is held by "Thread-A"
Java stack information for the threads listed above:
===================================================
"Thread-A":
at com.jam.demo.service.impl.OrderServiceImpl.updateOrder(OrderServiceImpl.java:75)
- waiting to lock <0x000000076ab12345> (a com.jam.demo.service.impl.OrderServiceImpl)
at com.jam.demo.service.impl.UserServiceImpl.updateUserAndOrder(UserServiceImpl.java:90)
- locked <0x000000076ab12350> (a com.jam.demo.service.impl.UserServiceImpl)
...
"Thread-B":
at com.jam.demo.service.impl.UserServiceImpl.queryUserByOrderId(UserServiceImpl.java:65)
- waiting to lock <0x000000076ab12350> (a com.jam.demo.service.impl.UserServiceImpl)
at com.jam.demo.service.impl.OrderServiceImpl.queryOrderDetail(OrderServiceImpl.java:50)
- locked <0x0000000076ab12345> (a com.jam.demo.service.impl.OrderServiceImpl)
...
Found 1 deadlock.
死锁分析:
CPU 100%通常是由于某个线程陷入无限循环或执行耗时过长的操作(如复杂计算、死循环),通过Jstack结合top命令可快速定位。
实战步骤:
top命令找到CPU占用高的Java进程:top -p 12345(12345为目标PID);H键查看进程内各线程的CPU占用率,找到CPU 100%的线程(假设线程ID为0x1234,即nid=0x1234);jstack 12345 | grep -A 20 4660;示例输出(过滤后的线程信息):
"ComputeThread-1" #30 prio=5 os_prio=0 cpu=99876.54ms elapsed=1234.56s tid=0x00007f8a12345690 nid=0x1234 runnable [0x00007f8a0abe0000]
java.lang.Thread.State: RUNNABLE
at com.jam.demo.service.impl.ComputeServiceImpl.calculate(ComputeServiceImpl.java:35)
at com.jam.demo.service.impl.ComputeServiceImpl.run(ComputeServiceImpl.java:20)
at java.lang.Thread.run(Thread.java:833)
分析:ComputeServiceImpl.java:35行存在无限循环(如while(true)未加退出条件),导致该线程持续占用CPU,最终使CPU使用率达到100%。
问题现象:线上系统部分订单相关接口无响应,日志无报错,线程数持续增加。排查步骤:
jps获取进程PID(12345);jstack -l 12345生成线程快照,发现存在死锁(如6.4.2节示例);updateUserAndOrder方法先锁UserServiceImpl,再调用OrderServiceImpl的updateOrder方法(需要锁OrderServiceImpl);Thread-B的queryOrderDetail方法先锁OrderServiceImpl,再调用UserServiceImpl的queryUserByOrderId方法(需要锁UserServiceImpl);前面分别讲解了各工具的使用,实际调优中,往往需要多工具协同配合,才能高效定位问题。下面通过一个综合案例,演示五款工具的协同使用流程。
线上电商系统(Spring Boot + MyBatis-Plus)运行一周后,出现以下问题:
命令:jstat -gcutil 12345 2000 100输出显示:
命令:jinfo -flags 12345输出显示:
-Xms4g -Xmx4g -Xmn1g -XX:+UseG1GC;命令:jmap -dump:live,format=b,file=/data/dump/ecommerce_dump.hprof 12345(凌晨2点业务低峰期执行)
jhat -J-Xmx4g /data/dump/ecommerce_dump.hprof;com.jam.demo.entity.Order类有50万+实例,总大小2.8GB;com.jam.demo.service.impl.OrderQueryServiceImpl的静态变量orderCache(HashMap)引用;orderCache的使用逻辑,发现是订单查询缓存,未设置过期时间,且无清理机制,导致订单数据持续堆积。命令:jstack 12345 > /data/dump/ecommerce_thread_dump.txt分析发现:
orderCache的锁;orderCache的操作未使用并发安全的集合,多个线程同时修改时,通过synchronized加锁,导致线程阻塞,响应时间增加。orderCache从HashMap改为ConcurrentHashMap(并发安全),并添加过期清理机制(使用com.google.common.cache.CacheBuilder设置过期时间和最大缓存数量);package com.jam.demo.service.impl;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.jam.demo.entity.Order;
import com.jam.demo.mapper.OrderMapper;
import com.jam.demo.service.OrderQueryService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;
/**
* 订单查询服务实现类
* @author ken
*/
@Service
@Slf4j
publicclass OrderQueryServiceImpl implements OrderQueryService {
@Resource
private OrderMapper orderMapper;
/**
* 订单缓存:过期时间30分钟,最大缓存10万条
*/
privatefinal LoadingCache<Long, Order> orderCache = CacheBuilder.newBuilder()
.expireAfterWrite(30, TimeUnit.MINUTES)
.maximumSize(100000)
.build(new CacheLoader<>() {
@Override
public Order load(Long orderId) {
// 缓存未命中时,从数据库查询
log.info("订单缓存未命中,查询数据库:orderId={}", orderId);
return orderMapper.selectById(orderId);
}
});
/**
* 根据订单ID查询订单详情
* @param orderId 订单ID
* @return 订单详情
*/
@Override
public Order queryOrderById(Long orderId) {
try {
return orderCache.get(orderId);
} catch (Exception e) {
log.error("查询订单详情失败,orderId={}", orderId, e);
returnnull;
}
}
}
<!-- Google Guava 缓存 -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>32.1.3-jre</version>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.30</version>
<scope>provided</scope>
</dependency>
<!-- MyBatis-Plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.5</version>
</dependency>
JDK自带的Jstat、Jinfo、Jmap、Jhat、Jstack五款工具,覆盖了JVM监控、参数调试、内存分析、线程诊断的全流程,是Java开发者必备的调优工具。它们虽然没有图形化界面,但轻量、高效、无需额外部署,在线上问题定位中发挥着不可替代的作用。