本文简单介绍 java 动态编译。
动态编译过程
相关类介绍 JavaCompiler JavaCompiler 为动态编译的入口。JavaCompiler的实现类,可以通过如下语句获取
1 JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
JavaFileObject 在编译过程中,通常需要容器存储 java源文件 和 class文件。JavaFileObject 就是这个容器。
JavaFileObject 有一个 kind 字段,分为以下四种:
1 2 3 4 5 6 enum Kind { SOURCE(".java" ), CLASS(".class" ), HTML(".html" ), OTHER("" ); }
DiagnosticCollector DiagnosticCollector 为编译的诊断器,当编译结果为失败时,可以通过该类获取 编译失败的原因.
1 2 3 4 5 6 7 8 9 10 11 private String simplifyDiagnostic (DiagnosticCollector<JavaFileObject> collector) { StringBuilder builder = new StringBuilder(); for (Diagnostic<? extends JavaFileObject> diagnostic : collector.getDiagnostics()) { builder.append("编译出错原因: " ) .append(diagnostic.getMessage(Locale.getDefault())).append("\n" ) .append(" 行数 " ).append(diagnostic.getLineNumber()).append("\n" ) .append(" 列数 " ).append(diagnostic.getColumnNumber()).append("\n" ) .append("\n" ); } return builder.toString(); }
JavaFileManager JavaFileManager 用来创建JavaFileObject,包括从特定位置输出和输入一个 JavaFileObject。
如: 在编译过程中,编译器会通过 JavaFileManager.getJavaFileForOutput() 方法创建一个 JavaFileObject 对象,然后将编译成功的 class 信息写入新生成的 JavaFileObject 中
在 jdk 中有个默认的 JavacFileManager 实现,它会将编译得到的 class 以本地文件的实现保存下来。
如果我们想要实现内存形式的JavaFileManager,我们可以继承 ForwardingJavaFileManager ,并重写 getJavaFileForOutput() 方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 public class MemJavaFileManager extends ForwardingJavaFileManager <JavaFileManager > { private Map<String, CharSequenceJavaFileObject> javaFileObjectMap; public MemJavaFileManager (JavaFileManager fileManager) { super (fileManager); javaFileObjectMap = new ConcurrentHashMap<>(); } @Override public JavaFileObject getJavaFileForOutput (Location location, String qualifiedClassName, JavaFileObject.Kind kind, FileObject sibling) { CharSequenceJavaFileObject javaFileObject = new CharSequenceJavaFileObject(qualifiedClassName, kind); javaFileObjectMap.put(qualifiedClassName, javaFileObject); return javaFileObject; } public Map<String, byte []> getClassMap() { Map<String, byte []> map = new HashMap<>(); javaFileObjectMap.forEach( (k, v) -> map.put(k, v.getCompiledBytes()) ); return map; } }
ClassLoader 在动态编译完成之后,我们需要将其加载到JVM中使用。因此我们需要自定义一个类加载器用于加载 class。
自定义的类加载器,牵扯到双亲委派机制。这个坑暂时先留着。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 package zl.compiler.hot;import java.util.Map;public class HotClassLoader extends ClassLoader { private Map<String, byte []> classMap; public HotClassLoader (Map<String, byte []> classMap) { this .classMap = classMap; } @Override protected Class<?> findClass(String name) throws ClassNotFoundException { byte [] bytes = classMap.get(name); if (bytes != null ) { return defineClass(name, bytes, 0 , bytes.length); } throw new ClassNotFoundException("源文件中找不到类:" + name); } }
实例 见 github https://github.com/longzl2015/compiler
动态编译内存泄露问题 1 2 3 4 Java 7 introduced this bug: in an attempt to speed up compilation, they introduced the SharedNameTable, which uses soft references to avoid reallocations, but unfortunately just causes the JVM to bloat out of control, as those soft references will never be reclaimed until the JVM hits its -Xmx memory limit. Allegedly it will be fixed in Java 9. In the meantime, there's an (undocumented) compiler option to disable it: -XDuseUnsharedTable.
https://stackoverflow.com/questions/14617340/memory-leak-when-using-jdk-compiler-at-runtime
参考资料 JavaCompilerAPI中文指南 使用 javax.tools 创建动态应用程序 从java源码到字节码