news 2026/7/30 9:46:25

Arthas classloader + sc 实战:JVM 类加载与手动加载

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Arthas classloader + sc 实战:JVM 类加载与手动加载

大家好,我是程序员天天困。

这是「Arthas 线上诊断实战」系列第 6 篇。上一篇 Profiler 火焰图 解决的是 CPU 热点定位,这篇讲Arthas classloader和 sc——回到一个更基础但更让人懵的场景:线上突然报ClassNotFoundException,代码明明就在那儿,依赖也引了,JVM 偏偏说找不到。点个收藏,我们直接上手。

一、ClassNotFoundException 不是代码写错了,是类没被加载

线上最常见的类加载问题,不是类不存在,而是类没被 JVM 加载过。

这种问题有一个非常典型的场景:一个报表服务引入了工具类ReportUtils,本地跑得好好的,上预发环境突然报ClassNotFoundException。第一反应查 pom 依赖——在的;反编译 jar——class 也在。折腾快一个小时才发现,那个类从来没被加载过,因为预发环境走的代码路径根本没触发到它。

这就引出一个很多人没意识到的真相:你写在项目里的类、maven 引进来的依赖,不等于已经被 JVM 加载了。类是「用到才加载」的,没用到的类可能永远躺在 jar 里不进内存。

那问题就变成两个:

  1. 当前 JVM 到底加载了哪些类?
  2. 能不能手动让 JVM 加载某个类?

Arthas 给了两个命令分别对付这两件事:sc 搜类classloader 管类加载器

二、类什么时候被加载:不是写在项目里就一定加载

类的加载是懒加载,JVM 不会一启动就把所有 class 全塞进内存,而是「用到才加载」。

什么是「用到」?JVM 规范里类的主动使用场景主要包括这几种:

主动使用场景举例
new 实例化new ReportUtils()
调用静态方法ReportUtils.format(...)
访问静态字段ReportUtils.DEFAULT_FORMAT
反射调用Class.forName("com.ttk.ReportUtils")
子类被加载父类会先被加载
main 方法所在的类启动时自动加载

我用一个简单的示例场景说明。假设有个com.ttk.ReportUtils类,里面只有一个静态代码块:

// 示例:静态代码块在类被加载时执行packagecom.ttk;publicclassReportUtils{static{System.out.println("ReportUtils 被加载了!");}}

然后有个启动类Main

// 示例:Main 类引用了 ReportUtils 但没主动使用packagecom.ttk;publicclassMain{publicstaticvoidmain(String[]args){System.out.println("Hello World");// 注意:这里没有 new ReportUtils(),也没调它的静态方法}}

跑一下,输出只有Hello WorldReportUtils 被加载了!这行不会出现。为什么?因为Main只是和ReportUtils在同一个包里,并没有主动使用它——JVM 没有理由加载它。

相同的逻辑也出现在依赖上:你用 maven 引了一个坐标,jar 包里的所有类也不会全部加载,只有被实际用到的才会进 JVM。

这就解释了为什么ClassNotFoundException会在线上突然冒出来–代码在 jar 里,但运行时恰好没走到那条加载路径。

三、Arthas sc 命令:搜出 JVM 里到底加载了哪些类

sc(Search Class)是 Arthas 用来搜索已加载类的命令,它能告诉你 JVM 里现在到底有哪些类。

Arthas sc(Search Class):Arthas 命令,按类名模糊匹配搜索 JVM 已加载的类,支持查看类加载器、字段、方法等详细信息。你可以把它理解为「JVM 内存里的类文件搜索器」。

前提还是先把 Arthas Attach 到目标 JVM(前面几篇讲过,java -jar arthas-boot.jar选进程进到[arthas@PID]$)。进来之后,搜com.ttk包下的所有类:

sc com.ttk.*

从输出结果能直观看到:只有Main被加载了,ReportUtils没出现。

sc 支持模糊匹配,*通配符搜包路径很方便。想看某个类的详细信息——是哪个 ClassLoader 加载的、有哪些字段方法——加-d

sc-dcom.ttk.Main

输出里有个关键字段叫classLoaderHash,这是一串十六进制的 hash 值,代表加载这个类的类加载器。记住这个 hash,后面 classloader 手动加载类的时候要用

可能有人会问:sc 和 jad 有什么区别?

jad 是把已加载的 class 反编译成源码给你看;sc 是列出 JVM 里有哪些类被加载了,看的是「有没有」而不是「源码长啥样」。排查类加载问题,先用 sc 看类在不在,再用 jad 看源码对不对。

sc 的完整用法见 Arthas sc 官方文档,支持-E正则匹配、-f查看字段、-m查看方法等,日常排查sc 类名sc -d 类名就够用了。

四、Arthas classloader 命令:查看当前所有类加载器

想手动加载类,得先搞清楚 JVM 里有哪些类加载器,classloader 命令就是干这个的。

Arthas classloader:Arthas 命令,查看 JVM 内所有 ClassLoader 实例、加载的类数量、层级关系,并支持手动加载类。你可以把它理解为「JVM 类加载器的管理员」。

类加载器是 JVM 加载类的执行者。JVM 里不止一个类加载器,它们按双亲委派模型分层协作

核心逻辑就一句话:收到加载请求时,先交给父加载器去加载,父加载器搞不定才自己加载。这样保证java.lang.String永远由 Bootstrap 加载,不会被你的自定义 ClassLoader 篡改。

双亲委派模型:JVM 类加载器的分层委派机制,子加载器收到请求先委托父加载器加载,父加载器加载不了才自己加载。你可以理解为「有事先请示上级,上级处理不了再自己来」。

用 classloader 命令查看当前 JVM 有哪些类加载器:

classloader

输出会列出每个 ClassLoader 的 hash、类型和已加载的类数量。加-t能看层级关系:

classloader-t

树形输出里能直观看到层级关系(以 JDK 11 为例):

  • BootstrapClassLoader在最顶层,C++ 实现,没有父加载器。
  • PlatformClassLoader(JDK 8 叫ExtClassLoader,JDK 9+ 改名)挂在 Bootstrap 下。
  • AppClassLoader挂在 PlatformClassLoader 下,负责加载你 classpath 下的类。
  • 注意:因为 Arthas attach 到了这个 JVM,所以输出里还会多一个ArthasClassloader,和 AppClassLoader 平级挂在 PlatformClassLoader 下 —— 这是 Arthas 用来加载自身命令类的。

五、手动加载一个类:classloader --load 实战

前面铺了这么多,现在到正题:用 classloader --load 把一个没被加载的类手动塞进 JVM。

为什么要手动加载?排查类加载问题时,有时需要验证「这个类能不能被加载」「加载后行为对不对」,而不想为了触发加载去改业务代码、走一遍完整调用链。classloader --load 给你一条直通路径。

操作分两步:

第 1 步,拿到目标 ClassLoader 的 hash。classloader命令列出所有类加载器,找到你要用的那个(通常是 AppClassLoader 或业务自定义的 ClassLoader),记下它的 hash 值,比如1dbd16a6

第 2 步,用 --load 指定 hash 和类全名:

classloader-c1dbd16a6--loadcom.ttk.ReportUtils

拆开看:

  1. -c 1dbd16a6:指定用哪个 ClassLoader 加载(hash 来自上一步)。
  2. --load com.ttk.ReportUtils:要加载的类全限定名。

执行成功后,终端没有报错就是加载成功了。再用 sc 验证一下:

sc com.ttk.ReportUtils

这次ReportUtils出现在输出里了——说明它已经被 JVM 加载进去了。

有个细节要注意:-c后面跟的 hash 必须是真实存在的 ClassLoader hash,写错了会报classloader not found。这个 hash 每次启动 JVM 都可能不一样,别想偷懒记死值。

六、sc + classloader 怎么和前面的命令搭配

sc 和 classloader 解决的是「类在不在 JVM 里」的问题,和前面几篇的命令是配合关系,不是替代。

六篇写下来,整个排查链路已经比较完整了:

阶段优先命令你在解决什么
入参 / 返回对不对watch结果对错
走了哪条分支、哪层慢trace路径与耗时
没请求也要看对象状态vmtool堆内实例
已定位到方法体写错mc + retransform在线换逻辑
CPU 飙高找全局热点profiler性能瓶颈
类没被加载、手动加载sc + classloader类加载问题
确认字节码是否已换jad反编译核对

推荐排查顺序:报 ClassNotFoundException 先用 sc 看类在不在 JVM 里 -> 不在就用 classloader 查有哪些类加载器、拿到 hash -> 用 classloader --load 手动加载 -> sc 再验一次确认加载成功。这条链路走完,类加载问题基本就闭环了。

结语

Arthas classloader 和 sc 解决的是工具箱里最基础但最容易被忽略的一环——「类到底加载没加载」。ClassNotFoundException不一定是类不存在,很多时候是 JVM 没走到加载路径。sc 帮你查 JVM 里有哪些类,classloader 帮你查有哪些类加载器、还能手动把类塞进去,两者配合,类加载问题基本不用再靠猜。

下一篇我会写这个系列的ognl:Arthas 里最灵活也最容易上头的表达式引擎,一行命令在运行时调任意方法、访问私有字段。本文命令参数以 Arthas sc 官方文档 与 Arthas classloader 官方文档 为准,版本更新了以官网为准。


我是程序员天天困,持续分享编程干货。觉得有用的话记得点赞收藏和关注~也欢迎在评论区聊聊:你有没有在线上踩过 ClassNotFoundException 的坑?用过 Arthas classloader 手动加载吗?

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/30 9:44:29

Elsevier Tracker:科研投稿状态智能追踪解决方案

Elsevier Tracker:科研投稿状态智能追踪解决方案 【免费下载链接】Elsevier-Tracker 项目地址: https://gitcode.com/gh_mirrors/el/Elsevier-Tracker 对于科研工作者而言,投稿到Elsevier期刊后的等待期充满不确定性。手动刷新页面、记录审稿节点…

作者头像 李华
网站建设 2026/7/30 9:44:01

传统文本分类技术优化与工程实践详解

1. 项目背景与核心价值 2025年这个看似普通的文本分类项目,背后隐藏着一位老匠人般的执着。用一整年时间打磨一个模型的应用,这种"慢工出细活"的做法在当今追求快速迭代的AI领域实属罕见。这个Python文本分类项目之所以值得深究,恰…

作者头像 李华
网站建设 2026/7/30 9:37:59

AI为何难以生成高质量高光Shader?图形学技术解析

1. 为什么AI写不好高光Shader?图形学视角的深度解析 最近在技术社区看到一个很有意思的讨论:"不是说现在AI很牛吗,怎么连个高光Shader效果都写不好?"作为在图形学领域摸爬滚打多年的开发者,这个问题确实戳中…

作者头像 李华
网站建设 2026/7/30 9:37:59

LobsterAI技能组合真能替代插件开发?搜索到邮件的自动化流水线实战

有道Lobster技能组合实战:从竞品监控到自动化报告全流程详解 上周用有道Lobster处理市场周报时,发现90%的重复操作其实不需要写插件——内置技能组合就能实现从数据采集到邮件发送的全流程。本文将深入剖析如何用5层嵌套组合技搭建自动化流水线&#xf…

作者头像 李华
网站建设 2026/7/30 9:33:27

罗技K75M机械键盘深度评测:轴体手感与编程场景实战解析

最近在挑选机械键盘时,发现很多朋友在追求个性化客制化的同时,往往忽略了键盘的实际使用体验和长期耐用性。作为长期使用多款键盘的开发者,我深刻体会到一把好键盘对工作效率和编码体验的重要性。本文将基于罗技K75M琥珀系列机械键盘&#xf…

作者头像 李华
网站建设 2026/7/30 9:30:45

Agent 的工程哲学:简单优于复杂、可观测优于自动化、安全优于速度

Agent 的工程哲学:简单优于复杂、可观测优于自动化、安全优于速度 Agent 是 2025 年最火的技术方向之一,同时也是事故率最高的方向之一。根本原因不是技术不成熟,而是工程哲学不清晰。很多人用做 demo 的思路做 Agent 系统,结果 …

作者头像 李华