首页 / 资讯中心 / 文章详情

用 jvisualvm.exe 实战排查 JVM 堆栈内存与线程堵塞问题

用 jvisualvm.exe 实战排查 JVM 堆栈内存与线程堵塞问题 ★ FEATURED ARTICLE
线上Java服务突然卡死接口一个接一个超时CPU飙到90%以上重启之后又恢复正常。凌晨三点收到告警你在电脑前盯着top输出不知所措一边猜是内存泄漏还是线程卡死一边祈祷再撑几分钟。这种场景我经历过太多次了最后真正帮我快速定位问题的往往不是什么高深的内存分析器而是JDK自带的那个不起眼的小工具——jvisualvm.exe。这篇文章就带你实战演练如何用jvisualvm.exe查看JVM堆栈内存、定位线程堵塞问题。全文基于我日常排查线上故障的真实经验整理适合Java开发、运维、架构师以及所有被线上JVM问题折磨过的同学。内容会覆盖工具定位、连接配置、堆内存分析、线程Dump抓取与阻塞定位、堆转储文件分析最后附上高频问题排查表和避坑心得。1. 先搞清楚问题jvisualvm.exe到底是做什么的1.1 它和“JVM堆栈内存、线程堵塞”有什么关系很多新手会把“堆栈”当成一个东西其实在JVM排查里它们是两个完全不同的维度。堆Heap管对象实例的分配和回收栈Stack管线程调用和执行。jvisualvm.exe最厉害的地方在于它用一个可视化界面把这两类数据全部打通了——你既能看到堆内存的占用曲线、Eden区和Old区的增长趋势也能看到每一个线程的状态、调用了什么方法、卡在哪一行代码。线程堵塞更准确的说法是线程阻塞恰恰是这两个维度的交叉地带。举一个最常见的例子线程A持有一把锁线程B在等这把锁结果线程A在做Full GC整个老年代都忙到飞起。这时候你只看线程Dump会看到线程B卡在锁等待上但它只是受害者真正的原因是堆内存压力太大GC频发导致持锁线程迟迟执行不完。线程状态和内存情况必须结合起来看才能找到根源。这个结论我验证了不下十次。每次线上故障第一件事就是打开jvisualvm.exe左边选中目标进程右边先切到Monitor面板扫一眼CPU和堆内存曲线再切到Threads面板看线程状态分布最后抓一个线程Dump去核对细节。三个动作两分钟完成比单纯拿着jstack反复猜要靠谱得多。1.2 jvisualvm.exe在哪里JDK安装目录里的隐藏利器jvisualvm.exe位于JDK安装目录下的bin文件夹中。Windows下就是%JAVA_HOME%\bin\jvisualvm.exeLinux/macOS对应目录下是jvisualvm这个可执行脚本。注意前提是你装的是完整的JDK而不是只有JRE的运行环境。JRE里没有这个工具这也是很多人折腾半天找不到原因所在。它本质上是基于NetBeans平台开发的一个可视化管理工具官方名叫VisualVM。JDK 6 Update 7之后它就被打包进了JDK发行版一直带着走到JDK 8、JDK 11哪怕到了JDK 15以后它被从Oracle JDK发行版中移除了你依然可以从独立发行渠道下载VisualVM的独立版本功能更全插件更新也更快。打开时的界面默认会列出本机所有JVM进程包括你自己启动的应用、IDE、甚至一些系统级Java服务。左边树形列表里能看到一大堆进程选中一个就直接进入监控面板。右侧的常用菜单包括Overview概览、Monitor监控、Threads线程、Sampler抽样、Profiler性能分析。另外还有Visual GC插件装上一个就能看到堆内存分代使用明细包括Eden、Survivor、Old和Metaspace的实时变化这是排查内存问题最直观的入口。1.3 启动失败我的第一条排错经验启动jvisualvm.exe遇到最典型的问题是双击之后没有任何反应尤其在国内开发者的Windows机器上特别常见。先检查Java版本是不是64位如果是多版本共存要确保JAVA_HOME指向的是目标JDK再看你是不是同时装了多个JDK导致jvisualvm.exe加载到了不兼容的库。万能的排查法是直接在命令行窗口执行%JAVA_HOME%\bin\jvisualvm.exe -J-Xdebug这样会把启动日志打到控制台任何异常堆栈都逃不掉。如果提示缺什么类或者抛出某个NoClassDefFoundError基本就是JDK版本和VisualVM版本不匹配换一个其他版本的JDK再试就好。还有一个容易被忽略的点——如果你把JDK装在有中文空格之类的路径下也可能导致视觉组件加载异常建议统一用英文路径。连接远程服务器进程的时候经常会遇到“无法连接”的报错这个放到后面第2节详细拆解因为涉及JMX参数的配置是踩坑重灾区。2. 上手第一步从进程到面板的完整导航2.1 本地进程列表里找不到目标多半是用户权限不对打开jvisualvm.exe以后默认“Local”目录下列出的进程其实是有限的。如果你用管理员身份启动jvisualvm.exe能看到的进程更多反过来用普通用户启动时有些属于其他用户的Java进程就会从列表里消失。这不是bug而是系统权限隔离的正常结果。实际项目里一个服务器上可能启动了好几个Java进程比如业务应用、定时任务、消息消费端。你启动jvisualvm.exe后如果只看到一两个大概率是权限问题。解决方式是用命令行切到和JVM进程同一个用户下再启动jvisualvm或者直接用root/管理员账户重新启动。另外需要注意jvisualvm.exe只能看到本机的JVM进程。要监控其他机器上的Java应用必须走JMX远程连接这属于第2.2节的范畴。本地进程列表还有一个特点——如果某个Java进程退出了它不会自动从列表消失而是变成灰显状态。你点进去会发现所有监控数据都是冻结的这是为了让你事后还能翻看最后留下的现场信息不要误以为还能活过来。2.2 远程进程怎么连远程连接的核心是JMX全称Java Management Extensions。你要在被监控的Java应用启动参数里加上这样一段-Dcom.sun.management.jmxremotetrue -Dcom.sun.management.jmxremote.port9090 -Dcom.sun.management.jmxremote.authenticatefalse -Dcom.sun.management.jmxremote.sslfalse这段配置的意思是开启JMX远程管理监听9090端口不启用认证和SSL。不启用认证和SSL只适合内网测试环境生产环境必须加上安全控制不然别人可以直接连你的JMX端口执行各种操作风险非常大。然后在本地的jvisualvm.exe里右键Local节点选择“Add JMX Connection”输入远程服务器的IP和端口就能连上。这一步有几个常见问题防火墙把9090端口拦了。解决方式是临时放开或者通过SSH隧道转发不要为了省事把防火墙整个关掉。服务器监听的是内网IP但你是从公网连过去JMX绑定地址不对。在启动参数里加-Djava.rmi.server.hostname服务器实际IP。连接时提示ClassNotFoundException说明远程JDK和本地VisualVM的版本差异太大建议两边都用统一版本的VisualVM独立版来连。在我自己项目里这么做之后远程机器的堆内存曲线、GC次数、线程状态就全部同步到本地了跟看本地进程一模一样。尤其适合排查那种“线上偶尔卡一下本地永远复现不出来”的疑难问题。2.3 从Overview面板开始的信息解读无论本地还是远程选中进程后首先看到的是Overview概览页。这个页面从左到右从上到下展示了JVM启动参数、系统属性、JVM版本等基础信息。很多人直接跳过这页去看监控图其实有点浪费——想知道应用到底用了多大的初始堆、最大堆、是否开启了某些GC日志参数这里一眼就能看到。往下翻到“VM arguments”部分你能直接核对启动参数比如-Xms2g -Xmx2g、-XX:UseG1GC、-XX:MaxMetaspaceSize。有一次我排查一个服务频繁Full GC的问题发现明明物理内存有16G却给JVM只配了512M的堆还打着CMS的旧参数问题定位后改配置就完事儿了。Overview页面的“System Properties”里还能看到java.io.tmpdir临时目录路径、user.home等环境信息有时候临时目录没有写权限也会导致应用莫名其妙报错这里排查起来非常方便。至少要把下面的信息记在心里JVM启动参数、JDK版本、最大堆配置。这三样是排查任何JVM问题的基础如果连配置都没搞准后面的分析方向很容易跑偏。3. 堆内存问题看懂“仓库”里到底装了什么3.1 Heap、Metaspace、PermGen的区别怎么监控先把概念理清。JVM内存区域按用途可以分成两块线程共享的堆Heap和非堆Non-Heap。堆里主要存对象实例、数组非堆里有方法区JDK 8之后叫Metaspace、线程栈、本地方法栈等。对应到jvisualvm.exe的Monitor面板你会看到几个名称。Heap就是我们平时配置-Xms和-Xmx对应的区域Metaspace是JDK 8以后的类元数据区域以前叫PermGen。Visual GC插件装上以后你能看到更细的分代结构Eden、S0/S1两个Survivor区、Old区。Eden区用于新对象分配Minor GC时幸存的对象会进入Survivor区年龄足够大的对象会晋升到Old区。Old区占满之后就会触发Full GC。这些区域的利用率曲线和颜色就像仓库的地图。Heap曲线平稳或缓慢上升属于正常如果曲线一路走高然后每隔一段时间被GC打断往下走一段说明应用在“正常地不断分配对象、不断回收对象”如果曲线在Full GC之后仍然下不来或者频繁出现阶梯式上升并伴随Full GC次数激增就是典型的堆内存压力过大。3.2 盯住内存趋势是缓慢增长还是断崖式突降我排查内存问题的经验是不要只看实时快照要看趋势。jvisualvm.exe的Monitor面板里默认会画Heap、PermGen/Metaspace、Classes、Threads这几条曲线。遇到可疑情况我会先观察10~30分钟结合业务流量看堆内存增长是“随流量波动”还是“只增不减”。举个例子一个每天固定时段跑批的任务堆内存用量伴随任务启动而上涨任务结束后老年代曲线缓慢回落这是正常的。如果每天任务结束后堆内存都比前一天高出好几百MB那就是存在对象无法回收的泄漏点。“断崖式下跌”这种情况我会重点讲一讲。如果你在堆内存曲线图上看到某个时间点内存突然断崖式掉下去大概率是触发了Full GC而且是大规模的Stop-The-World。这时候你切到Threads面板会看到大量线程集体变灰全部处在“暂停”状态——那是GC停顿时所有业务线程被冻住的视觉化呈现。这种情况一旦频繁发生用户体验会急剧变差表现为接口大规模超时。我遇到过最夸张的一次是某服务每隔5分钟就发生一次Full GC每次停顿时间长达4秒堆内存曲线看起来就像锯齿。打开Visual GC一看Old区一直在临界点徘徊命中一个数组扩容的隐藏缓存每次放不下的数据全堆积在Old区GC回收不掉。真实的故障点比你想象的更隐蔽光靠猜是真不行。3.3 什么时候该去抓Heap Dump当堆内存出现“只增不减”的迹象时单纯看曲线已经不够了。你需要抓一份Heap Dump看看堆里到底存了哪些对象、被谁引用着。jvisualvm.exe支持三种方式生成堆转储在进程节点上右键选择“Heap Dump”——相当于给内存拍一张快照包含所有对象的数量和大小。在监控界面点击“Dump”按钮。在命令行用jmap -dump:formatb,filexxx.hprof pid生成。应用启动参数里还可以预先配置-XX:HeapDumpOnOutOfMemoryError -XX:HeapDumpPath/data/logs/dump/一旦发生OOM自动生成dump文件省去找现场的时间。抓取Heap Dump本身会对线上性能造成一定影响尤其堆很大比如10G以上的时候dump过程会停顿一段时间。我的建议是能选低峰期就选低峰期能先通过GC日志缩小怀疑范围就先缩小范围。等真正定位到可能泄漏的类之后再精准抓dump。Heap Dump生成出来之后jvisualvm.exe右侧会出现一个以“.hprof”结尾的tab页按类名分组展示对象数量、占用字节、类加载器等。第一眼看“Size”排序Top 10以内往往就是大头。关于这块的具体分析手法后面第5节单独展开。4. 线程堵塞从线程面板到线程Dump的定位实战4.1 线程面板里那些状态到底是什么意思线程堵塞问题在jvisualvm.exe里的入口是Threads面板。这个面板默认按时间轴展示所有线程的“状态条”。线程状态用颜色区分比如绿色表示运行中蓝色表示等待橙色或红色可能是阻塞或者锁等待。刚看这个面板会有点眼花缭乱但最有价值的是观察“那些持续处于非运行状态的线程”。先对照官方定义把常见的线程状态梳理清楚线程状态含义常见原因RUNNABLE正在执行或等待CPU分配正常执行也可能是大量计算、GC导致变慢WAITING无限期等待某个条件调用了Object.wait()、LockSupport.park()TIMED_WAITING有限时间等待Thread.sleep()、join(timeout)BLOCKED被锁阻塞等待monitor锁多个线程竞争同一个锁持锁线程耗时过长NEW创建了但还没启动正常生命周期TERMINATED执行完毕正常生命周期实线上最常见的问题就是BLOCKED和WAITING。BLOCKED非常直白——线程在尝试获取一个已经被其他线程持有的对象锁一直进不去于是停在BLOCKED状态。WAITING则相对隐蔽一点它通常是线程主动选择挂起但也可能是被某个条件的永远不满足给“挂死”了比如消费者线程在阻塞队列上poll等消息队列空了线程就长期WAITING。我在实际项目里会按这个优先级排查线程面板第一步看有没有大量线程长时间处于BLOCKED第二步看有没有线程长期卡在RUNNABLE状态且对应的时间轴颜色异常明亮大量CPU占用第三步看线程调用的栈顶停在哪一个方法。这套逻辑比盯着某一个线程死磕要快得多。4.2 BLOCKED、WAITING、RUNNABLE现场模拟文字讲概念不够直观我经常用一段极其简单的Java代码来复现阻塞场景然后在jvisualvm.exe里观察。这是一段模拟两个线程争抢同一把锁的示例public class BlockedDemo { private static final Object LOCK new Object(); public static void main(String[] args) throws Exception { Thread t1 new Thread(() - { synchronized (LOCK) { System.out.println(t1 hold lock); try { Thread.sleep(60000); } catch (InterruptedException e) { } } }, Thread-1-HoldLock); Thread t2 new Thread(() - { synchronized (LOCK) { System.out.println(t2 should never run until t1 exit); } }, Thread-2-WaitLock); t1.start(); Thread.sleep(100); t2.start(); Thread.sleep(1000); System.out.println(main end); } }启动后在jvisualvm.exe的Threads面板里你会看到Thread-1-HoldLock处于RUNNABLE其实是sleep中Thread-2-WaitLock处于橙色阻塞状态。然后右键Thread-2-WaitLock抓一个线程Dump栈顶会显示java.lang.Object.wait(Native Method) java.lang.Object.wait(Object.java:...) synchronized (LOCK)如果你看到的栈是at BlockedDemo.lambda$main$1(BlockedDemo.java:12)再往下会有一行locked 0x... (a java.lang.Object)说明它正等着获取这把锁。此时dump文件里还会用特定格式标记出“Found one Java-level deadlock”如果两个线程互锁则会明确显示死锁环。WAITING状态的复现更简单用一个无参的Object.wait()或者直接上LockSupport.park()。RUNNABLE状态的“假运行”则要看高CPU场景——我曾经用一个大循环狂算线程状态保持RUNNABLE但实际干的活是大量无效计算这种通常得配合Sampler去抓热点方法才能定位只看线程状态是不够的。4.3 线程Dump怎么抓动作快、信息准所谓线程Dump就是把所有线程当前调用栈整体打印出来。jvisualvm.exe里右键线程面板空白处选“Thread Dump”一瞬间就能生成一份文本视图同时展示线程状态、操作系统线程ID、调用栈、锁持有关系等。实战操作里的几个关键点第一抓Dump通常要连续抓两次或三次间隔5~10秒。因为一次快照只能看到瞬时状态如果问题线程恰好在这瞬间执行完了你什么都看不到。多抓几次能观察到锁持有者是否在变化、某个等待线程是否一直等不到锁。第二保存Dump文件太重要了。线上复现困难的时候一份Dump文件就是案发现场。jvisualvm.exe允许把Thread Dump保存成文本文件命名带上时间戳。我习惯按服务名-时间-线程状态来命名比如order-service-20250112-1030-blocked.txt归档起来以后回查特别方便。第三不要只盯着BLOCKED状态。大量线程处于WAITING且集中在某个入口方法上也可能是你的线程池队列塞满了后续任务排队等不到执行线程表现出来同样是接口卡顿。只有结合线程Dump里完整的调用栈才能区分“锁竞争”和“池耗尽”这两类完全不同的根因。4.4 Likely Deadlock工具已经替你把死锁找出来了jvisualvm.exe的线程Dump有一个非常贴心的内置分析功能它会自动扫描线程间是否存在锁的循环依赖一旦发现就给出“Likely Deadlock”的明确提示并标记出互相持锁等待的线程以及它们持有的锁对象。我第一次看到这个提示时正值一个老项目线上告警两个定时任务线程要分别锁定A和B两个资源结果一个锁了A想拿B另一个锁了B想拿A两个线程都卡在对方的等待上。jvisualvm.exe直接告诉我这个信息的对应线程名被标在“Found one Java-level deadlock”下方那一刻真是长出一口气。不过Deadlock在实际生产环境里并不多见更多见的是“活锁”和“伪死锁”——比如线程A一直等待一个永远不会来的通知因为没有超时机制线程状态是WAITING而不是BLOCKED。遇到这种情况工具并不会自动报警你得结合业务调用链去分析为何那个producer线程没有发通知。所以我的方法是看到“Likely Deadlock”先高兴三秒然后立刻怀疑它是不是误报重点看那两条线程真实的锁持有栈以及它们到底卡在什么业务方法上。线程这个问题千万别被工具牵着鼻子走。工具提供的是线索根因还得靠你理解自己业务的并发模型。5. 堆转储与OOM排查从现象到根因5.1 用jvisualvm.exe生成堆转储文件的三种方式当你怀疑是内存泄漏或堆溢出时光靠监控曲线的趋势图说服不了自己得拿出堆转储文件来“实锤”。在jvisualvm.exe里生成堆转储文件其实有两种便捷路径一是选中进程节点右键选择“Heap Dump”。这是最简单的方式默认生成一份hprof文件并自动在编辑器打开同时保留一份历史快照文件。你在热点分析时打开它能看到所有对象的柱状图、引用链、调用路径。二是通过Monitor面板点“Dump”按钮。关于“Thread Dump”和“Heap Dump”这两个按钮我第一次用的时候容易搞混给新手提个醒左边是线程相关右边是堆相关不要点错。Thread Dump是文本Heap Dump是二进制快照里面包含了每个对象、每个引用、每个线程栈信息。第三种方式是命令行jmap -dump:live,formatb,filedump.hprof pid-dump:live参数的意思是在dump前先强制执行一次Full GC只保留可达对象。这对排查“谁能存活”很有用不过它会让线上服务卡顿一下谨慎使用。生产环境强烈推荐提前加-XX:HeapDumpOnOutOfMemoryErrorOOM发生时自动落盘包含OOM瞬间的完整现场。有几次我半夜被OOM告警叫醒第二天早上看dump目录文件就安静躺在那里不用再费劲抢抓现场。5.2 堆转储里第一眼要看的三个地方打开一个Heap Dump后jvisualvm.exe会显示“Summary”页签同时给你按“Classes”和“Instances”两个视图。第一眼根据自己的常见问题我会重点看三块第一块大对象统计。默认视图里byte[]、char[]、int[]、String、以及各种自定义的集合类常在前排。如果发现某个byte[]占用好几个GB基本可以断定是缓存了图片、大文件或者序列化后的二进制数据没释放。第二块可疑的List/Map。JDK集合类持有了绝大多数的对象引用。你展开一个HashMap能直接看到它的key、value是什么类型、总数多少。如果一个HashMap里堆了上千万条数据却还在被全局引用着那基本就是它了。第三块线程对应的栈对象。异常信息里经常包含StackTraceElement、ThreadLocal等。如果线程栈里挂着很大的局部变量也可能导致线程无法被回收从而连带持有大量关联对象。这三块扫完基本能判断是“量太大”还是“泄漏”。量太大多见于对数据不分页直接全量load泄漏多见于缓存没有被清理、ThreadLocal没remove、静态集合只加不减。这两种对应修复手段完全不同前提是必须先区分清楚。5.3 结合线程栈和堆转储定位内存泄漏根因有一次我们处理一个内存持续上涨的服务Heap Dump里发现一个内部名为“TemporaryTaskQueue”的队列对象占用了大概1.5G内存里面塞满了未处理的任务对象。单看堆Dump你只知道它塞满了但为什么塞满还得配线程栈。于是我又抓了一次线程Dump发现一个消费者线程始终停留在take()方法上属于正常等待而生产者的线程却在疯狂执行offer()而且没有任何休眠时间。也就是说消费速率跟不上生产速率任务队列越积越多最终导致内存膨胀。解法和“堵塞”有一定联系但又不完全一样这不是锁竞争而是流量削峰失败。我们在生产端加了一个限流阀门并在消费端扩容了一个并发队列内存曲线肉眼可见地平稳了。这件事给我的教训是看到内存问题不要只盯内存本身一定要同时抓线程Dump看谁在填数据、谁在消费数据。两个Dump文件相互印证才是完整证据链。JDK自带的jvisualvm.exe可以处理小中型堆转储。如果堆特别大10G以上打开会非常卡顿这时建议用独立版VisualVM或者考虑配合MATMemory Analyzer ToolEclipse基金会的内存分析工具来做离线分析。独立版VisualVM的关键连接参数和主程序一样但插件体系更全内存解析效率也好一些。6. 常见问题与排查技巧实录6.1 连接超时与进程丢失怎么处理使用jvisualvm.exe过程中我遇到最多的可能就是“连接不上”和“监控断线”。把几种典型的坑列成速查表方便你直接对照现象常见原因解决办法远程JMX连接失败防火墙屏蔽端口放开9090端口或用SSH隧道转发远程连接超时RMI绑定地址不对加-Djava.rmi.server.hostnameIP启动参数本地进程看不到权限不足用同用户或管理员身份启动jvisualvm本地进程灰显进程已退出查看冻结数据这是最后的现场连接一段时间后断开JMX连接过多或网络抖动重连并检查网络稳定性JMX端口被占用另一个实例已占用换一个端口或复用已有连接有一次我把某个服务的JMX端口配成了默认的1099结果该端口被另一个应用占用连接界面一直转圈。其实JMX端口最好是冷门一些的端口且每个JVM用独立端口避免冲突。如果你连的是Docker容器里的Java进程还要额外注意映射关系。容器内的JMX地址和宿主机端口如果映射不一致连接特别容易失败此时先docker exec进容器确认监听地址是否正确。6.2 为什么采样线程栈会“卡住”Sampler面板是jvisualvm.exe里另一个我很常用的功能它可以按固定时间间隔采集线程的CPU占用和内存分配情况。有人反馈采样线程栈时界面卡住不动大半不是工具崩溃而是样本统计在处理大堆栈时负载太高。这里的“CPU采样”一般会得到两个维度的结果哪个线程最耗CPU哪个方法自顶向下消耗CPU最多。我处理过一个疯狂消耗CPU的任务Sampler面板显示的Thread CPU占比极大的线程名是“RMI TCP Connection”当时还以为是网络线程的问题结果顺藤摸瓜发现是该连接对应的某个RMI操作在做无休止循环。如果只盯着业务线程永远找不到这种间接问题。Sampler模式下采样结果和真实执行情况存在误差尤其高频小方法可能会漏采。真要精确定位热点方法时我建议切换到Profiler模式做全量插桩统计代价是性能开销大适合压测/预发环境。线上环境最好用Sampler哪怕它不准至少不伤在线业务。6.3 一次“高并发请求堆积”的故障复盘最后分享一个完整故障复盘把前面各个板块串起来。某业务系统在早高峰时出现大量请求超时重启后短暂恢复很快又复发。第一步我用jvisualvm.exe连上应用进程Monitor面板看到Heap使用率已经到95%GC活动非常密集Full GC次数以分钟级在增长。第二步抓线程Dump发现业务线程大多处于WAITING状态栈顶指向java.util.concurrent.LinkedBlockingQueue.take()。第三步抓Heap Dump发现队列对象中堆积了大量待处理消息。综合分析结论上游消息推送速率远大于下游处理能力导致内存中缓存的消息对象越来越多GC越来越频繁最终所有线程都在等待队列有可用空间实际表现就是“请求全卡死”。这不算传统意义的线程死锁但症状非常像“堵塞”。后续措施是给队列容量设置上限、改造消费逻辑批量处理、降低上游生产速率。上线后我又用jvisualvm.exe连续观察了两个小时Heap曲线和线程状态条都稳定在一个健康的区间。整个排查过程从现象到定位到修复全靠这一个工具连命令行都没怎么用。点开jvisualvm.exe那个图标就是我处理线上Java疑难杂症的习惯起点。我个人在实际操作中的最大体会是工具永远是辅助你对进程、线程、内存模型的直观理解才是核心竞争力。jvisualvm.exe给你的是“看见”的能力而把看见的东西和业务逻辑对上号靠的是日常多练、多踩、多复盘。平时没有故障的时候随便连上自己正在运行的开发项目多点点Threads面板、Sampler按钮看看每个状态长什么样子。等你真遇到问题时你会发现自己对这套工具已经形成肌肉记忆根本不用翻文档。
阅读完成 · 觉得有帮助?
咨询建站