Compile la optimización de la máquina virtual JVM

¡Acostúmbrate a escribir juntos! Este es el día 18 de mi participación en el "Nuevo plan diario de Nuggets · Desafío de actualización de abril", haga clic para ver los detalles del evento .

Este artículo es principalmente un capítulo de conceptos, que resume y extrae varios conceptos básicos de JVM para la optimización de la compilación: detección de puntos calientes, inserción de métodos, análisis de escape, eliminación de subexpresiones comunes, eliminación de límites de matrices, etc.

Detección de puntos calientes

Cuando la máquina virtual encuentra que un método o bloque de código se ejecuta con mucha frecuencia, identificará estos códigos como "Código de punto caliente".

Este comportamiento de juzgar si un código es un código caliente se llama "Detección de puntos calientes", y hay dos métodos para juzgar un código caliente:

1. Detección de códigos de puntos calientes basada en muestras

La máquina virtual se usa para verificar periódicamente la parte superior de la pila de llamadas del subproceso del hermano mayor. Si se encuentra que un método aparece con frecuencia en la parte superior de la pila, entonces este método es un "método caliente". La ventaja de la detección de puntos críticos basada en muestreo es que es simple y eficiente, y es fácil obtener la relación de llamada del método (simplemente expanda la pila de llamadas).

2. Detección de código de punto caliente basada en contador

La máquina virtual que utiliza este método establece un contador para cada método (incluso un bloque de código), cuenta los tiempos de ejecución del método y lo considera un "método caliente" si los tiempos de ejecución superan un cierto umbral. Este método estadístico es más problemático de implementar, necesita establecer y mantener un contador para cada método, y la relación de llamada del método no se puede obtener directamente. Pero sus resultados estadísticos son relativamente más precisos y rigurosos.

La máquina virtual HotSpot utiliza un método de detección de puntos de acceso basado en contadores.Hay dos tipos de contadores:

  • Contador de invocaciones de métodos (Contador de invocaciones), por defecto 10000 veces en modo servidor
  • Contador de borde posterior, predeterminado 10700 veces en modo servidor

método en línea

La optimización de la restricción de métodos consiste en copiar el código del método de destino en el método llamado para evitar la llamada al método real.

  • Se activa la tecnología de detección de puntos de acceso
  • Límite de tamaño del cuerpo del método
  • 使用方法内联提升性能

测试代码:


/**
 * VM Args:
 * -XX:+PrintCompilation          控制台打印编译过程信息
 * -XX:+UnlockDiagnosticVMOptions 解锁对 JVM 进行诊断的选项参数,默认关闭
 * -XX:+PrintInlining             打印方法内联
 * @author Administrator
 */
public class MethodInliningTest {

    public static void main(String[] args) {
        testInline(args);
    }

    public static int add(int a, int b) {
        return a + b;
    }

    public static void testInline(String[] args) {
        for (int i = 0; i < 1000000; i++) {
            add(i,i+1);
        }
    }
}

复制代码

查看打印信息,当前代码触发方法内联 imagen.png

逃逸分析(Escape Analysis)

**逃逸分析(Escape Analysis)**是目前Java虚拟机中比较前沿的优化技术,它与类型继承关系分析一样,并不是直接优化代码的手段,而是为其他优化措施提供依据的分析技术。

逃逸分析的基本原理是: 分析对象动态作用域,当一个对象在方法里面被定义后,它可能被外部方法所引用,例如作为调用参数传递到其他方法中,这种称为方法逃逸;甚至还有可能被外部线程访问到,譬如赋值给可以在其他线程中访问的实例变量,这种称为线程逃逸;从不逃逸、方法逃逸到线程逃逸,称为对象由低到高的不同逃逸程度

栈上分配(Stack Allocations)

在Java虚拟机中,Java堆上分配创建对象的内存空间几乎是 Java程序员都知道的常识,Java堆中的对象对于各个线程都是共享和可见的,只要持有这个对象的引用,就可以访问到堆中存储的对象数据。虚拟机的垃圾收集子系统会回收堆中不再使用的对象,但回收动作无论是标记筛选出可回收对象,还是回收和整理内存,都需要耗费大量资源。如果确定一个对象不会逃逸出线程之外,那让这个对象在栈上分配内存将会是一个很不错的主意,对象所占用的内存空间就可以随栈帧出栈而销毁。在一般应用中,完全不会逃逸的局部对象和不会逃逸出线程的对象所占的比例是很大的,如果能使用栈上分配,那大量的对象就会随着方法的结束而自动销毁了,垃圾收集子系统的压力将会下降很多。栈上分配可以支持方法逃逸,但不能支持线程逃逸。

标量替换(Scalar Replacement)

若一个数据已经无法再分解成更小的数据来表示了,Java虚拟机中的原始数据类型(int、long等数值类型及reference类型等)都不能再进一步分解了,那么这些数据就可以被称为标量。相对的,如果一个数据可以继续分解,那它就被称为聚合量(Aggregate),Java 中的对象就是典型的聚合量。如果把一个Java对象拆散,根据程序访问的情况,将其用到的成员变量恢复为原始类型来访问,这个过程就称为标量替换。假如逃逸分析能够证明一个对象不会被方法外部访问,并且这个对象可以被拆散,那么程序真正执行的时候将可能不去创建这个对象,而改为直接创建它的若干个被这个方法使用的成员变量来代替。将对象拆分后,除了可以让对象的成员变量在栈上 (栈上存储的数据,很大机会被虚拟机分配至物理机器的高速寄存器中存储)分配和读写之外,还可以为后续进一步的优化手段创建条件。标量替换可以视作栈上分配的一种特例,实现更简单(不用考虑整个对象完整结构的分配),但对逃逸程度的要求更高,它不允许对象逃逸出方法范围内。

同步消除(Synchronization Elimination

线程同步本身是一个相对耗时的过程,如果逃逸分析能够确定一个变量不会逃逸出线程,无法被其他线程访问,那么这个变量的读写肯定就不会有竞争, 对这个变量实施的同步措施也就可以安全地消除掉。

VM 参数:

-XX:+EliminateLocks       开启锁消除(JDK1.8 默认开启)
复制代码

公共子表达式消除(Common Subexpression Elimination)

公共子表达式消除是一个非常经典的、普片应用各种编译器的优化技术。

在同一个作用域下,如果程序中有公共的计算表达式,多次出现参与运算的结果相同,计算结果为 E,那么后面的表达式直接用 E 替代结果。

一个表达式已经被计算过了,并且从先前计算到现在的计算所有的变量值都没有发生变化,称为 E 为公共表达式。对于这种表达式,我们就不需要重新计算了,只需要使用之前计算过的表达式结果替代 E 。如果这种代码优化局限与程序代码块内,可称为局部公共表达式消除(Local Common Subexpression Elimination)。如果这种优化的范围覆盖了多个代码块,那就称为全局公共子表达式消除(Global Common Subexpression Elimination)。

举个例子:

int d = (c * b) * 12 + a + (a + c * b);
复制代码

虚拟机的即时编译器会进行代码优化(因为在代码块内 c、b 的之没有改变,编译器认为没必要多次计算),上面的表达式会被优化为:

int d = E * 12 + a + (a + E);
复制代码

在这个时候编译器也可能会进行代数简化(Algebraic Simplification)。

int d = E * 13 + a + a
复制代码

经过简化后再计算起来就可以节省一些 CPU 的时间。

数组边界检查消除(Array Boounds Checking Elimination)

数组边界检查消除(Array Boounds Checking Elimination)是及时编译器优化的经典技术。对于 Java 而言本来就是一门动态安全的语言,比如我们访问一个数组 foo[i] 的时候:

  1. 先去检查数组是否为空,如果 foo 为空返回 NullPointException异常。
  2. 如果数组越界会得到 ArrayIndexOutOfBoundsExecption异常。

其实 JVM 为我们做了非常多的隐含条件判断。

但是对于这些操作,为了安全肯定是要做的,但是如果对 foo[i] 循环取值,如果编译器通过分析发现可以判定循环变量的取值永远在区间 [0, foo.length] 之内,那么在这个循环中我们就可以吧这个那个数组的上下界限检查消除掉。

举个例子:

if (i >= 0 && i < foo.length) {
    return foo[i];
}
复制代码

优化过后:

return foo[i];
复制代码

除此之外还有一种隐式异常处理,Java 空指针检查和算数运算除数为 0 的检查都采用了这种方案。 举个例子:

if (foo != null) {
    return foo.value;
} else {
    throw new NullPointException();
}
复制代码

在隐式异常优化之后:

try {
    return foo.value
} catch (segment_fault) {
    uncommon_trap();
}
复制代码

La máquina virtual registra un controlador de excepción de señal para el error de segmento. Cuando foo no está vacío, no hay sobrecarga para acceder al valor. Sin embargo, si foo está vacío, ingresará al procesamiento de excepción, que implica el proceso desde el modo de usuario al modo kernel, y luego regresa al modo de usuario una vez que se completa el procesamiento.La misma operación es mucho más lenta que la verificación nula. HptSpot seleccionará automáticamente la solución óptima en función de la información de monitoreo recopilada durante el tiempo de ejecución.

Otras optimizaciones: Eliminación de Autobox, Eliminación de Safepoint, Dereflection, etc.

Documentación de referencia

  • "Comprensión profunda de la máquina virtual JVM" Zhou Zhiming

Supongo que te gusta

Origin juejin.im/post/7087956428475858974
Recomendado
Clasificación