快来了解JDK10中引入的全新JIT编译器:Graal

Posted ImportSource

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了快来了解JDK10中引入的全新JIT编译器:Graal相关的知识,希望对你有一定的参考价值。

在()文中,我们提到jdk10中包含有一个实验性质的编译器(compiler)。它的名字叫做:Graal。这是一个基于Java的编译器(也就是使用Java语言来写的编译器)。


在Graal的github首页介绍中:


GraalVM is a project based in Oracle Labs developing a new JIT Compiler and Polyglot Runtime for the JVM. 


你会发现他们对自己的定位是开发一个全新的JIT Compiler。


JDK9被用作AOT的编译器(静态)


然而在Jdk9 的时候,就引入了Graal。但那时候的graal被用来作为一个AOT编译器。也就是静态编译。


就是在启动虚拟机之前将Java类编译为本地代码(native code)。


具体在JEP 295中可以看到细节:



上面的图中也展示了引入AOT的动机。


动机


就是因为JIT虽然也比较快,但我们知道当下的JIT编译器需要花很长的时间才能达到阈值(无论是client模式还是server模式),从而才会触发JIT编译。事实上一些不经常被用到的java方法可能永远都不会被提前编译为本地代码(native code)。那这些没有机会被编译为native code的方法,事实上就是每次都需要解释执行,虽然性能也还好,但毕竟没有被提前编译为native code的代码快,所以性能上是相对差的。


再加上其他的一些编程语言引入了AOT的编译模式,jdk也不敢怠慢,于是就在jdk9的时候引入了基于Graal的AOT静态编译器。


事实上,我们也可以通过jdk10的源码中看到jaotc的目录:


快来了解JDK10中引入的全新JIT编译器:Graal


JDK10又被用作JIT编译器(实验)


再回到本文开头那里,在JDK10的时候,Graal又被作为JIT编译器的一种选择,虽然是实验性的。


由于在9中已经引入了Graal,并且基于JVMCI接口做了适配。


JVMCI:是一个基于Java的JVM编译器接口。这个接口的目的,就是希望一些用java语言编写的编译器能够被用作JVM的动态编译器。比如:Graal编译器等。


所以JDK10就直接把已在jdk中的Graal用作JIT编译器了。但目前还只是作为实验和测试之用,并不具备商用的能力。


未来极有可能作为下一代 Java-based JIT动态编译器而被商用。


截止目前这个基于Graal的JIT编译器暂时只能用在Linux/x64平台。


并且在性能上达到甚至超越现有的JIT编译器并不是此Graal JIT编译器的目标。


那么Graal究竟是如何工作的呢?


JVMCI


上面我们已经介绍到一个接口JVMCI。从上面的介绍中我们知道他就是一个编译器接口。编译的这个动作,无非就是把一个方法编译成机器码(machine code),那么接口方法应该是这样:

interface JVMCICompiler {
   byte[] compileMethod(byte[] bytecode);
}


就是把一个方法通过byte数组传入进去,然后返回一个byte[]数组而已。


但事实上编译的时候可能需要更多的内容。所以在jdk源码中的jvmci接口是下面这样的:


public interface JVMCICompiler {
   int INVOCATION_ENTRY_BCI = -1;

   /**
    * Services a compilation request. This object should compile the method to machine code and
    * install it in the code cache if the compilation is successful.
    */
   
CompilationRequestResult compileMethod(CompilationRequest request);
}


参数是一个CompilationRequest,然后返回的结果是一个CompilationRequestResult。我们再来看看这个两个接口(或类)。


/**
* Represents a request to compile a method.
*/
public class CompilationRequest {

   private final ResolvedJavaMethod method;

   private final int entryBCI;

   /**
    * Creates a request to compile a method starting at its entry point.
    *
    *
@param method the method to be compiled
    */
   
public CompilationRequest(ResolvedJavaMethod method) {
       this(method, -1);
   }

   /**
    * Creates a request to compile a method starting at a given BCI.
    *
    *
@param method the method to be compiled
    *
@param entryBCI the bytecode index (BCI) at which to start compiling where -1 denotes the
    *            method's entry point
    */
   
public CompilationRequest(ResolvedJavaMethod method, int entryBCI) {
       assert method != null;
       this.method = method;
       this.entryBCI = entryBCI;
   }

   /**
    * Gets the method to be compiled.
    */
   
public ResolvedJavaMethod getMethod() {
       return method;
   }

   /**
    * Gets the bytecode index (BCI) at which to start compiling where -1 denotes a non-OSR
    * compilation request and all other values denote an on stack replacement (OSR) compilation
    * request.
    */
   
public int getEntryBCI() {
       return entryBCI;
   }

   @Override
   public String toString() {
       return method.format("%H.%n(%p)@" + entryBCI);
   }
}


/**
* Provides information about the result of a {
@link CompilationRequest}.
*/
public interface CompilationRequestResult {

   /**
    * Determines if the compilation was successful.
    *
    *
@return a non-null object whose {@link Object#toString()} describes the failure or null if
    *         compilation was successful
    */
   
Object getFailure();
}


我们再来看看HotSpot里具体的基于JVMCI接口的Graal的实现。


快来了解JDK10中引入的全新JIT编译器:Graal


快来了解JDK10中引入的全新JIT编译器:Graal


看了这么多。我想说的意思是你完全可以在compileMethod加上一些自己的逻辑,因为这个方法就是编译器把bytecode编译为machine code的地方。


class HotSpotGraalCompiler implements JVMCICompiler {
   CompilationRequestResult compileMethod(CompilationRequest request) {
       System.err.println("现在去编译方法:" + request.getMethod().getName());
   ...
   }
}


好,你大概知道JVMCI是个什么吧。


Graal Graph


没错,看到graph这个单词就应该大体要说什么了。通过前面的介绍你已经知道了Graal就是负责把输入的byte[]转换成另一个byte[]。现在我们就来说Graal在转换的过程中的一点点理论和数据结构的东西。因为有一点别致。


本质上来说,编译器就是处理和操作你的代码。在内部它肯定要把你的代码转换成某种数据结构来表示程序。那么bytecode或指令列表算是一种方式。但是这些都不够形象。


Graal就想到了使用图来表示待编译的代码。比如说你现在有一个简单加法操作,就是把两个变量相加。那么这个图(graph)就是有两个节点负责分别load本地变量,还有第三个节点是加法操作,两条边就表示加载局部变量的结果,然后指向加号节点。用语言描述有点绕,直接看图:



也有人把这种表示叫做程序依赖图(program-dependence-graph)。


从jdk10的代码中也可以看到有关graph的数据结构。


更多细节代码请去看JDK10的源码


总结


总之,Graal作为一个编译器。它可以被用来干任何它适合干的事情。在JDK9中引入它用作AOT静态编译器,在JDK10的时候使用它来作为一个全新的JIT编译器(实验的)。通过介绍Graal,让我们知道了在JVM中引入基于Java语言的编译器会带来现在没有的好处。为了支持JIT编译未来面向开发者开放,JDK9中引入了JVMCI接口的概念。过去你修改了JVM的代码,你不得不重新发布整个代码库。如今你可以把JVM和编译器分开部署,你可以部署一个A版本的JVM,然后再单独部署一个B版本的编译器,这都是JVMCI为我们带来的好处。

以上是关于快来了解JDK10中引入的全新JIT编译器:Graal的主要内容,如果未能解决你的问题,请参考以下文章

深入理解Java虚拟机(10-13)学习总结

JDK10新特性

JDK10 新特性

JDK14中JVM的性能优化

语言特性

Ruby 2.6.0 首个预览版本发布,引入重要新特性 JIT