尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Arthas mc 命令深度解析:内存编译器与 retransform 热更新实战

Arthas mc 命令深度解析:内存编译器与 retransform 热更新实战 Arthas mc 命令深度解析内存编译器与 retransform 热更新实战【免费下载链接】arthasAlibaba Java Diagnostic Tool Arthas/Alibaba Java诊断利器Arthas项目地址: https://gitcode.com/gh_mirrors/ar/arthasmcMemory Compiler内存编译器是 Arthas 提供的在线编译工具它直接在运行中的 JVM 内把.java源文件编译为.class字节码并可与retransform命令配合实现线上代码热更新全程无需重启应用。本文以 Arthas 官方 mc 文档为骨架结合仓库源码MemoryCompilerCommand、DynamicCompiler逐层拆解命令的参数语义、ClassLoader 解析逻辑与编译失败时的降级方案帮助你在真实生产场景中安全、精准地完成改代码 → 编译 → 热替换的完整闭环。mc 命令速览按照官方文档的定义Memory compiler, compiles.javafiles into.classfiles in memory.mc 会把用户提供的.java源文件在内存中编译成.class字节码。与本地javac不同的是编译过程发生在被诊断的 JVM 进程内且默认使用该进程中某个目标 ClassLoader 作为父加载器进行编译从而能够解析到应用自身的类最终产物可以落地为.class文件供retransform命令加载替换。该命令在源码中的注册与定义位于 MemoryCompilerCommand.java通过Name(mc)声明命令名并通过 BuiltinCommandPack.java 注册为内置命令。命令支持参数、ClassLoader 选择、编码与输出目录控制对应字段为参数说明源码定义sourcefiles位置参数待编译的.java源文件可传多个setClassPattern-c/--classloader指定目标 ClassLoader 的 hashCodesetHashCode--classLoaderClass按类名指定目标 ClassLoadersetClassLoaderClass--encoding源文件编码setEncoding-d/--directory编译产物的输出目录setDirectory基本用法编译单个源文件最简单的用法是直接传入源文件路径mc /tmp/Test.java命令会读取/tmp/Test.java的源码在内存中完成编译。若未指定 ClassLoader默认使用ClassLoader.getSystemClassLoader()作为编译时的父加载器见 MemoryCompilerCommand.java 中hashCode null的分支。未指定-d时产物默认输出到 Arthas 当前工作目录new File().getAbsoluteFile()见 MemoryCompilerCommand.java并按类名生成包路径输出目录/包路径/类名.class命令结束后会输出受影响行数统计例如Affect(row-cnt:1) cost in 346 ms从源码看写入每个.class文件时affect.rCnt(1)会累加计数最终通过RowAffectModel输出见 MemoryCompilerCommand.java。指定目标 ClassLoaderArthas 是借助 Java Instrumentation 实现的诊断工具目标应用中的类由各自的 ClassLoader 加载。编译出的字节码要能被后续的retransform正确替换编译时就必须贴着正确的 ClassLoader 上下文否则可能出现找不到依赖类或加载器不匹配的问题。通过-c指定 hashCode-c参数接收 ClassLoader 的 hashCode十六进制字符串。该值可以通过classloader命令查询mc -c 327a647b /tmp/Test.java底层通过ClassLoaderUtils.getClassLoader(inst, hashCode)在 Instrumentation 提供的已加载类中反查 ClassLoader若找不到命令会直接报错Can not find classloader with hashCode: 327a647b.对应 MemoryCompilerCommand.java 的分支。通过--classLoaderClass按类名指定--classLoaderClass允许直接传入 ClassLoader 的类名例如 Spring Boot 应用常见的启动加载器mc --classLoaderClass org.springframework.boot.loader.LaunchedURLClassLoader /tmp/UserController.java -d /tmp此时命令输出编译产物路径例如Memory compiler output: /tmp/com/example/demo/arthas/user/UserController.class Affect(row-cnt:1) cost in 346 ms从源码看--classLoaderClass的解析逻辑位于 MemoryCompilerCommand.java存在三种分支恰好匹配一个自动取其 hashCode 转为十六进制字符串作为-c值继续执行匹配多个命令报错并提示必须用-c精确指定Found more than one classloader by class name, please specify classloader with -c classloader hash一个都没有报错Can not find classloader by class name: className.由此可见当同一类名的 ClassLoader 在应用中出现多实例时例如多个 Web 应用上下文--classLoaderClass无法唯一定位必须退回使用-c加上具体 hashCode。这也解释了官方文档中同时提供两种参数的原因--classLoaderClass方便、-c精确。指定输出目录与一次编译多个文件-d参数用于指定.class产物的输出目录并支持一次传入多个源文件mc -d /tmp/output /tmp/ClassA.java /tmp/ClassB.java该命令会将/tmp/ClassA.java与/tmp/ClassB.java一并编译产物分别写入/tmp/output下各自的包路径。多文件同时编译尤其适合源文件之间存在相互引用同一个包内互相调用的场景——它们会在同一次DynamicCompiler.buildByteCodes()调用中被统一编译避免单文件编译时的依赖缺失问题。与 retransform 组合实现热更新mc 的典型使用链路是编译 热替换获取目标类的源码可通过jad反编译或直接在服务器上放置修改后的源文件用mc编译得到.class文件用retransform命令将新字节码加载进 JVM替换已加载的类定义。# 1. 编译 mc -d /tmp /tmp/UserController.java # 2. 热更新 retransform /tmp/com/example/demo/arthas/user/UserController.class完整的retransform使用说明见 retransform 命令文档。值得强调的是retransform只能替换已有类的方法体实现不能新增/删除方法或字段、不能变更类的继承关系因此 mc 编译出的类需要与被替换的类保持结构一致否则替换可能失败——这也是官方文档提示编译失败时可在本地编译好后上传的原因之一。底层实现原理内存编译的核心DynamicCompilermc 命令本身并不直接调用javac而是委托给 Arthas 独立模块 memorycompiler 中的DynamicCompiler。从源码结构看它基于javax.tools标准编译器 API 实现核心要素包括ToolProvider.getSystemJavaCompiler()获取 JDK 自带的编译器入口。注意如果目标 JVM 运行在 JRE而非 JDK环境中这里会直接抛出IllegalStateException提示Can not load JavaCompiler from javax.tools.ToolProvider#getSystemJavaCompiler(), please confirm the application running in JDK not JRE.因此使用 mc 的前提是目标进程运行在 JDK 上见 DynamicCompiler.java。编译选项固定注入-Xlint:unchecked输出 unchecked 警告与-g生成调试信息便于后续jad/watch等命令展示行号见构造函数中的options.add(...)。DynamicClassLoader以传入的目标 ClassLoader 为父加载器编译产物可在内存中直接加载build()方法返回MapString, Class?内存加载而 mc 命令使用的是buildByteCodes()返回MapString, byte[]字节码数组供写盘输出。诊断收集DiagnosticCollectorJavaFileObject收集编译诊断其中 WARNING/NOTE 归入警告列表ERROR 归入错误列表一旦存在错误则抛出DynamicCompilerException最终由 mc 命令捕获并提示查看日志见 DynamicCompiler.java。mc 命令的执行链路mc 的完整处理流程见 MemoryCompilerCommand.java可以概括为解析 ClassLoader 选择参数-c/--classLoaderClass/ 默认系统加载器用指定 ClassLoader 构造DynamicCompiler读取每个源文件内容默认使用 JVM 默认字符集若传了--encoding则按指定编码读取Charset.forName(encoding)文件名去掉.java后缀后作为类名注册到编译单元调用buildByteCodes()完成内存编译确定输出目录-d指定或当前工作目录按包路径/类名.class写盘返回MemoryCompilerModel产物文件列表与RowAffectModel影响行数。其中命令结果的输出模型定义在 MemoryCompilerModel.java其getType()返回mc渲染逻辑位于 MemoryCompilerView.java。此外mc 命令还实现了文件路径的 Tab 补全CompletionUtils.completeFilePath见 MemoryCompilerCommand.java交互式输入源文件路径时可以按 Tab 自动补全。编译失败的处理与排查官方文档特别强调注意mc 命令有可能失败。如果编译失败可以在本地编译好.class文件再上传到服务器。具体参考 retransform 命令说明。常见的失败场景包括目标进程是 JRE缺少编译器入口mc 直接不可用源文件本身有语法或类型错误编译诊断中的 ERROR 会触发失败依赖类无法解析选错了 ClassLoader导致源码中引用的类在编译上下文中不可见类结构被改动即使编译成功retransform也可能因方法签名变化等原因替换失败。失败时的兜底方案是在本地开发环境用javac编译出.class文件注意保持与线上一致的 JDK 版本与依赖上传到服务器后再执行retransform绕开线上编译的不确定性。此外mc 命令在捕获到编译异常时会输出错误信息并提示查看 Arthas 日志Memory compiler error, exception message: msg, please check $HOME/logs/arthas/arthas.log for more details.对应的异常捕获与日志记录位于 MemoryCompilerCommand.java排查时可结合$HOME/logs/arthas/arthas.log中的Memory compiler error堆栈进一步定位。小结mc 是 Arthas 命令体系中衔接代码修改与热更新的关键一环它把.java源码在目标 JVM 内即时编译为.class通过-c/--classLoaderClass精确绑定 ClassLoader 上下文借助-d控制产物落盘位置最终配合retransform完成不停机热替换。理解其底层对javax.tools编译器 API 与DynamicClassLoader的使用能帮助你准确判断编译失败根因并正确选择线上编译与本地编译上传两条路径。【免费下载链接】arthasAlibaba Java Diagnostic Tool Arthas/Alibaba Java诊断利器Arthas项目地址: https://gitcode.com/gh_mirrors/ar/arthas创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表