Arthas|Arthas mc内存动态编译原理

系列

  • Arthas入门篇
  • Arthas功能介绍
  • Arthas 启动过程分析
  • Arthas使用Idea调试
  • Arthas Command处理流程
  • Arthas类查找和反编译原理
  • Arthas内存动态编译原理
  • Arthas动态重新加载类
  • Arthas导出加载类
  • Arthas classloader类加载器
开篇
  • 【Arthas|Arthas mc内存动态编译原理】Arthas支持通过mc命令进行java文件的内存动态编译,mc = Memory Compiler/内存编译器,编译.java文件生成.class。
  • 从JDK1.6开始引入了Java代码重写过的编译器接口,使得我们可以在运行时编译Java源代码,然后再通过类加载器将编译好的类加载进JVM,这种在运行时编译代码的操作就叫做动态编译。
  • 动态编译即支持从源码文件编译得到字节码文件(输入一个Java源文件,编译成字节码文件),又支持从源码字符串编译得到字节码文件(输入字符串源码,编译成字节码文件)。


动态编译
// 定义动态编译的输入对象 public class SourceJavaFileObject extends SimpleJavaFileObject { private String source; //源码字符串//返回源码字符串 public SourceJavaFileObject(String name, String sourceStr){ super(URI.create("String:///" + name + Kind.SOURCE.extension),Kind.SOURCE); this.source = sourceStr; }@Override public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException{ if(source == null) throw new IllegalArgumentException("source == null"); else return source; } }

  • SimpleJavaFileObject实现JavaFileObject接口,构造函数用来输入源代码。
  • SimpleJavaFileObject的getCharContent需要重载构建java文件的输入。
// 定义动态编译的输出对象 public static class ClassJavaFileObject extends SimpleJavaFileObject {private ByteArrayOutputStream byteArrayOutputStream; //字节数组输出流 // 编译完成后会回调OutputStream,回调成功后, // 我们就可以通过下面的getByteCode()方法获取编译后的字节码字节数组public ClassJavaFileObject(String name, Kind kind){ super(URI.create("String:///" + name + kind.extension), kind); source = null; }@Override public OutputStream openOutputStream() throws IOException { byteArrayOutputStream = new ByteArrayOutputStream(); return byteArrayOutputStream; }//将输出流中的字节码转换为字节数组 public byte[] getCompiledBytes() { return byteArrayOutputStream.toByteArray(); } }

  • SimpleJavaFileObject实现JavaFileObject接口,构造函数用来输出源代码。
  • SimpleJavaFileObject的openOutputStream需要重载,用来输出class文件字节流。
// 定义动态编译管理Manager对象 public static class MyJavaObjectManager extends ForwardingJavaFileManager{private ClassFileObject classObject; //我们自定义的JavaFileObject//重写该方法,使其返回我们的ClassJavaFileObject @Override public JavaFileObject getJavaFileForOutput(Location location, String className, JavaFileObject.Kind kind, FileObject sibling) throws IOException {classObject= new ClassJavaFileObject(className, kind); return classObject; } }

  • 文件管理器JavaFileManager的getJavaFileForOutput返回类输出对象。
  • MyJavaObjectManager需要重载getJavaFileForOutput,返回字节流输出对象。
// 串联整体源码动态编译过程 public class CompileFileToFile {public static void main(String[] args) { //获取系统Java编译器 JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); // 获取源码文件的SourceJavaFileObject SourceJavaFileObject sourceFileObject = new SourceJavaFileObject("name","source code"); // 执行源码编译 Boolean result = compiler.getTask(null,new MyJavaObjectManager(), null, null, null, Arrays.asList(sourceFileObject)).call(); }

编译任务getTask()这个方法一共有 6 个参数,它们分别是:
  • Writer out:编译器的一个额外的输出 Writer,为 null 的话就是 System.err
  • JavaFileManager fileManager:文件管理器
  • DiagnosticListener diagnosticListener:诊断信息收集器
  • Iterable options:编译器的配置
  • Iterable classes:需要被 annotation processing 处理的类的类名
  • Iterable compilationUnits:要被编译的JavaFileObject


Arthas动态编译
public class MemoryCompilerCommand extends AnnotatedCommand {@Override public void process(final CommandProcess process) { RowAffect affect = new RowAffect(); try { Instrumentation inst = process.session().getInstrumentation(); // 定义ClassLoader对象 ClassLoader classloader = null; if (hashCode == null) { classloader = ClassLoader.getSystemClassLoader(); } else { classloader = ClassLoaderUtils.getClassLoader(inst, hashCode); if (classloader == null) { process.end(-1, "Can not find classloader with hashCode: " + hashCode + "."); return; } }// 定义DynamicCompiler的对象 DynamicCompiler dynamicCompiler = new DynamicCompiler(classloader); Charset charset = Charset.defaultCharset(); if (encoding != null) { charset = Charset.forName(encoding); }// 待编译的java源文件 for (String sourceFile : sourcefiles) { // 将待编译的java文件编译成class源文件 String sourceCode = FileUtils.readFileToString(new File(sourceFile), charset); String name = new File(sourceFile).getName(); if (name.endsWith(".java")) { name = name.substring(0, name.length() - ".java".length()); } // 添加到动态编译器的待编译源文件中 dynamicCompiler.addSource(name, sourceCode); }// 执行动态编译 Map byteCodes = dynamicCompiler.buildByteCodes(); File outputDir = null; if (this.directory != null) { outputDir = new File(this.directory); } else { outputDir = new File("").getAbsoluteFile(); } // 输出字节码到class文件中 List files = new ArrayList(); for (Entry entry : byteCodes.entrySet()) { File byteCodeFile = new File(outputDir, entry.getKey().replace('.', '/') + ".class"); FileUtils.writeByteArrayToFile(byteCodeFile, entry.getValue()); files.add(byteCodeFile.getAbsolutePath()); affect.rCnt(1); } } catch (Throwable e) { // 省略相关代码 } } }

  • 加载java文件后解析生成字节流
  • 添加字节流到动态编译器的待编译源当中
  • 执行动态编译并输出class文件
  • DynamicCompiler是动态编译器
public class DynamicCompiler { // 定义动态编译器JavaCompiler private final JavaCompiler javaCompiler = ToolProvider.getSystemJavaCompiler(); // 动态编译的自定义文件管理器,需要自定义 private final StandardJavaFileManager standardFileManager; // 保存动态编译的options private final List options = new ArrayList(); private final DynamicClassLoader dynamicClassLoader; // 保存待编译的源文件对应的JavaFileObject private final Collection compilationUnits = new ArrayList(); public DynamicCompiler(ClassLoader classLoader) { // 动态编译的文件管理器 standardFileManager = javaCompiler.getStandardFileManager(null, null, null); options.add("-Xlint:unchecked"); // dynamicClassLoader负责保存编译后的字节码和class类对象 dynamicClassLoader = new DynamicClassLoader(classLoader); }public Map> build() {JavaFileManager fileManager = new DynamicJavaFileManager(standardFileManager, dynamicClassLoader); DiagnosticCollector collector = new DiagnosticCollector(); // 定义 JavaCompiler.CompilationTask task = javaCompiler.getTask(null, fileManager, collector, options, null, compilationUnits); try { // 执行动态编译 if (!compilationUnits.isEmpty()) { boolean result = task.call(); } // 返回动态编译的结果 return dynamicClassLoader.getClasses(); } catch (Throwable e) { } finally { }}public Map buildByteCodes() {JavaFileManager fileManager = new DynamicJavaFileManager(standardFileManager, dynamicClassLoader); DiagnosticCollector collector = new DiagnosticCollector(); JavaCompiler.CompilationTask task = javaCompiler.getTask(null, fileManager, collector, options, null, compilationUnits); try { // 返回编译的字节码 return dynamicClassLoader.getByteCodes(); } catch (ClassFormatError e) { } finally { } }public void addSource(String className, String source) { // 添加待编译的源码字节流 addSource(new StringSource(className, source)); }public void addSource(JavaFileObject javaFileObject) { compilationUnits.add(javaFileObject); } }

  • DynamicCompiler用来实现源码字节流动态编译的核心对象,build实现字节码编译。
  • DynamicJavaFileManager文件管理器,DynamicClassLoader保存字节流。
  • 通过DynamicClassLoader.getClasses()返回class类对象。
  • 通过DynamicClassLoader.getByteCodes()返回字节流对象。
public class StringSource extends SimpleJavaFileObject { private final String contents; public StringSource(String className, String contents) { super(URI.create("string:///" + className.replace('.', '/') + Kind.SOURCE.extension), Kind.SOURCE); this.contents = contents; }@Override public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException { return contents; }}

  • StringSource定义输入的源文件java文件对象。
public class MemoryByteCode extends SimpleJavaFileObject { private static final char PKG_SEPARATOR = '.'; private static final char DIR_SEPARATOR = '/'; private static final String CLASS_FILE_SUFFIX = ".class"; private ByteArrayOutputStream byteArrayOutputStream; public MemoryByteCode(String className) { super(URI.create("byte:///" + className.replace(PKG_SEPARATOR, DIR_SEPARATOR) + Kind.CLASS.extension), Kind.CLASS); }public MemoryByteCode(String className, ByteArrayOutputStream byteArrayOutputStream) throws URISyntaxException { this(className); this.byteArrayOutputStream = byteArrayOutputStream; }@Override public OutputStream openOutputStream() throws IOException { if (byteArrayOutputStream == null) { byteArrayOutputStream = new ByteArrayOutputStream(); } return byteArrayOutputStream; }public byte[] getByteCode() { return byteArrayOutputStream.toByteArray(); }public String getClassName() { String className = getName(); className = className.replace(DIR_SEPARATOR, PKG_SEPARATOR); className = className.substring(1, className.indexOf(CLASS_FILE_SUFFIX)); return className; } }

  • MemoryByteCode定义源码字节流输出文件对象。
public class DynamicJavaFileManager extends ForwardingJavaFileManager { private static final String[] superLocationNames = { StandardLocation.PLATFORM_CLASS_PATH.name(), /** JPMS StandardLocation.SYSTEM_MODULES **/ "SYSTEM_MODULES" }; private final PackageInternalsFinder finder; private final DynamicClassLoader classLoader; private final List byteCodes = new ArrayList(); public DynamicJavaFileManager(JavaFileManager fileManager, DynamicClassLoader classLoader) { super(fileManager); this.classLoader = classLoader; finder = new PackageInternalsFinder(classLoader); }@Override public JavaFileObject getJavaFileForOutput(JavaFileManager.Location location, String className, JavaFileObject.Kind kind, FileObject sibling) throws IOException {for (MemoryByteCode byteCode : byteCodes) { if (byteCode.getClassName().equals(className)) { return byteCode; } }MemoryByteCode innerClass = new MemoryByteCode(className); byteCodes.add(innerClass); classLoader.registerCompiledSource(innerClass); return innerClass; } }

  • 重载的getJavaFileForOutput返回字节流输出对象MemoryByteCode,通过反射会将对应的字节流保存在MemoryByteCode当中。
  • byteCodes用来保存类的字节流对象,用到了java当中引用对象概念。
public class DynamicClassLoader extends ClassLoader { private final Map byteCodes = new HashMap(); public DynamicClassLoader(ClassLoader classLoader) { super(classLoader); }public void registerCompiledSource(MemoryByteCode byteCode) { byteCodes.put(byteCode.getClassName(), byteCode); }@Override protected Class findClass(String name) throws ClassNotFoundException { MemoryByteCode byteCode = byteCodes.get(name); if (byteCode == null) { return super.findClass(name); } // 通过父类的defineClass来加载类 return super.defineClass(name, byteCode.getByteCode(), 0, byteCode.getByteCode().length); }public Map> getClasses() throws ClassNotFoundException { Map> classes = new HashMap>(); for (MemoryByteCode byteCode : byteCodes.values()) { classes.put(byteCode.getClassName(), findClass(byteCode.getClassName())); } return classes; }public Map getByteCodes() { Map result = new HashMap(byteCodes.size()); for (Entry entry : byteCodes.entrySet()) { result.put(entry.getKey(), entry.getValue().getByteCode()); } return result; } }

  • getByteCodes返回保存的字节流对象。
  • getClasses返回字节流对应的类对象。
  • findClass负责加载类。


参考文章
  • 【Java动态编译】动态编译的应用

    推荐阅读