
类加载与线上诊断在笔记里本来是两块内容,但它们有共同的工作方式:不要凭现象猜 JVM,先找到运行时实际加载了什么、线程实际卡在哪里、堆里实际留下了什么。
类加载的过程
一个类从字节码到可使用,大致经历加载、连接和初始化。连接又包括验证、准备、解析:验证检查字节码合法性;准备为静态变量分配内存并设置默认值;解析把符号引用转为直接引用。初始化阶段才会执行类初始化逻辑与静态赋值。
这套顺序能解释很多「为什么静态字段的值和我以为的不一样」的问题。准备阶段的默认值与初始化阶段的显式赋值不要混为一谈;编译期常量、触发初始化的主动使用和被动引用也有差别。

原始笔记图示:类加载阶段与类加载器的委派关系。
什么是双亲委派模型
当类加载器收到加载请求,通常先让父加载器尝试,父加载器无法加载时再由自己处理。它的目的之一是避免核心类被应用类路径中的同名类替换,并使同一个类在层级结构中尽量保持唯一。
「双亲」不是严格的继承关系,更接近委派关系。SPI、容器隔离、热部署等场景会出现线程上下文类加载器或自定义加载器;此时重点要问:由谁加载、从哪里加载、同名类是否被不同加载器分别定义。JVM 判断两个类是否相同,不只看全限定名,还看定义它的类加载器。
对象创建过程
对象创建通常包括类是否已加载、堆上分配内存、把内存初始化为零值、设置对象头、执行构造器等步骤。内存分配可能采用指针碰撞或空闲列表,取决于堆是否规整以及收集器如何维护可用空间。
对象访问常见两种实现:句柄访问与直接指针访问。前者让对象移动后只需更新句柄中的指针;后者少一次间接访问。具体实现属于 JVM 细节,应用层不该依赖某个 HotSpot 版本的内存布局做正确性判断。

原始笔记图示:通过句柄或直接指针定位对象实例与类型信息。
CPU 飙高怎么排查
第一步是把进程和线程对应起来:定位高 CPU 进程,再找高 CPU 线程,把线程 ID 转换为十六进制后与 jstack 中的 nid 对照。这样才能知道高 CPU 对应哪段栈帧,而不是只看到一个百分比。
常见结果有三类:业务循环或重复计算、锁竞争后的自旋重试、GC 或序列化等运行时开销。不同原因的处理完全不同:无限重试需要退出条件或退避,热点计算可能要优化算法或分片,GC 则要回到分配速率与堆证据。盲目调大线程池往往只会放大问题。
OOM 如何区分
Java heap space 说明普通堆内存无法满足分配;Metaspace 要检查动态类加载、代理类或类加载器泄漏;Direct buffer memory 指向堆外缓冲;unable to create new native thread 则可能是线程数、栈大小或操作系统资源到达上限。
处理 OOM 不能只加 -Xmx。先保留现场:GC 日志、堆 dump、触发时的流量和队列长度。然后回答三个问题:是谁持有对象,为什么它仍可达,增长是否和某类请求或任务强相关。只有证据表明工作集确实合理且容量不足时,扩容才是合适解法。
死锁怎么排查
jstack 会在检测到 monitor 死锁时给出线程与锁的循环关系。若是 ReentrantLock 等显式锁,仍要结合线程栈、业务锁顺序和锁等待时间确认。修复策略优先是统一资源获取顺序、缩小临界区,并在可降级路径上使用超时与回退;重启只能恢复服务,不能解释问题。
小结
类加载题的关键是「哪个加载器定义了这个类」,线上 JVM 题的关键是「哪份运行时证据支持这个判断」。把类、线程和内存都落到可观察的对象上,JVM 才不只是参数表。
FIELD NOTES / DISCUSS
文章讨论
读完后,欢迎留下你的补充、疑问或不同看法。
正在读取评论…