如何反编译JVM已加载的类?
在线上问题排查中,我们可能会遇到代码运行没有按照预期去执行,或者是怀疑依赖的类版本不对、需要确认线上运行的代码是否是预期版本、或是排查动态代理类的真实逻辑,此时,我们就可以使用Arthas 的 jad 命令来实时反编译 JVM 中已加载的类,无需停服务、无需下载 JAR 包,直接获取运行时源码,是线上排查的核心工具之一。
点击查看官方教程
一、前置准备:安装并启动 Arthas
在使用之前可以先参考官方文档或者在arthas控制台使用jad -help 命令查看当前版本的使用示例或者支持参数。
[arthas@390]$ jad -help
USAGE:
jad [--classLoaderClass <value>] [-c <value>] [-d <value>] [-h] [--hideUnicode] [--lineNumber <value>] [-E] [--source-only] class-pattern [method-name]
SUMMARY:
Decompile class
EXAMPLES:
jad java.lang.String
jad java.lang.String toString
jad java.lang.String -d /tmp/jad/dump
jad --source-only java.lang.String
jad -c 39eb305e org/apache/log4j/Logger
jad -c 39eb305e -E org\\.apache\\.*\\.StringUtils
WIKI:
https://arthas.aliyun.com/doc/jad
OPTIONS:
--classLoaderClass <value> The class name of the special class's classLoader.
-c, --code <value> The hash code of the special class's classLoader
-d, --directory <value> Sets the destination directory for dumped class files required by cfr decompiler
-h, --help this help
--hideUnicode Hide unicode, default value false
--lineNumber <value> Output source code contains line number, default value true
-E, --regex Enable regular expression to match (wildcard matching by default)
--source-only Output source code only
<class-pattern> Class name pattern, use either '.' or '/' as separator
<method-name> Method name pattern, decompile a specific method instead of the whole class二、核心基础:jad 命令入门用法
jad 的核心功能是“反编译运行时类”,最基础的用法仅需指定类的全限定名,语法简洁直观。
1. 基础语法
jad 全类名2. 快速示例:反编译普通类
假设需要查看线上服务中 com.vvhz.vlog.service.impl.FileServiceImpl 类的源码,直接执行:
# 反编译JVM已经加载的FileServiceImpl类
jad com.vvhz.vlog.service.impl.FileServiceImpl
# 反编译JVM已经加载的FileServiceImpl类的upload方法
jad com.vvhz.vlog.service.impl.FileServiceImpl upload3. 执行结果说明
输出内容为:
ClassLoader:类加载器信息;
Location:类路径;
反编译后的 Java 源码 示例如下:
ClassLoader:
+-org.springframework.boot.loader.LaunchedURLClassLoader@1593948d
+-jdk.internal.loader.ClassLoaders$AppClassLoader@277050dc
+-jdk.internal.loader.ClassLoaders$PlatformClassLoader@77b076be
Location:
file:/home/app/vlog-server/vlog-server-1.0.0-SNAPSHOT.jar!/BOOT-INF/classes!/
package com.vvhz.vlog.service.impl;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
@Service
public class FileServiceImpl implements FileService {
private static final Logger log = LoggerFactory.getLogger(FileServiceImpl.class);
private UserDao userDao;
}三、常用参数:提升反编译效率
jad 提供了多个实用参数,可适配不同场景(如导出源码、过滤方法、指定类加载器),以下是高频参数详解:
关键参数实操示例
(1)导出class文件到本地
线上排查时,若需后续分析class或分享给同事,可导出为 .class 文件:
# 导出 FileServiceImpl 类文件到本地
jad -d /home/temp/jad/dump com.vvhz.vlog.service.impl.FileServiceImpl导出路径:/home/temp/jad/dump/com/vvhz/vlog/service/impl/FileServiceImpl.class,导出到本地后,我们可以使用其他反编译工具打开,例如:Luyten反编译class文件教程。
(2)只查看单个方法的实现
若仅关心 upload 方法的逻辑,无需反编译整个类:
jad com.vvhz.vlog.service.impl.FileServiceImpl upload输出仅包含该方法的源码,简洁高效。
(3)解决多类加载器冲突
Spring Boot 项目中,同一个类可能被多个类加载器加载(如系统类加载器、Spring 自定义加载器),直接 jad 可能找不到类或反编译错误版本,步骤如下:
先用
sc(search class)命令查找类的所有加载器:
sc -d com.vvhz.vlog.service.impl.FileServiceImpl # -d 显示详细信息输出中会包含 classLoaderHash(如 1b6d3586),即类加载器的唯一标识;示例:
[arthas@390]$ sc -d com.vvhz.vlog.service.impl.FileServiceImpl
class-info com.vvhz.vlog.service.impl.FileServiceImpl
code-source file:/home/app/vlog-server/vlog-server-1.0.0-SNAPSHOT.jar!/BOOT-INF/classes!/
name com.vvhz.vlog.service.impl.FileServiceImpl
isInterface false
isAnnotation false
isEnum false
isAnonymousClass false
isArray false
isLocalClass false
isMemberClass false
isPrimitive false
isSynthetic false
simple-name FileServiceImpl
modifier public
annotation org.springframework.stereotype.Service
interfaces com.vvhz.vlog.service.FileService
super-class +-java.lang.Object
class-loader +-org.springframework.boot.loader.LaunchedURLClassLoader@1593948d
+-jdk.internal.loader.ClassLoaders$AppClassLoader@277050dc
+-jdk.internal.loader.ClassLoaders$PlatformClassLoader@77b076be
classLoaderHash 1593948d
Affect(row-cnt:1) cost in 58 ms.用
-c参数指定类加载器反编译:
jad -c 1593948d com.vvhz.vlog.service.impl.FileServiceImpl四、高级场景:适配复杂线上环境
1. 反编译动态代理类(如 Spring AOP、CGLIB)
线上很多类是通过动态代理生成的(如事务代理、权限代理),jad 可直接反编译代理类,查看真实执行逻辑:
# 先搜索UserController的代理类
sc com.vvhz.vlog.controller.UserController*
# 反编译动态生成的代理类
jad com.vvhz.vlog.controller.UserController$$EnhancerBySpringCGLIB$$77511504注意:代理类的源码会包含代理逻辑(如 invokewithincrementinterceptor 方法),需结合原始类分析。
2. 批量反编译多个类
若需查看某个包下所有类的源码,用 -E 正则匹配:
# 反编译 com.example.util 包下所有类,并导出到本地
jad -E -d /home/temp/jad/dump com.vvhz.vlog.controller.UserController.*导出后,对应目录下会生成所有类的 .class 文件。
3. 结合其他命令排查问题
jad 常与 Arthas 其他命令配合,形成排查闭环:
先用
sc(search class)命令确认类是否已加载:sc com.example.service.UserService;若未加载,用
ognl命令触发加载:ognl 'Class.forName("com.example.service.UserService")';再用
jad反编译查看源码;若需修改代码,后续可通过
redefine命令热更新(需谨慎使用)。
五、常见问题排查
1. 提示“Class not found”
原因:类未加载到 JVM、全类名错误、类路径缺失;
解决方案:
核对全类名(必须包含完整包名,如
com.example.UserService而非UserService);触发类加载(如访问对应接口、调用静态方法、用
ognl Class.forName("全类名"));检查目标类的 JAR 包是否在服务的
classpath中。
2. 反编译的源码乱码
原因:Arthas 终端编码与类文件编码不一致;
解决方案:启动 Arthas 时指定编码,如
java -Dfile.encoding=UTF-8 -jar arthas-boot.jar。
3. 源码缺失方法体或语法错误
原因:类经过混淆(如 ProGuard 混淆)、字节码被篡改、高版本 Java 特性支持不足;
解决方案:
若为混淆类,仅能通过变量名和方法逻辑推测功能(无法完全还原);
升级 Arthas 到最新版本(优化了高版本 Java 特性支持);
用
-l参数显示行号,辅助定位语法错误位置。
4. 多类加载器场景下反编译错误版本
原因:未指定类加载器,
jad默认使用系统类加载器;解决方案:用
sc -d 全类名找到目标类的classLoaderHash,再用-c参数指定加载器。
六、线上使用注意事项
低侵入性,可放心使用:
jad仅读取 JVM 中类的字节码,不修改运行时状态、不阻塞业务线程,正常使用(单次反编译单个类)对服务无影响;避免高频批量执行:高并发场景下,批量反编译大量大类可能导致 Arthas 进程 CPU 短暂升高,建议避开业务峰值时段,按需反编译;
遵守合法性要求:反编译仅用于排查自身项目依赖的类,未经授权不得反编译商业软件(违反软件授权协议);
混淆类的局限性:若类经过混淆,反编译后的变量名、方法名会变成
a/b/c等无意义名称,可读性极差,需结合业务逻辑分析。
七、总结
Arthas 的 jad 命令是线上排查“类代码一致性”问题的终极工具,核心优势在于:
实时性:直接获取 JVM 运行时的真实字节码,避免本地环境与线上不一致;
便捷性:无需停服务、无需下载 JAR 包,交互终端直接操作;
灵活性:支持导出、过滤方法、指定类加载器等,适配复杂场景。
无论是确认线上代码版本、排查动态代理逻辑、验证类加载是否正确,jad 都能高效解决。