Arthas mc内存动态编译原理

系列

  • Arthas入门篇
  • Arthas功能介绍
  • Arthas 启动过程分析
  • Arthas使用Idea调试
  • Arthas Command处理流程
  • Arthas类查找和反编译原理
  • Arthas内存动态编译原理
  • Arthas动态重新加载类
  • Arthas导出加载类
  • Arthas classloader类加载器

开篇

  • 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动态编译】动态编译的应用

你可能感兴趣的:(Arthas mc内存动态编译原理)