【Arthas】Arthas mc内存动态编译原理

2023-11-11

在这里插入图片描述

1.概述

转载:Arthas mc内存动态编译原理

2.开篇

Arthas支持通过mc命令进行java文件的内存动态编译,mc = Memory Compiler/内存编译器,编译.java文件生成.class

从JDK1.6开始引入了Java代码重写过的编译器接口,使得我们可以在运行时编译Java源代码,然后再通过类加载器将编译好的类加载进JVM,这种在运行时编译代码的操作就叫做动态编译

动态编译即支持从源码文件编译得到字节码文件(输入一个Java源文件,编译成字节码文件),又支持从源码字符串编译得到字节码文件(输入字符串源码,编译成字节码文件)。

3.动态编译

// 定义动态编译的输入对象
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<JavaFileManager>{

    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<? super JavaFileObject> diagnosticListener:诊断信息收集器
Iterable<String> options:编译器的配置
Iterable<String> classes:需要被 annotation processing 处理的类的类名
Iterable<? extends JavaFileObject> compilationUnits:要被编译的JavaFileObject

4.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<String, byte[]> byteCodes = dynamicCompiler.buildByteCodes();

            File outputDir = null;
            if (this.directory != null) {
                outputDir = new File(this.directory);
            } else {
                outputDir = new File("").getAbsoluteFile();
            }
            // 输出字节码到class文件中
            List<String> files = new ArrayList<String>();
            for (Entry<String, byte[]> 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<String> options = new ArrayList<String>();
    private final DynamicClassLoader dynamicClassLoader;
    // 保存待编译的源文件对应的JavaFileObject
    private final Collection<JavaFileObject> compilationUnits = new ArrayList<JavaFileObject>();

    public DynamicCompiler(ClassLoader classLoader) {
        // 动态编译的文件管理器
        standardFileManager = javaCompiler.getStandardFileManager(null, null, null);
        options.add("-Xlint:unchecked");
        // dynamicClassLoader负责保存编译后的字节码和class类对象
        dynamicClassLoader = new DynamicClassLoader(classLoader);
    }

    public Map<String, Class<?>> build() {

        JavaFileManager fileManager = new DynamicJavaFileManager(standardFileManager, dynamicClassLoader);
        DiagnosticCollector<JavaFileObject> collector = new DiagnosticCollector<JavaFileObject>();
        // 定义
        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<String, byte[]> buildByteCodes() {

        JavaFileManager fileManager = new DynamicJavaFileManager(standardFileManager, dynamicClassLoader);

        DiagnosticCollector<JavaFileObject> collector = new DiagnosticCollector<JavaFileObject>();
        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<JavaFileManager> {
    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<MemoryByteCode> byteCodes = new ArrayList<MemoryByteCode>();

    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<String, MemoryByteCode> byteCodes = new HashMap<String, MemoryByteCode>();

    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<String, Class<?>> getClasses() throws ClassNotFoundException {
        Map<String, Class<?>> classes = new HashMap<String, Class<?>>();
        for (MemoryByteCode byteCode : byteCodes.values()) {
            classes.put(byteCode.getClassName(), findClass(byteCode.getClassName()));
        }
        return classes;
    }

    public Map<String, byte[]> getByteCodes() {
        Map<String, byte[]> result = new HashMap<String, byte[]>(byteCodes.size());
        for (Entry<String, MemoryByteCode> entry : byteCodes.entrySet()) {
            result.put(entry.getKey(), entry.getValue().getByteCode());
        }
        return result;
    }
}

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

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【Arthas】Arthas mc内存动态编译原理 的相关文章

随机推荐

  • Windows7下安装docker记录

    docker火了也那么好几年了 偶才开始学习docker 说来真是落后主潮流太久 不过落后有落后的好处 因为大多数的坑都已经有人填过 所以遇见问题解决问题那也是相当的迅速 但就算是相当的迅速 这windows7下安装docker 也花了我大
  • java 算数

    public class Arith 提供精确加法计算的add方法 param value1 被加数 param value2 加数 return 两个参数的和 public static double add double value1
  • Spring cloud系列十五 使用线程池优化feign的http请求组件

    1 概述 在默认情况下 spring cloud feign在进行各个子服务之间的调用时 http组件使用的是jdk的HttpURLConnection 没有使用线程池 本文先从源码分析feign的http组件对象生成的过程 然后通过为fe
  • 深入理解web安全攻防策略

    前言 互联网时代 数据安全与个人隐私信息等受到极大的威胁和挑战 本文将以几种常见的攻击以及防御方法展开分析 1 XSS 跨站脚本攻击 定义 通过存在安全漏洞的Web网站注册用户的浏览器内运行非法的HTML标签或JavaScript进行的一种
  • VS视图菜单中找不到服务器资源管理器怎么办?

    http www cnblogs com SissyNong archive 2011 06 18 1981970 html 前几天同事安装了VS2010后 发现视图菜单中根本就没有服务器管理器这一项 如果想打开服务器管理器 都要使用快捷键
  • 区块链共识算法的发展现状与展望

    区块链共识算法的发展现状与展望 袁勇等 1 传统分布式一致性算法 2 主流区块链共识算法 3 共识算法的模型与分类 4 区块链共识算法的新进展 4 1 主线 1 PoW 与 PoS 算法的有机结合 4 2 主线 2 原生 PoS 算法的改进
  • 翻转数组

    题目描述 给定一个长度为n的整数数组a 元素均不相同 问数组是否存在这样一个片段 只将该片段翻转就可以使整个数组升序排列 其中数组片段 l r 表示序列a l a l 1 a r 原始数组为 a 1 a 2 a l 2 a l 1 a l
  • 数据挖掘顶级比赛---综合整理

    整理所有可以参加的数据挖掘顶级比赛 1 DrivenData https www drivendata org 2 CrowdANALYTIX https www crowdanalytix com solutions community
  • loss-FSCE 小样本识别

    FSCE Few Shot Object Detection via Contrastive Proposal Encoding 以Faster RCNN 作为小样本目标检测的基本框架 采用两阶段的训练方法 第一阶段的训练集是大量标注的基本
  • OpenCV4 视频目标检测 场景文本检测 手写数字识别 案例

    转载 一直想找本书 能在机器学习复杂的算法原理和高效的编程实战之间达到合适的平衡 让感兴趣的同学拿到就有能用的代码 还有基本原理的介绍 因为了解原理才知道什么时候用什么算法最合适 以及如何调整参数 一直没找到合适的 所以动手写了一本 p 拖
  • 片内外设、片上外设和片外外设的区别

    片内外设就是片上外设 同一种意思不同说法而已 片内外设和片外外设的区别 片内 外设是两个概念 片内指做成芯片的集成电路内部 简称片内 片外同理显而易见 外设是外部设备的简称 是指集成电路芯片外部的设备 集成电路芯片与外部设备的连接一般需要专
  • 2023.6.3 华为机试题小记(附c++题解)

    华为机试小记 导语 进阶题 堆积木 200分 思路 代码 基础题一 寻找最后一个匹配子序列的下标 100分 思路 代码 基础题二 种植白杨树 100分 思路 导语 机试一共三个题 分为两个基础题和一个进阶题 两个基础题各100分 进阶题20
  • 数字统计 题解(c++)

    先看题目 当然 你可以看原题 题目描述 请统计某个给定范围 L R L R L R 的所有整数中 数字 2 2 2 出现的次数 比如给定范围 2 22 2 22 2 22 数字 2 在数 2 中出现了 1 1 1 次 在数 12 中出现 1
  • matlab获取矩阵的行数与列数

    matlab里面与其他高级语言里面获取数据的长度length方法不一样 matlab里面通过size 矩阵变量 返回一个 行数m 列数n 比如一个m n的矩阵A 通过size A 可以得到 m n 通过size A 1 可以得到行数m 通过
  • 关于如何使用neo4j-admin工具批量导入已处理好的csv数据(neo4j 社区版 5.5)

    数据格式有两种 一个是节点 一个是关系 节点类型数据头格式 xxx ID name LABEL 关系类型数据头格式 START ID END ID TYPE 这里不多赘述关于csv数据处理的问题 可以通过搜索找相关资料 本文主要解决的问题是
  • LSTM原理图解

    在解释LSTM原理前先来理解一下RNN的原理 RNN基本原理 原理简介 当我们处理与事件发生的时间轴有关系的问题时 比如自然语言处理 文本处理 文字的上下文是有一定的关联性的 时间序列数据 如连续几天的天气状况 当日的天气情况与过去的几天有
  • sqli-labs(39关-53关)

    目录 第三十九关 第四十关 第四十一关 第四十二关 第四十三关 第四十四关 第四十五关 第四十六关 第四十七关 第四十八关 第四十九关 第五十关 第五十一关 第五十二关 第五十三关 第三十九关 id 1 and 1 1 id 1 and 1
  • 图像处理-双边滤波原理

    双边滤波 Bilateral filter 是一种可以去噪保边的滤波器 之所以可以达到此效果 是因为滤波器是由两个函数构成 一个函数是由几何空间距离决定滤波器系数 另一个由像素差值决定滤波器系数 原理示意图如下 双边滤波器中 输出像素的值依
  • Midjourney如何集成到自己(个人/企业)的平台(二)

    前面一篇写了需要准备东西 如何注册discord平台账号 如何登录discord创建个人服务器把Midjourney机器人授权添加到个人服务器中 并且开通订阅 这篇文章主要讲如何自定义机器人 设置自定义机器人 并授权添加到个人服务器中 1
  • 【Arthas】Arthas mc内存动态编译原理

    1 概述 转载 Arthas mc内存动态编译原理 2 开篇 Arthas支持通过mc命令进行java文件的内存动态编译 mc Memory Compiler 内存编译器 编译 java文件生成 class 从JDK1 6开始引入了Java