在线上问题排查中,我们可能会遇到代码运行没有按照预期去执行,或者是怀疑依赖的类版本不对、需要确认线上运行的代码是否是预期版本、或是排查动态代理类的真实逻辑,此时,我们就可以使用Arthas 的 jad 命令来实时反编译 JVM 中已加载的类,无需停服务、无需下载 JAR 包,直接获取运行时源码,是线上排查的核心工具之一。

点击查看官方教程

一、前置准备:安装并启动 Arthas

参考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 upload

3. 执行结果说明

输出内容为:

  • 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 提供了多个实用参数,可适配不同场景(如导出源码、过滤方法、指定类加载器),以下是高频参数详解:

参数

作用说明

示例命令

-d

反编译时将class dump到文件默认会dump到logback.xml中配置的log目录下,使用-d可以将文件dump到指定目录

jad -d /home/temp/jad/dump com.example.service.UserService

--lineNumber true

显示源码行号(与原始代码行号对应,方便关联日志报错,默认为true)

jad --lineNumber true com.example.service.UserService

<method>

只反编译指定方法(避免源码过长,提升效率)

jad com.example.service.UserService getUserById

-c <classLoaderHash>

指定类加载器(解决多类加载器冲突,如 Spring Boot 多模块、插件化项目)

jad -c 1b6d3586 com.example.service.UserService

-E

支持正则表达式匹配类名(批量反编译符合规则的类)

jad -E com.example.service.*Service(反编译所有以 Service 结尾的类)

--source-only

只输出源码

jad --source-only -f com.example.service.UserService

关键参数实操示例

(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 可能找不到类或反编译错误版本,步骤如下:

  1. 先用 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.
  1. -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 参数指定加载器。

六、线上使用注意事项

  1. 低侵入性,可放心使用jad 仅读取 JVM 中类的字节码,不修改运行时状态、不阻塞业务线程,正常使用(单次反编译单个类)对服务无影响;

  2. 避免高频批量执行:高并发场景下,批量反编译大量大类可能导致 Arthas 进程 CPU 短暂升高,建议避开业务峰值时段,按需反编译;

  3. 遵守合法性要求:反编译仅用于排查自身项目依赖的类,未经授权不得反编译商业软件(违反软件授权协议);

  4. 混淆类的局限性:若类经过混淆,反编译后的变量名、方法名会变成 a/b/c 等无意义名称,可读性极差,需结合业务逻辑分析。

七、总结

Arthas 的 jad 命令是线上排查“类代码一致性”问题的终极工具,核心优势在于:

  • 实时性:直接获取 JVM 运行时的真实字节码,避免本地环境与线上不一致;

  • 便捷性:无需停服务、无需下载 JAR 包,交互终端直接操作;

  • 灵活性:支持导出、过滤方法、指定类加载器等,适配复杂场景。

无论是确认线上代码版本、排查动态代理逻辑、验证类加载是否正确,jad 都能高效解决。

文章作者: Z
本文链接:
版权声明: 本站所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 微博客
工具 运维 基础
喜欢就支持一下吧