Método de inserción en la JVM

1. Introducción

En este tutorial, veremos qué método está incluido en la máquina virtual Java y cómo funciona.

También veremos cómo obtener y leer la información relacionada con la inserción desde la JVM y qué podemos hacer con esta información para optimizar nuestro código.

2. ¿Qué método es el Inlining?

Básicamente, la inserción es una forma de optimizar el código fuente compilado en tiempo de ejecución reemplazando las invocaciones de los métodos ejecutados con mayor frecuencia por sus cuerpos.

Aunque hay compilación involucrada, no la realiza el compilador javac tradicional , sino la propia JVM. Para ser más precisos, es responsabilidad del compilador Just-In-Time (JIT) , que es parte de la JVM; javac solo produce un código de bytes y permite que JIT haga la magia y optimice el código fuente.

Una de las consecuencias más importantes de este enfoque es que si compilamos el código usando Java antiguo, lo mismo. El archivo de clase será más rápido en las JVM más nuevas. De esta forma no es necesario volver a compilar el código fuente, solo actualizar Java.

3. ¿Cómo lo hace JIT?

Básicamente, el compilador JIT intenta integrar los métodos que llamamos a menudo para evitar la sobrecarga de la invocación de un método . Se tienen en cuenta dos cosas a la hora de decidir si incorporar un método o no.

Primero, utiliza contadores para realizar un seguimiento de cuántas veces invocamos el método. Cuando el método se llama más de un número específico de veces, se vuelve "caliente". Este umbral está establecido en 10,000 por defecto, pero podemos configurarlo a través del indicador JVM durante el inicio de Java. Definitivamente no queremos incluir todo en línea, ya que llevaría mucho tiempo y produciría un código de bytes enorme.

Debemos tener en cuenta que la alineación se llevará a cabo solo cuando lleguemos a un estado estable. Esto significa que tendremos que repetir la ejecución varias veces para proporcionar suficiente información de perfil para el compilador JIT.

Además, estar "caliente" no garantiza que el método esté en línea. Si es demasiado grande, el JIT no lo integrará. El tamaño aceptable está limitado por el indicador -XX: FreqInlineSize = , que especifica el número máximo de instrucciones de código de bytes que se pueden insertar en línea para un método.

Sin embargo, se recomienda encarecidamente no cambiar el valor predeterminado de esta bandera a menos que estemos absolutamente seguros de saber qué impacto podría tener. El valor predeterminado depende de la plataforma; para Linux de 64 bits, es 325.

El JIT inlines estática , privadas , o finales métodos en general . Y aunque los métodos públicos también son candidatos para la inserción, no todos los métodos públicos estarán necesariamente integrados. La JVM debe determinar que solo hay una implementación de dicho método . Cualquier subclase adicional evitaría la inserción y el rendimiento disminuirá inevitablemente.

4. Encontrar métodos calientes

Seguramente no queremos adivinar qué está haciendo el JIT. Por lo tanto, necesitamos alguna forma de ver qué métodos están en línea o no. Podemos lograr esto fácilmente y registrar toda esta información en la salida estándar configurando algunos indicadores JVM adicionales durante el inicio:

-XX:+PrintCompilation -XX:+UnlockDiagnosticVMOptions -XX:+PrintInlining

La primera bandera se registrará cuando ocurra la compilación JIT. El segundo indicador habilita indicadores adicionales, incluido -XX: + PrintInlining , que imprimirá qué métodos se están insertando y dónde.

Esto nos mostrará los métodos en línea en forma de árbol. Las hojas están anotadas y marcadas con una de las siguientes opciones:

  • en línea (caliente) : este método está marcado como activo y está en línea
  • demasiado grande : el método no está activo, pero también su código de bytes generado es demasiado grande, por lo que no está en línea
  • método caliente demasiado grande : este es un método caliente, pero no está en línea porque el código de bytes es demasiado grande

Debemos prestar atención al tercer valor e intentar optimizar los métodos con la etiqueta “método caliente demasiado grande”.

Generalmente, si encontramos un método caliente con una declaración condicional muy compleja, deberíamos intentar separar el contenido de la declaración if y aumentar la granularidad para que el JIT pueda optimizar el código. Lo mismo ocurre con el interruptor y el lucro sentencias de bucle.

Podemos concluir que la inserción de un método manual es algo que no necesitamos hacer para optimizar nuestro código. La JVM lo hace de manera más eficiente y posiblemente hagamos que el código sea largo y difícil de seguir.

4.1. Ejemplo

Veamos ahora cómo podemos comprobar esto en la práctica. Primero crearemos una clase simple que calcula la suma de los primeros N números enteros positivos consecutivos:

public class ConsecutiveNumbersSum { private long totalSum; private int totalNumbers; public ConsecutiveNumbersSum(int totalNumbers) { this.totalNumbers = totalNumbers; } public long getTotalSum() { totalSum = 0; for (int i = 0; i < totalNumbers; i++) { totalSum += i; } return totalSum; } }

A continuación, un método simple hará uso de la clase para realizar el cálculo:

private static long calculateSum(int n) { return new ConsecutiveNumbersSum(n).getTotalSum(); }

Finalmente, llamaremos al método varias veces y veremos qué sucede:

for (int i = 1; i < NUMBERS_OF_ITERATIONS; i++) { calculateSum(i); }

En la primera ejecución, lo ejecutaremos 1.000 veces (menos que el valor umbral de 10.000 mencionado anteriormente). Si buscamos el resultado del método calculateSum () , no lo encontraremos. Esto se esperaba ya que no lo llamamos suficientes veces.

Si ahora cambiamos el número de iteraciones a 15,000 y buscamos la salida nuevamente, veremos:

664 262 % com.baeldung.inlining.InliningExample::main @ 2 (21 bytes) @ 10 com.baeldung.inlining.InliningExample::calculateSum (12 bytes) inline (hot)

Podemos ver que esta vez el método cumple las condiciones para la inserción y la JVM lo integra.

Vale la pena mencionar nuevamente que si el método es demasiado grande, el JIT no lo integrará, independientemente del número de iteraciones. Podemos verificar esto agregando otra bandera al ejecutar la aplicación:

-XX:FreqInlineSize=10

Como podemos ver en la salida anterior, el tamaño de nuestro método es de 12 bytes. El indicador -XX: FreqInlineSize limitará el tamaño del método elegible para la inserción a 10 bytes. Consecuentemente, la alineación no debería tener lugar esta vez. Y, de hecho, podemos confirmar esto echando otro vistazo a la salida:

330 266 % com.baeldung.inlining.InliningExample::main @ 2 (21 bytes) @ 10 com.baeldung.inlining.InliningExample::calculateSum (12 bytes) hot method too big

Aunque hemos cambiado el valor del indicador aquí con fines ilustrativos, debemos enfatizar la recomendación de no cambiar el valor predeterminado del indicador -XX: FreqInlineSize a menos que sea absolutamente necesario.

5. Conclusión

En este artículo, vimos qué método está integrado en la JVM y cómo lo hace el JIT. Describimos cómo podemos verificar si nuestros métodos son elegibles para la inserción o no y sugerimos cómo hacer uso de esta información al tratar de reducir el tamaño de los métodos largos llamados con frecuencia que son demasiado grandes para insertarlos.

Finalmente, ilustramos cómo podemos identificar un método caliente en la práctica.

Todos los fragmentos de código mencionados en el artículo se pueden encontrar en nuestro repositorio de GitHub.