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 super JavaFileObject> diagnosticListener:诊断信息收集器
- Iterable options:编译器的配置
- Iterable classes:需要被 annotation processing 处理的类的类名
- Iterable extends JavaFileObject> 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动态编译】动态编译的应用
推荐阅读
- JAVA(抽象类与接口的区别&重载与重写&内存泄漏)
- 动态组件与v-once指令
- Java内存泄漏分析系列之二(jstack生成的Thread|Java内存泄漏分析系列之二:jstack生成的Thread Dump日志结构解析)
- iOS内存对齐原则
- iview|iview upload 动态改变上传参数
- react-navigation|react-navigation 动态修改 tabBar 样式
- K14|K14 9/15销售提问法D2
- jvm|【JVM】JVM08(java内存模型解析[JMM])
- 内存管理概念与原理以及解决办法
- c语言|一文搞懂栈(stack)、堆(heap)、单片机裸机内存管理malloc