1 A Retargetable JIT Compiler for Java
动态的JavaJavaCompilerAPI中文指南

动态的JavaJavaCompilerAPI中文指南动态的Java - 无废话JavaCompilerAPI中文指南Sep 24th, 2013 | CommentsJavaCompiler API1.6之后JDK提供了一套compiler API,定义在JSR199中, 提供在运行期动态编译java代码为字节码的功能简单说来,这一套API就好比是在java程序中模拟javac程序,将java源文件编译为class文件;其提供的默认实现也正是在文件系统上进行查找、编译工作的,用起来感觉与javac基本一致;不过,我们可以通过一些关键类的继承、方法重写和扩展,来达到一些特殊的目的,常见的就是“与文件系统解耦”(就是在内存或别的地方完成源文件的查找、读取和class编译)需要强调的是,compiler API的相关实现被放在tools.jar中,JDK默认会将tools.jar放入classpath而jre没有,因此如果发现compiler API相关类找不到,那么请检查一下tools.jar是否已经在classpath中;当然我指的是jdk1.6以上的版本提供的tools.jar包基本使用一个基本的例子public static CompilationResult compile(String qualifiedName, String sourceCode,Iterable<? extends Processor> processors) {JavaStringSource source = new JavaStringSource(qualifiedName, sourceCode);List<JavaStringSource> ss = Arrays.asList(source);List<String> options = Arrays.asList("-classpath", HotCompileConstants.CLASSPATH);JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();MemClsFileManager fileManager = null;Map<String, JavaMemCls> clses = new HashMap<String, JavaMemCls>();Map<String, JavaStringSource> srcs = new HashMap<String, JavaStringSource>();srcs.put(source.getClsName(), source);try {fileManager = new MemClsFileManager(compiler.getStandardFileManager(null, null, null), clses, srcs);DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<JavaFileObject>();StringWriter out = new StringWriter();CompilationTask task = compiler.getTask(out, fileManager, diagnostics, options, null, ss);if (processors != null) task.setProcessors(processors);boolean sucess = task.call();if (!sucess) {for (Diagnostic<? extends JavaFileObject> diagnostic : diagnostics.getDiagnostics()) {out.append("Error on line " + diagnostic.getLineNumber() + " in " + diagnostic).append('\n');}return new CompilationResult(out.toString());}} finally {try {fileManager.close();} catch (Exception e) {throw new RuntimeException(e);}}// every parser class should be loaded by a new specific class loaderHotCompileClassLoader loader = new HotCompileClassLoader(Util.getParentClsLoader(), clses);Class<?> cls = null;try {cls = loader.loadClass(qualifiedName);} catch (ClassNotFoundException e) {throw new RuntimeException(e);}return new CompilationResult(cls, loader);}解释一下这段程序:这个static方法提供这样一种功能:输入希望的类名和String形式的java代码内容,动态编译并返回编译好的class对象; 其中CompilationResult只是一个简单的pojo封装,用于包装返回结果和可能的错误信息类名和源码首先被包装成一个JavaStringSource对象, 该对象继承自javax.tools.SimpleJavaFileObject类,是compiler API对一个“Java文件”(即源文件或class文件)的抽象;将源文件包装成这个类也就实现了“将java源文件放在内存中”的想法JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();这是取得一个默认的JavaCompiler工具的实例由于我打算将编译好的class文件直接存放在内存中,因此我自定义了一个MemClsFileManager:public class MemClsFileManager extendsForwardingJavaFileManager<StandardJavaFileManager> {private Map<String, JavaMemCls> destFiles;private Map<String, JavaStringSource> srcFiles;protected MemClsFileManager(StandardJavaFileManager fileManager, Map<String, JavaMemCls> destFiles,Map<String, JavaStringSource> srcFiles){super(fileManager);this.destFiles = destFiles;this.srcFiles = srcFiles;}public JavaFileObject getJavaFileForOutput(Location location, String className, Kind kind, FileObject sibling) throws IOException {if (!(Kind.CLASS.equals(kind) && StandardLocation.CLASS_OUTPUT.equals(location))) return super.getJavaFileForOutput(location,className,kind,sibling);if (destFiles.containsKey(className)) {return destFiles.get(className);} else {JavaMemCls file = new JavaMemCls(className);this.destFiles.put(className, file);return file;}}public void close() throws IOException {super.close();this.destFiles = null;}public Iterable<JavaFileObject> list(Location location, String packageName, Set<Kind> kinds, boolean recurse)throws IOException {List<JavaFileObject> ret = new ArrayList<JavaFileObject>();if ((StandardLocation.CLASS_OUTPUT.equals(location) || StandardLocation.CLASS_PATH.equals(location))&& kinds.contains(Kind.CLASS)) {for (Map.Entry<String, JavaMemCls> e : destFiles.entrySet()) {String pkgName = resolvePkgName(e.getKey());if (recurse) {if (pkgName.contains(packageName)) ret.add(e.getValue());} else {if (pkgName.equals(packageName)) ret.add(e.getValue());}}} else if (StandardLocation.SOURCE_PATH.equals(location) && kinds.contains(Kind.SOURCE)) {for (Map.Entry<String, JavaStringSource> e : srcFiles.entrySet()) {String pkgName = resolvePkgName(e.getKey());if (recurse) {if (pkgName.contains(packageName)) ret.add(e.getValue());} else {if (pkgName.equals(packageName)) ret.add(e.getValue());}}}// 也包含super.listIterable<JavaFileObject> superList = super.list(location, packageName, kinds, recurse);if (superList != null) for (JavaFileObject f : superList)ret.add(f);return ret;}private String resolvePkgName(String fullQualifiedClsName) {return fullQualifiedClsName.substring(0, stIndexOf('.'));}public String inferBinaryName(Location location, JavaFileObject file) {if (file instanceof JavaMemCls) {return ((JavaMemCls) file).getClsName();} else if (file instanceof JavaStringSource) {return ((JavaStringSource) file).getClsName();} else {return super.inferBinaryName(location, file);}}}其中最主要的步骤就是重写了getJavaFileForOutput方法,使其使用内存中的map来作为生成文件(class文件)的输出位置CompilationTask task = compiler.getTask(out, fileManager, diagnostics, options, null, ss);boolean sucess = task.call();上面这两行是创建了一个编译task,并调用最后使用自定义ClassLoader来加载编译好的类并返回:HotCompileClassLoader loader = new HotCompileClassLoader(Util.getParentClsLoader(), clses);Class<?> cls = null;try {cls = loader.loadClass(qualifiedName);} catch (ClassNotFoundException e) {throw new RuntimeException(e);}return new CompilationResult(cls, loader);而该ClassLoader的实现关键在于“到内存中(即之前存放编译好的class的map中)加载字节码”:public class HotCompileClassLoader extends ClassLoader { private Map<String, JavaMemCls> inMemCls;public HotCompileClassLoader(ClassLoader parent, Map<String, JavaMemCls> clses){super(parent);this.inMemCls = clses;}protected Class<?> findClass(String name) throws ClassNotFoundException {byte[] b = this.inMemCls.get(name).getClsBytes();return defineClass(name, b, 0, b.length);}}之后只要调用方法compile(className, source, null)这样就算完成了一个基本的、不依赖实际的文件系统的动态编译过程JavaFileManager的意义一个广义的、管理“文件”资源的接口,并不一定指“操作系统的磁盘文件系统”其实JavaFileManager只是一个接口,只要行为正确,那么就无所谓“文件”到底以何种形式、实际被存放在哪里StandardJavaFileManager的默认行为这是基于磁盘文件的JavaFileManager实现, 所有的文件查找、新文件输出位置都在磁盘上完成;也就是说,如果直接使用默认的StandardJavaFileManager来做动态编译,那么得到的效果就跟命令行中直接使用javac编译差不多继承ForwardingJavaFileManager类,让compiler API脱离对文件系统的依赖如果想要将编译好的class文件放在内存中而不是磁盘上,那么需要使用一个ForwardingJavaFileManager来包装默认的StandardJavaFileManager并重写getJavaFileForOutput方法,将其实现改为内存操作;这个实现可参考上面的MemClsFileManager 类不过ForwardingJavaFileManager还有许多别的方法,没有文档说明动态编译过程中到底那些方法会被调用,原则上讲,所有方法都有可能被调用但具体哪些方法被调用了可以被实测出来,这样可以有选择性地重写其中一些方法比如MemClsFileManager中,重写了inferBinaryName, list,close, getJavaFileForOutput方法,因为这些方法都会被“class文件放在内存中”这一策略所影响,所以需要兼容JavaCompiler Tree API下面想介绍的其实是另一个更进一步的话题:如何在动态编译的过程中分析被编译的源代码因为几乎所有打算用到java动态编译的应用场景,都会想要对被编译的代码做一些检查、review,以防编译运行了让人出乎意料的代码从而对系统造成破坏那么这个事情,除了人工review之外,一些简单的验证,完全可以在编译期自动地做到;而JavaCompiler Tree API就是用来做这个静态编译期检查的它的基本思路很简单,就是hook进编译的过程中,在java源码被parse成AST的时候,使用visitor模式对该AST做遍历分析,以找出你需要定位的语法结构,这样来达到验证目的; 比如”如果发现assert语句就报错”或“不允许定义嵌套类”这样的检查都很容易做这之中的关键,当然是java的AST和其对应的visitor的实现了Java的AST:/javase/7/docs/jdk/api/javac/tree/com/sun/source/tree/package-summary.html上述链接给出了java的AST类结构,所有语法元素的节点都有而对应的visitor接口TreeVisitor<R,P>与AST形成了标准的visitor模式TreeVisitor<R,P>的默认实现及其用途:1.SimpleTreeVisitor: 简单的、“消极”的visitor实现,当访问一个分支节点时不会默认“往下”继续遍历它的所有子节点, 若想要遍历所有子节点需要继承、编码实现2.TreeScanner: 默认会遍历所有子节点的“积极”的visitor实现,还能让当前节点的遍历逻辑取到上一个被访问的邻接兄弟节点被访问的返回结果(换句话说能够拿到最近的、刚才被访问过的兄弟节点对象,或其它等效的自定义返回值)3.TreePathScanner: 跟TreeScanner一样“积极”,且除了能拿到前一个邻接兄弟节点的访问返回结果外,还能拿到父亲节点(这样来追踪该节点的路径)有了上面这个visitor模式的脚手架,我们就能通过实现一个visitor来达到对java源码的分析了比如下面这个visitor, 它继承自TreePathScanner(ExprCodeChecker是我自定义的一个TreePathScanner的子类):public class ForbiddenStructuresChecker extends ExprCodeChecker<Void, FbdStructContext> {private StringBuilder errMsg = new StringBuilder();public String getErrorMsg() {return this.errMsg.toString();}public FbdStructContext getInitParam() {return new FbdStructContext();}/*** 禁止定义内部类*/public Void visitClass(ClassTree node, FbdStructContext p) { if (p.isInClass) {// 已经位于另一个外层class定义中了,直接报错返回,不再继续遍历子节点this.errMsg.append("Nested class is not allowed in api expressions. Position: " + resolveRowAndCol(node)).append('\n');return null;} else {boolean oldInClass = p.isInClass;p.isInClass = true;// 继续遍历子节点super.visitClass(node, p);p.isInClass = oldInClass;return null;}}/*** 禁止定义'assert'语句*/public Void visitAssert(AssertTree node, FbdStructContext p) {this.errMsg.append("Assertions are not allowed in api expressions. Position: " + this.resolveRowAndCol(node)).append('\n');return null;}/*** 禁止定义goto(break or continue followed by a label)语句*/public Void visitBreak(BreakTree node, FbdStructContext p) { if (node.getLabel() != null) {this.errMsg.append("'break' followed by a label is not allowed in api expressions. Position: "+ this.resolveRowAndCol(node)).append('\n');return null;} else {return super.visitBreak(node, p);}}public Void visitContinue(ContinueTree node, FbdStructContext p) {if (node.getLabel() != null) {this.errMsg.append("'continue' followed by a label is not allowed in api expressions. Position: "+ this.resolveRowAndCol(node)).append('\n');return null;} else {return super.visitContinue(node, p);}}// *************禁止定义goto end*************/*** 禁止定义死循环,for/while/do-while loop, 只限制常量类型的循环条件造成的明显死循环; 这种静态校验是不完善的,要做完善很复杂,没必要加大投入;若要做到更精确的控制应从动态期方案的方向考虑*/public Void visitDoWhileLoop(DoWhileLoopTree node, FbdStructContext p) {boolean condTemp = p.isConstantTrueCondition;boolean isLoopExpTemp = p.isLoopConditionExpr;p.isLoopConditionExpr = true;node.getCondition().accept(this, p);if (p.isConstantTrueCondition) {// 死循环this.errMsg.append("Dead loop is not allowed in api expressions. Position: " + this.resolveRowAndCol(node)).append('\n');}p.isConstantTrueCondition = condTemp;p.isLoopConditionExpr = isLoopExpT emp;return super.visitDoWhileLoop(node, p);}public Void visitForLoop(ForLoopTree node, FbdStructContext p) {if (node.getCondition() == null) {// 无条件,相当于'true'this.errMsg.append("Dead loop is not allowed in api expressions. Position: " + this.resolveRowAndCol(node)).append('\n');} else {boolean condTemp = p.isConstantTrueCondition;boolean isLoopExpTemp = p.isLoopConditionExpr;p.isLoopConditionExpr = true;node.getCondition().accept(this, p);if (p.isConstantTrueCondition) {// 死循环this.errMsg.append("Dead loop is not allowed in api expressions. Position: "+ this.resolveRowAndCol(node)).append('\n');}p.isConstantTrueCondition = condTemp;p.isLoopConditionExpr = isLoopExpT emp;}return super.visitForLoop(node, p);}public Void visitWhileLoop(WhileLoopTree node, FbdStructContext p) {boolean condTemp = p.isConstantTrueCondition;boolean isLoopExpTemp = p.isLoopConditionExpr;p.isLoopConditionExpr = true;node.getCondition().accept(this, p);if (p.isConstantTrueCondition) {// 死循环this.errMsg.append("Dead loop is not allowed in api expressions. Position: " + this.resolveRowAndCol(node)).append('\n');}p.isConstantTrueCondition = condTemp;p.isLoopConditionExpr = isLoopExpT emp;return super.visitWhileLoop(node, p);}// 处理循环条件, 需要关心结果为boolean值的表达式// 二元表达式public Void visitBinary(BinaryTree node, FbdStructContext p) {boolean isLoopCondTemp = p.isLoopConditionExpr;// 求左值p.isLoopConditionExpr = false;node.getLeftOperand().accept(this, p);p.isLoopConditionExpr = isLoopCondTemp;Object leftVal = p.expValue;// 求右值p.isLoopConditionExpr = false;node.getRightOperand().accept(this, p);p.isLoopConditionExpr = isLoopCondTemp;Object rightVal = p.expValue;// 求整体值Object val = null;if (leftVal != null && rightVal != null) switch (node.getKind()) {case MULTIPLY:val = ((Number) leftVal).doubleValue() * ((Number) rightVal).doubleValue();break;case DIVIDE:val = ((Number) leftVal).doubleValue() / ((Number) rightVal).doubleValue();break;case REMAINDER:val = ((Number) leftVal).intValue() % ((Number) rightVal).intValue();break;case PLUS:if (leftVal instanceof Number && rightVal instanceof Number) {val = ((Number) leftVal).doubleValue() + ((Number) rightVal).doubleValue();} else {val = String.valueOf(leftVal) + String.valueOf(rightVal);}break;case MINUS:val = ((Number) leftVal).doubleValue() - ((Number) rightVal).doubleValue();break;case LEFT_SHIFT:val = ((Number) leftVal).longValue() << ((Number) rightVal).intValue();break;case RIGHT_SHIFT:val = ((Number) leftVal).longValue() >> ((Number) rightVal).intValue();break;case UNSIGNED_RIGHT_SHIFT:val = ((Number) leftVal).longValue() >>> ((Number) rightVal).intValue();break;case LESS_THAN:val = ((Number) leftVal).doubleValue() < ((Number) rightVal).doubleValue();break;case GREATER_THAN:val = ((Number) leftVal).doubleValue() > ((Number) rightVal).doubleValue();break;case LESS_THAN_EQUAL:val = ((Number) leftVal).doubleValue() <= ((Number) rightVal).doubleValue();break;case GREATER_THAN_EQUAL:val = ((Number) leftVal).doubleValue() >= ((Number) rightVal).doubleValue();break;case EQUAL_TO:val = leftVal == rightVal;break;case NOT_EQUAL_TO:val = leftVal != rightVal;break;case AND:if (leftVal instanceof Number) {val = ((Number) leftVal).longValue() & ((Number) rightVal).longValue();} else {val = ((Boolean) leftVal) & ((Boolean) rightVal);}break;case XOR:if (leftVal instanceof Number) {val = ((Number) leftVal).longValue() ^ ((Number) rightVal).longValue();} else {val = ((Boolean) leftVal) ^ ((Boolean) rightVal);}break;case OR:if (leftVal instanceof Number) {val = ((Number) leftVal).longValue() | ((Number) rightVal).longValue();} else {val = ((Boolean) leftVal) | ((Boolean) rightVal);}break;case CONDITIONAL_AND:val = ((Boolean) leftVal) && ((Boolean) rightVal);break;case CONDITIONAL_OR:val = ((Boolean) leftVal) || ((Boolean) rightVal);break;default:val = null;}if (p.isLoopConditionExpr) {if (val != null && val instanceof Boolean && (Boolean) val) p.isConstantTrueCondition = true;} else {p.expValue = val;}return null;}// 3元条件表达式public Void visitConditionalExpression(ConditionalExpressionTree node, FbdStructContext p) {boolean isLoopCondTemp = p.isLoopConditionExpr;p.isLoopConditionExpr = false;node.getCondition().accept(this, p);p.isLoopConditionExpr = isLoopCondTemp;Object val = null;if (p.expValue != null) {if ((Boolean) p.expValue) {// 取true expr值p.isLoopConditionExpr = false;node.getTrueExpression().accept(this, p);p.isLoopConditionExpr = isLoopCondTemp;val = p.expValue;} else {// 取false expr值p.isLoopConditionExpr = false;node.getFalseExpression().accept(this, p);p.isLoopConditionExpr = isLoopCondTemp;val = p.expValue;}}if (p.isLoopConditionExpr) {p.isConstantTrueCondition = val != null && val instanceof Boolean && (Boolean) val;} else {p.expValue = val;}return null;}// 常量public Void visitLiteral(LiteralTree node, FbdStructContext p) {if (p.isLoopConditionExpr) {p.isConstantTrueCondition = Boolean.TRUE.equals(node.getValue());} else {p.expValue = node.getValue();}return null;}// 括起来的表达式public Void visitParenthesized(ParenthesizedTree node, FbdStructContext p) {boolean isLoopCondTemp = p.isLoopConditionExpr;if (p.isLoopConditionExpr) {// 求值子表达式p.isLoopConditionExpr = false;node.getExpression().accept(this, p);p.isLoopConditionExpr = isLoopCondTemp;if (p.expValue != null && Boolean.TRUE.equals(p.expValue)) p.isConstantTrueCondition = true;} else {// 直接以子表达式的结果作为括号表达式的结果p.isLoopConditionExpr = false;node.getExpression().accept(this, p);p.isLoopConditionExpr = isLoopCondTemp;}return null;}// 类型转换表达式public Void visitTypeCast(TypeCastTree node, FbdStructContext p) {boolean isLoopCondTemp = p.isLoopConditionExpr;if (p.isLoopConditionExpr) {p.isLoopConditionExpr = false;node.getExpression().accept(this, p);p.isLoopConditionExpr = isLoopCondTemp;if (p.expValue != null&& ("Boolean".equals(node.getType().toString()) || "boolean".equals(node.getType().toString())))p.isConstantTrueCondition = true;} else {p.isLoopConditionExpr = false;node.getExpression().accept(this, p);p.isLoopConditionExpr = isLoopCondTemp;}return null;}// 一元表达式public Void visitUnary(UnaryTree node, FbdStructContext p) {boolean isLoopCondTemp = p.isLoopConditionExpr;// 求子表达式值p.isLoopConditionExpr = false;node.getExpression().accept(this, p);p.isLoopConditionExpr = isLoopCondTemp;Object val = null;if (p.expValue != null) {switch (node.getKind()) {case POSTFIX_INCREMENT:case POSTFIX_DECREMENT:case PREFIX_INCREMENT:case PREFIX_DECREMENT:val = null;break;case UNARY_PLUS:val = p.expValue;break;case UNARY_MINUS:val = -((Number) p.expValue).doubleValue();break;case BITWISE_COMPLEMENT:val = ~((Number) p.expValue).longValue();break;case LOGICAL_COMPLEMENT:val = !((Boolean) p.expValue);break;default:val = null;}}if (p.isLoopConditionExpr) {if (val != null && val instanceof Boolean && (Boolean) val) p.isConstantTrueCondition = true;} else {p.expValue = val;}return null;}// *************禁止定义死循环end*************}•上述visitor通过对visitClass的处理,对“定义嵌套类”这种行为进行了报错•通过对visitAssert的处理,凡是遇到代码中出现assert语句的,均给出错误信息•通过对visitBreak和visitContinue的处理禁止了goto语句(即带label的break和continue语句)•通过对visitDoWhileLoop等循环语法结构的访问,以及部分表达式结构的访问(如visitBinary、visitLiteral),禁止了如while(true)、for(;;)或while(1<2)等明显死循环在什么阶段能将Java代码转化为AST从而被TreeVisitor分析?接下来的问题就是:我们有了处理AST的visitor,那么到底要在什么时候运行它呢?答案就是使用jdk1.6的PluggableAnnotationProcessor机制, 在创建compilerTask时设置对应的Processor, 然后在该Processor中调用我们的visitor下面是我们的processor实现:@SupportedSourceVersion(SourceVersion.RELEASE_6)@SupportedAnnotationTypes("*")public class ExprCodeCheckProcessor extends AbstractProcessor {// 工具实例类,用于将CompilerAPI, CompilerTreeAPI和AnnotationProcessing框架粘合起来private Trees trees;// 分析过程中可用的日志、信息打印工具private Messager messager;// 所有的CodeCheckerprivate List<ExprCodeChecker<?, ?>> codeCheckers = new ArrayList<ExprCodeChecker<?, ?>>();// 搜集错误信息private StringBuilder errMsg = new StringBuilder();// 代码检查是否成功, 若false, 则'errMsg'里应该有具体错误信息private boolean success = true;* ==============在这里列出所有的checker实例==============*/public ExprCodeCheckProcessor(){// 检查——禁止定义一些不必要的结构,如内部类this.codeCheckers.add(new ForbiddenStructuresChecker());}public synchronized void init(ProcessingEnvironment processingEnv) {super.init(processingEnv);this.trees = Trees.instance(processingEnv);this.messager = processingEnv.getMessager();// 为所有checker置入工具实例for (ExprCodeChecker<?, ?> c : this.codeCheckers) {c.setTrees(trees);c.setMessager(messager);}}public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment env) {if (!env.processingOver()) for (Element e : env.getRootElements()) {for (ExprCodeChecker<?, ?> c : this.codeCheckers) {c.check(this.trees.getPath(e));if (!c.isSuccess()) {this.success = false;this.errMsg.append(c.getErrorMsg()).append('\n');}}/** 这里若return true将阻止任何后续可能存在的Processor的运行,因此这里可以固定返回false*/return false;}/*** 获取代码检查的错误信息** @return*/public String getErrMsg() {return errMsg.toString();}/*** 指示代码检查过程是否成功,若为false,则可调用getErrMsg 取得具体错误信息** @return*/public boolean isSuccess() {return success;}}这里面关键的就是实现init方法和process方法init方法初始化了Trees工具并将其设置到了所有的ExprCodeChecker(其实就是visitor)中; Trees是一个很重要的工具类实例,它能帮助我们获取AST结构对应的行号、列号等重要信息process方法则是真正对源码进行处理,在这里,真正调用了所有的ExprCodeChecker(也就是visitor)然后在使用Compiler API的时候,将processor设置到CompilationTask中即可:CompilationTask task = compiler.getTask(out, fileManager, diagnostics, options, null, ss);if (processors != null) task.setProcessors(processors);顺便贴下ExprCodeChecker的代码如下,它就是一个对TreePathScanner的简单继承,封装了一些代码分析过程中的基本属性和常用方法, 所有的visitor只要继承自它就可以了:public abstract class ExprCodeChecker<R, P> extends TreePathScanner<R, P> {// 当前被扫描代码对应的节点转换工具类, 运行时由Processor负责置入protected Trees trees;// 错误信息打印、处理流程控制工具, 运行时由Processor负责置入protected Messager messager;/*** 取得代码检查的错误信息, 返回结果为null或空字符串串则表示无错误, 否则认为有错误发生** @return*/public abstract String getErrorMsg();/*** 取得初始参数** @return 用于遍历代码树的初始参数*/protected abstract P getInitParam();/*** 代码检查是否成功** @return true - 成功,无问题;false - 失败,调用getErrorMsg可获取错误信息*/final boolean isSuccess() {String err = this.getErrorMsg();return err == null || err.length() == 0;}/*** package访问权限,专门用于由Processor置入Trees工具实例** @param trees*/final void setTrees(Trees trees) {this.trees = trees;}/*** package访问权限,专门用于由Processor置入Messager工具实例** @param messager*/final void setMessager(Messager messager) {this.messager = messager;}/*** 开始遍历处理传入的代码树节点** @param path*/final void check(TreePath path) {this.scan(path, getInitParam());}/*** 获取指定语法节点缩在源文件中的行号和列号信息, 用于错误信息输出** @param node* @return*/protected final String resolveRowAndCol(Tree node) {CompilationUnitTree unit = this.getCurrentPath().getCompilationUnit();long pos = this.trees.getSourcePositions().getStartPosition(unit, node);LineMap m = unit.getLineMap();return "row: " + m.getLineNumber(pos) + ", col: " + m.getColumnNumber(pos);}}其中check方法是遍历分析的起点,由processor调用resolveRowAndCol则是获取AST节点对应的行号、列号的方法,用于输出错误信息使用trees.getSourcePositions()获取节点的源码位置,以及LineMap获得指定位置的行号、列号在processor的init方法中被置入的Trees工具实例,最大的用处就是获取对应AST节点的行号、列号,具体代码参见上述resolveRowAndCol方法JavaCompiler Tree API的限制有必要讨论下什么样的控制,适合用Tree API来做?总结起来应该是:静态的、简单的比如“不能定义内部类”、“不能写annotation”、“不能写assert语句”等随着需求的复杂度增高,使用Tree API的编码成本也会增高,毕竟使用visitor来分析复杂的AST模式并非十分容易的事情比如上面的例子,“限制死循环”这种需求;如果说非常简单的死循环,比如while(true),这种是非常好做的但如果稍微复杂一点, 比如while(1<2),那么这里势必会牵涉到一个”计算”过程,我们需要在分析过程中对1<2这个表达式做出计算,从而知晓该循环语句是否死循环;虽然人眼对1<2的结果一目了然,但这里靠程序来做的话,增加的复杂度还是相当可观的如果在继续复杂下去,可以想象,其开发成本会越来越高,且这个分析过程本身的“运行”成本也会越来越接近真正运行这段被分析的代码的成本,这个时候使用Tree API来做分析就不划算了所以说,考虑到成本因素,Tree API并不适合做太复杂的分析其次就是”静态的”代码,才能在编译期做分析,如果是这样的代码:while(x<1),而x又是从方法参数中传入,那么x的值就完全在运行期才能确定,那么Tree API就无法判断该循环是否是死循环还有就是Tree API很容易让人联想到一个问题:可否在遍历AST 的过程中改变AST的结构?这是个激动人心的话题,运行期改变源码的AST是一个想象空间很大的想法,就像在groovy和ruby中能办到的那样,这能成为一种强大的元编程机制不过,从java的Tree API规范上讲,是不能在遍历AST过程中修改AST的结构的,但目前有一个bug可以做到:Erni08b.pdf 并且目前的Project Lombok就是基于此bug实现; 就未来的版本发展来讲,利用此bug来实现功能是不可靠的, Project Lombok的开发者对此也表示担心不过这个bug也不失为一种必要时的选择,毕竟通过它能实现的功能很酷Posted by pf_miles Sep 24th, 2013 java。
Java语言JIT编译器工作流程

Java语言JIT编译器工作流程Java语言是现代编程语言中最受欢迎的之一,其独有的特性之一就是使用了即时编译器(Just-In-Time Compiler,简称JIT)。
JIT编译器在代码的执行过程中起着重要的作用,可以提高程序的性能。
本文将介绍Java语言JIT编译器的工作流程。
一、JIT编译器简介JIT编译器是Java虚拟机(Java Virtual Machine,简称JVM)中的组成部分,它的主要功能是将Java源代码或字节码即时编译成本地机器码,以便于更快地执行程序。
JIT编译器能够根据程序的运行情况进行动态优化,提高程序的执行效率。
二、编译阶段JIT编译器的工作流程可以分为两个主要阶段:解释阶段和编译阶段。
在解释阶段,JVM会逐行解释执行Java源代码或字节码。
解释阶段的目的是为了收集程序的运行数据,例如热点代码(Hot Spot)的执行次数、方法的调用频率等。
三、热点代码识别在解释阶段,JIT编译器会根据收集到的运行数据识别出热点代码,即被频繁执行的代码块。
热点代码的执行次数超过一定的阈值后,JIT编译器会将其标记为热点方法,并进行后续的优化编译。
四、优化编译在编译阶段,JIT编译器会对热点方法进行优化编译。
优化编译的目标是生成更高效的机器码,以提高程序的执行效率。
在优化编译过程中,JIT编译器会利用收集到的运行数据进行静态和动态的分析,然后根据这些分析结果进行各种优化操作。
五、优化技术JIT编译器采用了多种优化技术来提高程序的性能。
其中包括:1. 内联(Inlining):将频繁调用的小方法直接插入到调用点处,减少方法调用的开销。
2. 去虚拟化(Devirtualization):将虚方法调用转换为直接方法调用,减少动态分派的开销。
3. 基于类型的优化(Type-based Optimization):利用静态类型信息进行优化,例如消除类型检查等。
4. 逃逸分析(Escape Analysis):分析对象的作用域,决定是否可以进行栈上分配等优化操作。
java jit编译

java jit编译Java JIT编译器是Java虚拟机中的一个重要组成部分,它可以将Java代码实时编译成本地机器码,从而提高Java程序的执行效率。
本文将从JIT编译器的原理、优化技术和应用场景等方面进行介绍。
一、JIT编译器的原理JIT编译器是Just-In-Time的缩写,意为即时编译器。
它的工作原理是在程序运行时,将Java字节码实时编译成本地机器码,从而避免了Java解释器的性能瓶颈。
JIT编译器的主要工作流程如下:1. 解释器执行Java字节码,将其转换为中间代码。
2. JIT编译器对中间代码进行分析和优化,生成本地机器码。
3. 本地机器码被执行,程序运行速度得到提升。
二、JIT编译器的优化技术JIT编译器的优化技术主要包括以下几个方面:1. 方法内联:将方法调用直接替换为方法体,避免了方法调用的开销。
2. 循环展开:将循环体展开成多个重复的代码块,减少了循环控制的开销。
3. 常量折叠:将常量表达式计算出结果,避免了重复计算。
4. 类型推断:根据上下文推断变量类型,避免了类型转换的开销。
5. 代码消除:删除无用的代码,减少了程序的执行时间。
三、JIT编译器的应用场景JIT编译器的应用场景主要包括以下几个方面:1. Web应用程序:JIT编译器可以提高Web应用程序的性能,加快页面加载速度。
2. 游戏开发:JIT编译器可以提高游戏的帧率,提升游戏的流畅度。
3. 大数据处理:JIT编译器可以提高大数据处理的效率,加快数据分析的速度。
4. 移动应用程序:JIT编译器可以提高移动应用程序的性能,减少应用程序的卡顿现象。
JIT编译器是Java虚拟机中的一个重要组成部分,它可以提高Java 程序的执行效率,优化程序的性能。
在实际应用中,我们可以根据具体的场景选择合适的优化技术,从而达到更好的效果。
【Java动态编译】动态编译的应用

【Java动态编译】动态编译的应⽤1、动态编译动态编译,简单来说就是在Java程序运⾏时编译源代码。
从JDK1.6开始,引⼊了Java代码重写过的编译器接⼝,使得我们可以在运⾏时编译Java源代码,然后再通过类加载器将编译好的类加载进JVM,这种在运⾏时编译代码的操作就叫做动态编译。
静态编译:编译时就把所有⽤到的Java代码全都编译成字节码,是⼀次性编译。
动态编译:在Java程序运⾏时才把需要的Java代码的编译成字节码,是按需编译。
静态编译⽰例:静态编译实际上就是在程序运⾏前将所有代码进⾏编译,我们在运⾏程序前⽤Javac命令或点击IDE的编译按钮进⾏编译都属于静态编译。
⽐如,我们编写了⼀个xxx.java⽂件,⾥⾯是⼀个功能类,如果我们的程序想要使⽤这个类,就必须在程序启动前,先调⽤Javac编译器来⽣成字节码⽂件。
如果使⽤动态编译,则可以在程序运⾏过程中再对xxx.java⽂件进⾏编译,之后再通过类加载器对编译好的类进⾏加载,同样能正常使⽤这个功能类。
动态编译⽰例:JDK提供了对应的JavaComplier接⼝来实现动态编译(rt.jar中的javax.tools包提供的编译器接⼝,使⽤的是JDK⾃带的Javac编译器)。
⼀个⽤来进⾏动态编译的类:public class TestHello {public void sayHello(){System.out.println("hello word");}}编写⼀个程序来对它进⾏动态编译:public class TestDynamicCompilation {public static void main(String[] args) {//获取Javac编译器对象JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();//获取⽂件管理器:负责管理类⽂件的输⼊输出StandardJavaFileManager fileManager = compiler.getStandardFileManager(null,null,null);//获取要被编译的Java源⽂件File file = new File("/project/test/TestHello.java");//通过源⽂件获取到要编译的Java类源码迭代器,包括所有内部类,其中每个类都是⼀个JavaFileObjectIterable<? extends JavaFileObject> compilationUnits = fileManager.getJavaFileObjects(file);//⽣成编译任务pilationTask task = compiler.getTask(null, fileManager, null, null, null, compilationUnits);//执⾏编译任务task.call();}}启动main函数,会发现在程序运⾏过程中,使⽤了Javac编译器对类TestHello进⾏了编译,并⽣成了字节码⽂件TestHello.class。
Java即时编译和逃逸分析

Java即时编译和逃逸分析在Java编程语⾔和环境中,即时(JIT ,just-in-time compiler)是⼀个把Java的(包括需要被解释的指令的程序)转换成可以直接发送给处理器的指令的程序。
当你写好⼀个Java程序后,源语⾔的语句将由Java编译器编译成,⽽不是编译成与某个特定的处理器硬件平台对应的指令代码(⽐如,Intel的Pentium微处理器或IBM的System/390处理器)。
是可以发送给任何平台并且能在那个平台上运⾏的独⽴于平台的代码。
最早的建置⽅案是由⼀套转译(interpreter),将每个Java指令都转译成对等的微处理器指令,并根据转译后的指令先后次序依序执⾏,由于⼀个Java指令可能被转译成⼗⼏或数⼗⼏个对等的微处理器指令,这种模式执⾏的速度相当缓慢。
针对这个问题,业界⾸先开发出(just in time)。
当Java执⾏环境时,每遇到⼀个新的类别(:类别是Java中的功能群组),类别是Java程式中的功能群组-JIT在此时就会针对这个类别进⾏编译(compile)作业。
经过编译后的,被优化成相当精简的原⽣型指令码(native code),这种程式的执⾏速度相当快。
花费少许的编译时间来节省稍后相当长的执⾏时间,JIT这种设计的确增加不少效率,但是它并未达到最顶尖的效能,因为某些极少执⾏到的Java指令在编译时所额外花费的时间可能⽐转译器在执⾏时的时间还长,针对这些指令⽽⾔,整体花费的时间并没有减少。
基于对JIT的经验,业界发展出动态(dynamic ),动态仅针对较常被执⾏的码进⾏编译,其余部分仍使⽤转译程式来执⾏。
也就是说,动态会研判是否要编译每个类别。
动态拥有两项利器:⼀是转译器,另⼀则是JIT,它透过智慧机制针对每个类别进⾏分析,然后决定使⽤这两种利器的哪⼀种来达到最佳化的效果。
动态针对的特性或者是让程式执⾏⼏个循环,再根据结果决定是否编译这段程式码。
cmpl_01

原中间代码:
序号 (1) (2) (3) (4) (5) OP := J< * + * 1 100 10 I 10 K K T1 K ARG! ARG2 RESULT K (9) T1 M T2 K:=1 若100<K 转至第(9)个四 元式 T1:=10*K;T1为临时变量 M:=I + T1; T2:=10*K;T2为临时变量 注 释
注
释
(1) := (2) := (3) := (4) j< (5) + (6) + (7) + (8) j (9)
若100<K 转至第(9)个四 元式 M:=M+10 N:+N+10 K:+K+1 转至第 (4)个四元式
源程序
原中间代码
16
5、目标代码生成
任务: 中间代码→依赖于机器的目标代码(汇编语言或机器语言)
10
id3 id2 T3
T1 T2 -
T1 )
T2 ) T3 ) id1) ( * id3 10.0 T2 )
( +
id2
T2
id1 )
29
目标代码生成
sum := first + count * 10 ( ( * + id3 id2 10.0 t1 id3, t1 ) id1 ) R2
MOVF MULF
begin var sum , first , count : real ; sum := first + count * 10 end .
22
赋值语句
标识符 id1 sum :=
id1:=id2+id3*10 的语法树
javaCompiler简析

javaCompiler简析 javaCompiler简单来说就是⼀个⽤来调⽤java语⾔编译器的接⼝,我们使⽤它可以实现对其他路径下或者远程代码的编译。
显然我们可以实现这样⼀种操作,将⼀串符合java语法的字符串写⼊⼀个java⽂件中。
然后利⽤javaCompiler编译此⽂件。
最后通过反射的⽅法实现对此⽂件的运⾏(online judge)。
public static void main(String[] args) throws Exception {/*** 将 string 写⼊Hello.java中* 通过⽂件输出流*/String string = "public class Hello { public static void main(String []args){System.out.println(\"Hello\");}}";File file = new File("C:\\Users\\Administrator\\Desktop\\temp\\Hello.java");if (!file.exists()) {file.createNewFile();}byte[] bytes = string.getBytes();FileOutputStream stream = new FileOutputStream(file);stream.write(bytes, 0, bytes.length);stream.close();/*** 编译Hello.java* 通过反射调⽤main函数实现函数的运⾏*/JavaCompiler javaCompiler = ToolProvider.getSystemJavaCompiler();int result = javaCompiler.run(null, null, null, "C:\\Users\\Administrator\\Desktop\\temp\\Hello.java");System.out.println(result == 0 ? "success" : "failure");URL[] urls = new URL[]{new URL("file:/" + "C:/Users/Administrator/Desktop/temp/")};URLClassLoader classLoader = new URLClassLoader(urls);Class c = classLoader.loadClass("Hello");System.out.println(c.getName());Method method = c.getDeclaredMethod("main", String[].class);method.invoke(null, (Object) new String[]{"aa","bb"});}。
java jit的触发条件

java jit的触发条件
Java JIT(即时编译器)是Java虚拟机在运行时将字节码转换为机器代码的一种编译技术。
JIT的触发条件通常有以下几个方面:
1. 热点代码:JIT主要优化热点代码,即程序中频繁执行的代码块。
当某个方法或循环代码块被多次执行时,JIT会将其编译为机器代码,以提高执行速度。
2. 稳定的方法:JIT会对程序中稳定的方法进行编译,以获得更好的性能。
稳定的方法是指在程序的生命周期内都没有被修改过的方法。
3. 方法内联:JIT会对频繁调用的方法进行内联优化,即将被调用的方法的代码直接插入到调用方法中,减少了方法调用的开销。
4. 循环优化:JIT会对循环进行优化,例如去除循环不变量、循环不变条件的计算、循环展开等,以提高循环的执行效率。
5. 字段优化:JIT会对频繁访问的字段进行优化,例如将字段的读写操作优化为局部变量的读写,减少了字段访问的开销。
需要注意的是,JIT在编译时会有一定的延迟,即程序一开始执行时可能是解释执行的,直到某些代码被判定为热点代码或稳定的方法后,才会被JIT编译为机器代码。
因此,JIT的触发条件可能会随着程序的运行而动态变化。
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
A Retargetable JIT Compiler for JavaMichael Golm,Hans Kopp,Jürgen KleinöderJan 1999TR-I4-99-10Computer ScienceDepartment Operating Systems — IMMD IV Friedrich-Alexander-University Erlangen-Nürnberg, Germany Technical ReportA Retargetable JIT Compiler for JavaMichael Golm, Hans Kopp, Jürgen KleinöderUniversity of Erlangen-NürnbergDept. of Computer Science IV (Operating Systems) Martensstr. 1, D-91058 Erlangen, Germany {golm, kopp, kleinoeder}@informatik.uni-erlangen.deAbstractInterpreted Java has poor performance.Therefore most Java Virtual Machines(JVM)contain a just-in-time com-piler(JIT)that compiles the bytecode to the processor’s native instruction set.Most of these compilers are written in C and tightly integrated with the JVM.We describe a JIT compiler that is written in Java and retargetable to a differ-ent JVMs and CPUs.1IntroductionModern JVMs contain a JIT compiler that improves the JVM performance for about5to10times.However,most of these JIT compilers have two fundamental deficiencies. They are written in C for the reason of execution speed and they are tightly integrated with the JVM.Writing the compiler in C means that it is very difficult to extend it and integrate new optimization algorithms once they become available.It is also impossible to make appli-cation specific optimizations by downloading code into the compiler.Writing the compiler in Java means that the com-piler can be customized to include application specific opti-mizers (as in [Mats98]).Integrating the JIT with the JVM allows the compiler to make assumptions about the JVM that ease the task of code generation and allow several performance optimizations. However,it is difficult to enhance the JIT and JVM indepen-dently and it is impossible to use one JIT for different JVMs.This paper makes the following contributions:It describes the problems that must be solved when a Java written com-piler interacts with other parts of the JVM.The paper describes an object-oriented implementation of such a com-piler that works with our own JVM metaXa[Gol97].The paper is structured as follows.Thefirst part describes how our compiler can be ported to a different CPU.The second part discusses the problems of retargeting the compiler to a different JVM.Then we describe the architecture of our compiler together with our solutions to the retargetability problems.We continue bydescribing several optimizations that are implemented in our compiler or could be integrated in the compiler.Finally we give some preliminary performance figures.2Retargeting to different CPUsThere are several approaches to make a compiler retargeta-ble to different CPUs[Fras96].One is to use a register trans-fer language(RTL)[Tie89].We decided not to use an RTL, because it imposes an overhead that seems not acceptable for a JIT compiler.Instead we separated the compiler into a frontend and backend in a similar way as in the kaffe JIT [Wil+97].The following section describes the backend,that must be changed when compiling for a different CPU.2.1The backendThe backend encapsulates the details of the target CPU.It is implemented by the class IMCode(see Figure1).The inter-face of IMCode contains many methods that start with the prefix“add”.These methods can be considered an interme-diate language.Invoking such a method generates code fora new instruction of this intermediate language.TheIMCode interface also contains methods to generate code for relative jumps.When all instructions of a method are generated,jump offsets for these relative jumps can be com-puted.The current implementation of IMCode generates code for the Intel x86 family of CPUs.1Besides the code generator the backend also contains a reg-ister allocator that uses a virtual stack.2.2The virtual stackThe virtual stack is an array that has the same size as the stack frame of the method.Every array element describes where the value of the stack element is located(register, main memory)or how it can be computed.Register alloca-tion is done using the virtual stack.If an machine instruction needs its operands in a register,the virtual stack is instructed to move contents the stack cell into a register.It remembers that the stack cell is now available in a register.Tofind a reg-ister itfirst tries tofind an unused register.If there is no unused register,the virtual stack selects a used register, remembers to transfer the contents of a register to main memory,and use this register.The virtual stack is imple-mented by the class RegisterAllocStack (see Figure 2).The method in Figure3uses the IMCode code generation interface and the RegisterAllocStack register allocator to generate code for an integer addition of two slots.Thefirst slot is loaded into a register which is then added to the sec-ond slot.If thefirst slot was already associated with a regis-ter,RegisterAllocStack keeps track of this and does not gen-erate code for loading the slot into a register in such a case. 3Retargeting to different JVMsThis section describes the problems that must be coped with when retargeting the compiler to a different JVM.The dis-cussed retargetability problems must be coped with even if the compiler is written in C.The Microsoft JIT compiler [MS98],which is written in C,defines an interface between the JIT compiler and the JVM to retarget the JIT to a differ-ent JVM.However,their compiler is allowed to make too many assumptions about the structure of the JVM.Some of these assumptions that do not hold for other JVMs are that thefirst element of the object’s data is the vtable pointer orclass IMCode {void addMoveIntS_S(int dstSlot, int srcSlot);// copy between slotsvoid addMoveIntS_MB(int dstSlot, int baseSlot, int offset);// load memory cell into slotvoid addInstrIntS_S(int operator, int dstSlot, int srcSlot);// arithmetic operationsvoid addInstrCnvsS(int slot, int dstType, int srcType);// type conversionsvoid addJump(UnresolvedJump jumpObject);// unconditional jumpvoid addJumpTarget(UnresolvedJump jumpObject);// jump targetvoid addCondJumpIntS_S(int condition, int slot1, int slot2, UnresolvedJump jumpObject);// conditional jump}Figure 1:The backend IMCodeclass RegisterAllocStack {int allocRegisterFor(int slot);// allocate register for a specific slot, return register IDint allocFreeRegisterFor(int slot);//load register with slot value,but don’t assiciate register with slot void freeSlot(int slot);// write register contents back into stack slotvoid assignRegisterTo(int slot, int register);// assign a register to a slotInstrOperand slotOpConst(int slot, InstrOperand op);// get operand for slot access}Figure 2:The virtual stack RegisterAllocStackvoid makeCodeIntAdd {int srcReg1 = virtualStack.allocFreeRegisterFor(srcSlot1);// allocate register for first operand targetCode.addInstr(IntInstr.ADD, i1.register(srcReg1), virtualStack.slotOpConst(srcSlot2, i2));// generate codevirtualStack.assignRegisterTo(dstSlot, srcReg1);// assign result register to target slot}Figure 3:Using IMCode and RegisterAllocStack2that the calling convention of compiled methods is __pascal.These assumptions are not valid for other JVMs, like Sun’s JDK which encodes the vtable pointer in the objects handle[YeLi97].One could also imagine a JIT com-piler that uses the C calling convention instead of the __pascal calling convention.In fact,our JIT compiler uses the C calling convention.We think that making it easy to port the compiler to a differ-ent JVM requires a clean interface between the native exe-cution environment and the Java execution environment (see Figure 4).Major obstacles in the way of making the compiler retar-getable are exception handling,the garbage collector,stack layout, and calling conventions.3.1Exception handlingWhen an exception occurs,the appropriate exception han-dler must be found.If no matching handler can be found in the current stack frame,the stack must be unrolled until a handler is found or the stack contains no more stack frames (in which case the thread terminates).Stack unrolling could be difficult if the thread has executed code that uses differ-ent stack layouts.This could happen if native code or JIT compiled code was executed alternating with interpreted Java code.In ordinary JITs such a problem does not appear because all code is compiled.When compiled code and interpreted code is executed alternating,the layout of both native and interpreter stack must be known to unroll the stack.Our architecture aims at encapsulating details like the stack layout between the interpreter and the compiler. Therefore we do not unroll the stack at the exception point but use another technique,which is also used in[Gra97].In [Gra97]every method indicates with a second return value whether an exception occurred.The method checks this return value and unrolls only its own stack frame when an exception is indicated.This way the method only has to know its own stack layout and information about its own exception handlers.The drawback of this solution is the additional overhead of checking this return value after each method call.In metaXa an exception is indicated in a vari-able in thread specific storage.When the compiled code tests this variable it has to look for a handler in the current method.The JVM provides a helper function for this pur-pose.The helper function gets the instruction pointer and the method id and looks in a table if a handler for the excep-tion exists.It returns the address of this handler or a null pointer if no handler was found.The native code then jumps to this address.3.2Garbage CollectionA variety of garbage collection(GC)algorithms[JoLi96]is used in today’s JVMs.GC algorithms can be classified in conservative GC and exact GC.3The algorithm that places the least burden to a JIT compiler is conservative garbage collection.In a conservative GC each value is considered a reference.There is no need for a JIT compiler to cooperate with a conservative GC.An exact(or accurate)garbage collector needs to know all references that are hold by the JIT compiled code.At least two techniques can be used to provide this information.JIT compiled code can be treated like native code written in C. As in the JNI system[Sun97],each reference that is passed to the method as a return value of a method or as method parameter is ing this technique,JIT compiled methods are treated like C-compiled native methods.Every method is passed a JNIEnv pointer to a thread specific data structure that is used to track references and so to support the garbage collector.This is not the fastest way but is has a simple implementation.In a more efficient technique the JIT compiler computes a map of reference locations for each code position where the thread can be preempted by the garbage collector[MS98],[Cram+97].These code posi-tions are called gc points[Diw+92].To reduce the overhead of map creation and storage the garbage collector is only allowed to run at dedicated gc points,like method calls,and object allocations, or at the backward branch of a loop. 3.3The compile-time interfaceThe compile-time interface(Figure4)plays a central role in hiding JVM details from the compiler.The compile-time interface is the interface of the JVM that is used by the JIT compiler to get static information.To compile a method the compiler needs information about the class and the method that must be compiled.We extended the Reflection API allowing the compiler to obtain this information.The extended API provides the bytecodes of a method,the con-stantpool,the maximal depth of the operator stack,the num-ber of local variables,and the exception tables.This extended Reflection API is only one part of the compile-time interface.The other part of the compile-time interface gives informa-tion about the structure of the ing this interface the compiler can obtain•method IDs•the index of a method in the vtable•class IDs•the offset of a field in an object•the address of static fields•the address of String objects in the constant poolThis information can only be obtained after resolving the constant pool.However,this is allowed according to the JVM specification.4Compiling a methodIn order to allow easy adoption of new compilation tech-niques we designed and implemented the JIT in a strictly object-oriented manner.The compiler is structured into a frontend and a backend.The frontend is mostly machine independent.The backend is used for native code genera-tion and is therefore machine dependent.4.1Time of CompilationThe decision when the compiler is to be invoked is not hard-wired into the VM.Most JIT compilers generate a method-table that contains compiler-invocation stubs when initially loading a class[Crel97].This means that a method is com-piled before itsfirst use.Because compilation consumes time,one could use several heuristics to decide when to compile a method and when to interpret it[Gris98].Our JIT compiler calls a class that decides whether a stub for com-piler invocation or for interpreter invocation should be used.This class currently bases its decision only on the class name and the method name.However,it also could count method invocations and compile the method when the invo-cation count exceeds a certain threshold.4.2Method compilationThe compilation of a method proceeds as shown in Figure5.The frontendfirst obtains the method’s bytecode using anextended reflection API.An object is created for every byte-code of the method.Then the position of the bytecode’s operands is computed and this way the stack oriented code is transformed into a two-address code.The next step is the actual code generation,where all bytecode objects are asked to translate themselves.They do the translation using the platform-dependent classes of the backend.JVM dependent bytecodes,such as invokevirtual,delegate the translation to the JVM dependent code.The CPU dependent part then does the register allocation and then the creation of machine code.If the machine code of the complete method is cre-ated, the jump offsets can be computed.44.3Bytecode objectsAll bytecodes of a method are represented by objects.The classes of these bytecode objects are organized in a inherit-ance hierarchy as shown in Figure 6.Jump instructions are a bit more complicated.At the target of the jump instruction a pseudo instruction JumpTarget is insert into the list of instruction objects.The Jump instruc-tion object has a reference to this object.To create an instruction object,the prototype design pattern is used.An array of bytecode objects contains a prototype of the byte-code object at the index of the bytecode number.This object is cloned.During this process many new objects are cloned, which is a very expensive operation.We currently work on restructuring this process to reuse the bytecode objects in the compilation of the next method by employing thefly-weight design pattern.4.4Using the bytecode objectsUsing an object for each bytecode allows us to implement operations that can easily be applied to the whole method.A traverser object is responsible for traversing the bytecode. It determines the order,in which a certain operation is applied to the bytecodes. These operations could be •setting the operands of the bytecode•finding jump targets•generation of native code•copy propagation•interpretation of the bytecodes•eliminating unnecessary null-reference checks •dead code elimination 4.4.1Operand positionThe JVM is a stack-oriented machine.This means that oper-ands are passed at the stack.Most CPUs are register ori-ented,therefore the compiler should transform the stack-oriented code into an register oriented code.Thefirst step is tofind the stack positions of the operands.This position depends on the stack depth at this code position.And the stack depth depends on all bytecodes that are executed before.A property of the JVM is that the stack depth depends not on the code path on which a certain code posi-tion was reached [Gos95], [Age+98].4.5JVM dependent compilationThe JIT compiler must known certain details about the internal structure of the JVM when compiling code.The JVM provides an interface to obtain all the information that is necessary to compile the following operations that are dependent on the JVM structure(see Figure2).This inter-face must be replaced when the compiler is ported to a new JVM.The compiler makes several assumptions about the structure of the JVM.Most of these assumptions are encap-sulated in the class ExecEnvironment and could be changed if the compiler is ported to a JVM where these assumptions do not hold.5As shown in Figure4the compiler uses several helper func-tions that are accessed through a run-time interface.During JVM initialization severalfields in the ExecEnvironment class are initialized with function pointers to these helper functions.The compiler generates a procedure call(a CALL instruction when compiling for the x86)using the function pointer as the target address.In the following we will explain which information is obtained from the ExecEnvi-ronment class and for which purposes helper functions are used.The bytecodes that perform the discussed operations are listed in parenthesis.Access to fields (getfield, putfield).To generate code for a field access the compiler must know the structure of the object store and the layout of an object.An object can be addressed directly or indirectly.Direct addressing means that an object reference contains a pointer to the object data. In this model,thefirst word of the object data is usually a pointer to the objects class.Indirect addressing means that an object reference contains a pointer to an object handle. This handle contains a pointer to the object data and a pointer to the class of the ing handles slows down field access but makes it easier to move objects during a gar-bage collection.The JVM provides the information which model of the object store is used and it also provides the information that is necessary to compile code for afield access.In case of a handle-based model the offset of the object pointer in the handle must be provided by the JVM. Access to arrays (arraylength, aaload, aastore, ...).We assume that every JVM stores array elements sequentially and that the array length can be obtained using the array ref-erence.The JVM gives an index of the length information in the array data and the offset of the array elements. Invocation of virtual methods (invokevirtual).Virtual methods can be invoked by calling a helper function of the JVM or directly by looking into the method table.The JVM interface can be asked to give the offset of the method in the method table.Invocation of static methods (invokestatic).The static method invocation can be compiled to a direct call to the method.The JVM interface gives the address of this method.Interface invocation (invokeinterface).The compiler cur-rently uses a helper function of the JVM to invoke an inter-face method.It would be faster if the compiler would gen-erate code to directly invoke the interface method.To do this,the compiler must know the layout of the method tables for interfaces.Type checking (instanceof, checkcast).The JVM pro-vides helper functions to do runtime type checking. Throwing exceptions (athrow).An exception must be thrown either because the athrow bytecode is executed or a null reference or an other run-time error occurred.The6native code then passes a reference to the exception object to a JVM helper function.This helper functions indicates, that an exception occurred in this thread.Object creation (new).The JVM provides a helper func-tion for object creation.This helper function gets a class ID and it returns an object reference.Array creation (newarray, anewarray, multianewarray).Similar to object creation the JVM pro-vides a helper function for array creation.This helper func-tion gets a class ID for the array elements and the length of the array. It returns an array reference.Monitors (monitorenter, monitorexit).There are helper functions that are used to acquire or release a monitor. These helper functions get the object reference that is asso-ciated with the monitor.Certain run-time checks.(Certain tests that are done at runtime are also dependent on the JVM.These tests include the null-reference check,test for array bounds,integer divi-sion by zero,and checkcast.A JVM can use the MMU hard-ware to detect null-pointer accesses.If such a JVM is used, no null-reference checks are generated by the compiler. There are some other parts of the compiler that are depen-dent on the JVM:•calling convention of the compiled method•stack layout of the compiled method•calling convention of methods and helper functions •exception handling64-bit arithmetic (ldiv, lrem, lcmp, lshr, lshl, lushr).The compiler does not generate code for arithmetic operations with64-bit integers.Istead it calls helper functions of the JVM interface to perform operations.4.5.1Stack ManagementTo be portable to different JVMs the compiler must be able to cope with different layouts of stack frames.Stack frames are typically used to store parameters,local variables,tem-porary variables,and the return address of the method.Our compiler encapsulates all code generation that depends on the stack layout in a separate class.4.6Installation of compiled codeIf a method is loaded that has to be compiled,the JVM gen-erates a stub to invoke the compiler and installs this stub as the method code by writing a pointer to this stub in the method table.After the compiler compiled the method,the pointer to the stub is replaced by a pointer to the compiled code.5Optimizations5.1Adaptive optimizationAs shown in[Ro+96]different Java programs would profit from different optimization strategies.Simple and fast opti-mizations that are supported by most JIT compilers are reg-ister allocation,delayed code generation using a virtual stack,elimination of redundant array range checks,and elimination of redundant null pointer checks.Some applica-tions would profit from other optimizations,so an open JIT architecture,where applications can guide the optimizer,is useful [Mats98].5.2InliningOur compiler supports inlining of static methods.Inlining methods results in speedup,because method call overhead is eliminated and inter-method optimizations can be applied.These optimizations could perform a better register allocation and elimination of unnecessary null-reference tests.All methods that are private,static,orfinal can be inlined.The principle of inlining is very simple.The code of the method call(e.g.invokestatic)is replaced by the method code.The stack frame of the inlined method must be merged into the stack frame of the caller method.The following example illustrates this merging.If a method test()contains the method call a = addInt(a, 2), this call is compiled toThe method int addInt(int x,int y){return x+y;}is compiled to The merged stack frame has the following layout:aload 0iload 1iconst_2invokenonvirtual addInt(II)Iistore 1iload 1iload 2iaddret7The created code would look as follows(bytecodes have stack positions as operands):Several optimizations can now be applied to the code.Pro-vided that the method test only contains the addInt()method call,the code can be transformed using copy propagation and dead code elimination [Aho+85] toVirtual methods can be inlined,if no subclasses override the method.If the JVM loads a subclass that overrides the method,the inlining must be undone.This code modifica-tion must be done very carefully,because a different thread could be executing this code.5.3Storing compiled codeStoring the JIT compiled code means that the compilation must not be done just-in-time.If a class is loaded in a sepa-rate run of the VM,the stored native code can be used as long as the class has not changed.One could also construct an offline compiler that compiles a whole application and stores the compiled code.All data that depends on the cur-rent instantiation of the JVM must be set during a linking phase when the stored code is loaded into the JVM.This must be done for the following data:•addresses of helper functions•addresses of other compiled non-virtual methods•addresses of static fields•IDs of classes and methods•offsets of methods in the method table•offsets of fields in the objectTo set this information when the code is loaded into the JVM we store a symbol table with every compiled method. This symbol table contains the offset of the constant within the compiled code,the type of the constant,and information to compute the constant.5.4Low-level optimizationsOur compiler uses several optimizations that are“standard”for JIT compilers,like register allocation,virtual stacks, copy propagation,dead code elimination,elimination of redundant null-reference checks,and elimination of redun-dant array range checks.6PerformanceWe did not carry out detailed performance measurements yet.Our compiler uses optimizations that are used be other JIT compiler,so we expect the generated code to be of a comparable quality.The performance of the compiler itself is worse than the performance of other JIT compilers,because we did not compile the compiler with itself.It currently compiles about 3000bytecodes per second on a100MHz Pentium piling the compiler would give a5to10times speedup,so the compiler would be comparable to JIT com-pilers written in C,which compile about30000bytecodes per second.7Related WorkThere exist a number of JIT compilers that are written in C (JBuilder[Crel97],Sun[Cram+97],Cacao[Gra97], Microsoft[MS98]).There are also some compilers that operate offline by compiling the bytecode to C code and then translating this C code([Proe+97],[Mul+97]).We know only one other JIT compiler that is written in Java [Mats98].This compiler was derived from an existing C-written JIT compiler and it is not designed to be JVM inde-pendent.8Status and Future WorkWe extended our own JVM,which is described in[Gol97] for the interfaces described in the above sections.We did not used the compiler with another JVM,although we intend to do this.To use the compiler with another JVM,the targetcheckreference 0iadd 1, #2 -> 5istore 5 -> 2istore 5 -> 18JVM must provide the compile-time and run-time interfaces described in this paper.A detailed description of the com-piler is contained in [Ko98].9ReferencesAge+98O. Agesen, D. Detlefs, J. Moss, Garbage Collection and Local Variable Type-Precision and Liveness in Java Vir-tual Machines, Proc. Conf. on Programming LanguagesDesign and Implementation, PLDI ‘98Aho+85V.A.Aho,R.Sethi,D.J.Ullman,Compilers,Principles, Techniques, and Tools, Addison Wesley, 1985Cram+97T. Cramer et al., Compiling Java Just-in-time.IEEE Micro, May/June 1997Crel97R Crelier,Interview about the Borland JBuilder JIT Com-piler.Diw+92A. Diwan, J. Moss, R. L. Hudson, Compiler support for garbage collection in a statically typed language, Proc.Conf. on Programming Languages Design and Imple-mentation, PLDI ‘92, San Francisco 1992Fras96 C. W. Fraser, D. A. Hanson,A retargetable C compiler, Redwood City, Calif. Benjamin/Cummings 1995Tie89M. D. Tiemann. The GNU Instruction Scheduler,, June 1989Gol97M.Golm,Design and Implementation of a Meta Architec-ture for Java. Diplomarbeit (masters thesis), UniversitätErlangen, IMMD 4, Jan. 1997.Gos95J. Gosling, Java Intermediate Bytecodes, Workshop on Intermediate Representations, IR 95, ACM SigplanNotices V ol. 30, No. 3, March 1995Gra97R. Grafl,Cacao, Ein 64-Bit-JavaVM-Just-In-Time-Com-piler, Diplomarbeit (masters thesis), Technische Univer-sität Wien, Institur für Computersprachen, 1997.Gris98 D.Griswold,The Java HotSpot Virtual Machine Architec-ture,A White Paper About Sun’s Second Generation Jav-aTM Virtual Machine,, March 1998Hsi+97A.Hsieh et al.,Optimizing NET Compilers for Improved Java Performance,IEEE Computer V ol. 30, Iss. 6, June1997JoLi96R. Jones, R. Lins,Garbage Collection: Algorithms for Automatic Dynamic Memory Management,John Wiley&Sons, New York, 1996.Ko98H. Kopp, Design und Implementierung eines maschine-nunabhängigen Just-in-Time-Compilers für Java, Diplo-marbeit(masters thesis),Universität Erlangen,IMMD4,Oct. 1998Mats98Matsuoka, OpenJIT - A Reflective Java JIT Compiler, OOPSLA 98 Workshop on Reflective Programming inC++ and Java, UTCCP Report 98-4, Center for Compu-tational Physics,University of Tsukuba,Japan,Oct.1998. MS98Microsoft Web Page. /java/ sdk/20/vm/Jit_Compiler.htmMul+97G. Muller, B. Moura, F. Bellard, C. Consel, Harissa: A Flexible and Efficient Java Environment Mixing Byte-code and Compiled Code,The Third Usenix Conferenceon Object-Oriented Technologies and Systems, Oregon1997Proe+97T.A.Proebsting,G.Townsend,P.Bridges,J.H.Hartman, T.Newsham,S.A.Watterson,Toba:Java for Applications- A Way Ahead of Time (W AT) Compiler.The ThirdUsenix Conference on Object-Oriented Technologies andSystems, Oregon 1997.Ro+96T. H. Romer, D. Lee, G. M. Voelker, A. Wolman, W. A.Wong, J.-L. Baer, B. N. Bershad H. M. Levy. The Struc-ture and Performance of Interpreters,ASPLOS VII,Octo-ber 1996Sun97Java Native Interface Specification, Sun Microsystems, May 1997Wil+97T. Wilkinson et al.,Kaffe Homepage, YeLi97F.Yellin,T.Lindholm,Java Runtime Internals,Presented at JavaOne 1997, available at /java-one/sessions/slides/TT27/index.html9。