[转帖]Java方法的JIT编译

java,方法,jit,编译 · 浏览次数 : 0

小编点评

**方法执行编译等级逐渐提升,有机会通过JIT编译为特定平台汇编执行,以此获得最佳性能。** **JIT编译的三个关键参数:** - **`-XX:+DontCompileHugeMethods`**:如果设置该参数为 `true`,则不会编译方法大于 8000 字节的巨型方法。 - **`-XX:HugeMethodLimit`**:该参数指定巨型方法的代码大小限制,默认值为 8000 字节。 - **`-XX:+DontCompileHugeMethods`**:该参数和 `-XX:HugeMethodLimit` 一起使用,则会根据巨型方法大小限制编译方法。 ** `canBeCompiled()` 方法用于判断方法是否能被 JIT 编译。** 该方法考虑以下因素: - 方法是否抽象或原生 - 方法的代码大小是否超过 `c.hugeMethodLimit` - 方法是否包含 `@Snippet` 注解 - 方法是否被 `Snippet` 注解注释 **其他参数的影响:** - `-XX:CompileCommand` 参数控制 JIT 编译命令,默认值为 `dontinline`。 - `-XX:CompileProfile` 参数用于设置编译时使用的运行信息。

正文

https://www.jianshu.com/p/a6275e239eac

 

Java方法执行一般会利用分层编译,先通过c1解释执行。方法执行编译等级逐渐提升,有机会通过JIT编译为特定平台汇编执行,以此获得最好的性能。

方法执行除了达到一定热度外,是否JIT编译也受到以下两个参数影响:

-XX:+DontCompileHugeMethods

-XX:HugeMethodLimit=8000

HotSpot默认不会编译巨型方法,也就是-XX:+DontCompileHugeMethods-XX:-DontCompileHugeMethods可以支持巨型方法编译。

判断方法是否为大对象由-XX:HugeMethodLimit=8000来决定,8000表示JIT编译字节码大小超过8000字节的方法就是巨型方法,这个阈值在产品版HotSpot里无法调整。

DontCompileHugeMethodsHugeMethodLimit默认值在/hotspot/src/share/vm/runtime/globals.hpp

product(bool, DontCompileHugeMethods, true,
      "Do not compile methods > HugeMethodLimit")  

develop(intx, HugeMethodLimit,  8000,                                     
      "Don't compile methods larger than this if "                      
      "+DontCompileHugeMethods") 

下面看JDK9CompileTheWorld.java,路径为

$ cd ./hotspot/src/jdk.internal.vm.compiler/share/classes/org.graalvm.compiler.hotspot/src/org/graalvm/compiler/hotspot

源码canBeCompiled() Line 756决定Java方法是否能被JIT编译。

private boolean canBeCompiled(HotSpotResolvedJavaMethod javaMethod, int modifiers) {
    // 抽象方法或原生方法不编译
    if (Modifier.isAbstract(modifiers) || Modifier.isNative(modifiers)) {
        return false;
    }

    // 获取JVM配置参数
    GraalHotSpotVMConfig c = compiler.getGraalRuntime().getVMConfig();
    // 如果c.dontCompileHugeMethods为true,根据c.hugeMethodLimit判断该方法是否为巨型方法
    if (c.dontCompileHugeMethods && javaMethod.getCodeSize() > c.hugeMethodLimit) {
        println(verbose || methodFilters != null,
                        String.format("CompileTheWorld (%d) : Skipping huge method %s (use -XX:-DontCompileHugeMethods or -XX:HugeMethodLimit=%d to include it)",
                                        classFileCounter,
                                        javaMethod.format("%H.%n(%p):%r"),
                                        javaMethod.getCodeSize()));
        return false;
    }

    // dontinline标志开启不编译方法,用来排除可能有问题的目标方法
    // Allow use of -XX:CompileCommand=dontinline to exclude problematic methods
    if (!javaMethod.canBeInlined()) {
        return false;
    }

    // 注解包含@Snippets不编译
    for (Annotation annotation : javaMethod.getAnnotations()) {
        if (annotation.annotationType().equals(Snippet.class)) {
            return false;
        }
    }

    return true; // 经过判断可以被JIT编译
}

总的来说只要符合以下任一条件就不能被编译:

  1. 抽象方法或原生方法;
  2. 巨型方法;
  3. dontinline标志的方法;
  4. 方法注解包含@Snippets

经过canBeCompiled() Line 627被判定可以被编译的方法送到compileMethod() Line 725等待编译。

// Are we compiling this class?
MetaAccessProvider metaAccess = JVMCI.getRuntime().getHostJVMCIBackend().getMetaAccess();
if (classFileCounter >= startAt) {
    println("CompileTheWorld (%d) : %s", classFileCounter, className);

    // 循环遍历构造方法,判断能否JIT编译
    for (Constructor<?> constructor : javaClass.getDeclaredConstructors()) {
        HotSpotResolvedJavaMethod javaMethod = (HotSpotResolvedJavaMethod) metaAccess.lookupJavaMethod(constructor);
        if (canBeCompiled(javaMethod, constructor.getModifiers())) {
            compileMethod(javaMethod);
        }
    }

    // 循环遍历实例方法,判断能否JIT编译
    for (Method method : javaClass.getDeclaredMethods()) {
        HotSpotResolvedJavaMethod javaMethod = (HotSpotResolvedJavaMethod) metaAccess.lookupJavaMethod(method);
        if (canBeCompiled(javaMethod, method.getModifiers())) {
            compileMethod(javaMethod);
        }
    }

    // Also compile the class initializer if it exists
    // 类初始化器也是能被编译
    HotSpotResolvedJavaMethod clinit = (HotSpotResolvedJavaMethod) metaAccess.lookupJavaType(javaClass).getClassInitializer();
    if (clinit != null && canBeCompiled(clinit, clinit.getModifiers())) {
        compileMethod(clinit);
    }
}

目标方法compileMethod(),编译完成后下次执行时新编译方法替换旧方式。

// Compiles a method and gathers some statistics.
private void compileMethod(HotSpotResolvedJavaMethod method, int counter) {
    try {
        long start = System.currentTimeMillis();
        long allocatedAtStart = MemUseTrackerImpl.getCurrentThreadAllocatedBytes();
        int entryBCI = JVMCICompiler.INVOCATION_ENTRY_BCI;
        HotSpotCompilationRequest request = new HotSpotCompilationRequest(method, entryBCI, 0L);
        // For more stable CTW execution, disable use of profiling information
        boolean useProfilingInfo = false;
        boolean installAsDefault = false;
        // 创建一个编译任务
        CompilationTask task = new CompilationTask(jvmciRuntime, compiler, request, useProfilingInfo, installAsDefault);
        // 开始编译任务
        task.runCompilation();

        // Invalidate the generated code so the code cache doesn't fill up
        HotSpotInstalledCode installedCode = task.getInstalledCode();
        if (installedCode != null) {
            installedCode.invalidate();
        }

        memoryUsed.getAndAdd(MemUseTrackerImpl.getCurrentThreadAllocatedBytes() - allocatedAtStart);
        compileTime.getAndAdd(System.currentTimeMillis() - start);
        compiledMethodsCounter.incrementAndGet();
    } catch (Throwable t) {
        // Catch everything and print a message
        println("CompileTheWorld (%d) : Error compiling method: %s", counter, method.format("%H.%n(%p):%r"));
        printStackTrace(t);
    }
}

参考链接

Java中一个方法字节码的长度为什么会影响程序并发下的性能? - RednaxelaFX的回答 - 知乎

-XX:-DontCompileHugeMethods - BLOG OF JEROEN LEENARTS

与[转帖]Java方法的JIT编译相似的内容:

[转帖]Java方法的JIT编译

https://www.jianshu.com/p/a6275e239eac Java方法执行一般会利用分层编译,先通过c1解释执行。方法执行编译等级逐渐提升,有机会通过JIT编译为特定平台汇编执行,以此获得最好的性能。 方法执行除了达到一定热度外,是否JIT编译也受到以下两个参数影响: -XX:+

[转帖]java中方法不要写太长的真正原因

https://www.iteye.com/blog/enetor-976070 java中一般建议一个方法不要写的过长,不方便维护和阅读是其中的一个原因,但是其真正性能的原因大家知道吗? 我们知道,JVM一开始是以解释方式执行字节码的。当这段代码被执行的次数足够多以后,它会被动态优化并编译成机器码

【转帖】26.Java本地方法的理解(native方法)

目录 1.什么是本地方法?2. 为什么要使用Native method? 1.什么是本地方法? 本地方法就是java代码里面写的native方法,它没有方法体。是为了调用C/C++代码而写的。在JNI程序里面使用。 例子: 下面的代码在java中声明了一个本地方法,但是没有实现,它的实现是在c或者c

[转帖]什么是方法内联

写在前面 本文隶属于专栏《100个问题搞定Java虚拟机》,该专栏为笔者原创,引用请注明来源,不足和错误之处请在评论区帮忙指出,谢谢! 本专栏目录结构和文献引用请见100个问题搞定Java虚拟机 解答 方法内联是指,在编译过程中,当遇到方法调用时,将目标方法的方法体纳入编译范围之中,并取代原方法调用

[转帖]Java实战之OutOfMemoryError异常问题及解决方法

https://www.jb51.net/article/244872.htm + 目录 在Java虚拟机规范的描述中,除了程序计数器外,虚拟机内存的其他几个运行时区域都有发生OutOfMemoryError (下文称OOM)异常的可能。本篇主要结合着【深入理解Java虚拟机】一书当中整理了本篇博客

[转帖]Java IO篇:序列化与反序列化

1、什么是序列化: 两个服务之间要传输一个数据对象,就需要将对象转换成二进制流,通过网络传输到对方服务,再转换成对象,供服务方法调用。这个编码和解码的过程称之为序列化和反序列化。所以序列化就是把 Java 对象变成二进制形式,本质上就是一个byte[]数组。将对象序列化之后,就可以写入磁盘进行保存或

[转帖]Java IO篇:序列化与反序列化

1、什么是序列化: 两个服务之间要传输一个数据对象,就需要将对象转换成二进制流,通过网络传输到对方服务,再转换成对象,供服务方法调用。这个编码和解码的过程称之为序列化和反序列化。所以序列化就是把 Java 对象变成二进制形式,本质上就是一个byte[]数组。将对象序列化之后,就可以写入磁盘进行保存或

[转帖]【JVM】类加载机制

什么是类的加载 将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang.Class对象,用来封装类在方法区内的数据结构。类的加载的最终产品是位于堆区中的Class对象,Class对象封装了类在方法区内的数据结构,并且向Java程序员提供

[转帖]netflix火焰圖(profile + 轉為圖像)

本文转载自 ligeforrent 查看原文 2017-06-30 0 file/file/ 运维/运维/ profile/profile/ 图像/图像/ netnet 使用方法:利用google的lightweight-Java-profiler獲取java進程的profile(類hprof格式)

[转帖]JVM类加载机制

概述 虚拟机把描述类的数据从class文件加载到内存,并对数据进行校验,转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制。 类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang.Cl