Compresión de puntero Java


El artículo discutirá la implementación de la compresión del puntero en la máquina virtual Java de 64 bits , que está controlada por la opción UseCompressedOops y está habilitada de manera predeterminada para sistemas de 64 bits que comienzan con Java SE 6u23.


Descripción del problema


En una JVM de 64 bits, los punteros ocupan 2 veces más espacio de memoria (sorpresa-sorpresa) que en una de 32 bits. Esto puede aumentar el tamaño de los datos en 1,5 veces en comparación con el mismo código para la arquitectura de 32 bits. Al mismo tiempo, en la arquitectura de 32 bits, solo se pueden direccionar 2 ^ 32 bytes (4 GB), que es bastante pequeño en el mundo moderno.


Escribamos un pequeño programa y veamos cuántos bytes ocupan los objetos enteros:


import java.util.stream.IntStream; import java.util.stream.Stream; class HeapTest { public static void main(String ... args) throws Exception { Integer[] x = IntStream.range(0, 1_000_000).boxed().toArray(Integer[]::new); Thread.sleep(6000000); Stream.of(x).forEach(System.out::println); } } 

Aquí destacamos un millón de objetos de la clase Integer y nos quedamos dormidos durante mucho tiempo. La última línea es necesaria para que el compilador no ignore repentinamente la creación de la matriz (aunque en mi máquina, los objetos se crean normalmente sin esta línea).


Compilamos y ejecutamos el programa con la compresión de puntero deshabilitada:


 > javac HeapTest.java > java -XX:-UseCompressedOops HeapTest 

Usando la utilidad jcmd , observamos la asignación de memoria:


 > jps 45236 HeapTest ... > jcmd 45236 GC.class_histogram 



La imagen muestra que el número total de objetos es 1000128 , y el tamaño de la memoria que ocupan estos objetos es 24003072 bytes . Es decir 24 bytes por objeto (por qué exactamente se escribirán 24 a continuación).


Y aquí está la memoria del mismo programa, pero con la bandera UseCompressedOops activada :




Ahora cada objeto ocupa 16 bytes .
Las ventajas de la compresión son obvias =)


Solución


¿Cómo comprime los punteros JVM? Esta técnica se llama Compressed Oops . Oop significa puntero de objeto ordinario o puntero de objeto ordinario .


El truco es que en un sistema de 64 bits, los datos en la memoria están alineados con la palabra máquina, es decir. 8 bytes cada uno. Y la dirección siempre tiene tres bits cero al final.


Si guarda el puntero desplazando la dirección 3 bits hacia la derecha (la operación se llama codificar ), y antes de usar, desplaza 3 bits hacia la izquierda (respectivamente, decodificar ), entonces puede ajustar punteros de 32 bits con un tamaño de 35 bits , es decir. Dirección de hasta 32 GB (2 ^ 35 bytes).


Si el tamaño de almacenamiento dinámico para su programa es superior a 32 GB, la compresión deja de funcionar y todos los punteros se convierten en 8 bytes de tamaño.


Cuando la opción UseCompressedOops está habilitada, se comprimen los siguientes tipos de punteros:


  • Campo de clase para cada objeto
  • Objetos de campo de clase
  • Elementos de una matriz de objetos.

Los objetos de la propia JVM nunca se comprimen. En este caso, la compresión se produce a nivel de la máquina virtual, y no por código de bytes.


Lea más sobre cómo colocar objetos en la memoria


Ahora, usemos la utilidad jol (Java Object Layout) para ver más de cerca la cantidad de memoria que ocupa nuestro Integer en diferentes JVM:


 > java -jar jol-cli-0.9-full.jar estimates java.lang.Integer ***** 32-bit VM: ********************************************************** java.lang.Integer object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 8 (object header) N/A 8 4 int Integer.value N/A 12 4 (loss due to the next object alignment) Instance size: 16 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total ***** 64-bit VM: ********************************************************** java.lang.Integer object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 16 (object header) N/A 16 4 int Integer.value N/A 20 4 (loss due to the next object alignment) Instance size: 24 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total ***** 64-bit VM, compressed references enabled: *************************** java.lang.Integer object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 12 (object header) N/A 12 4 int Integer.value N/A Instance size: 16 bytes Space losses: 0 bytes internal + 0 bytes external = 0 bytes total ***** 64-bit VM, compressed references enabled, 16-byte align: ************ java.lang.Integer object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 12 (object header) N/A 12 4 int Integer.value N/A Instance size: 16 bytes Space losses: 0 bytes internal + 0 bytes external = 0 bytes total 

La diferencia entre "VM de 64 bits" y "VM de 64 bits, referencias comprimidas habilitadas" es reducir el encabezado del objeto en 4 bytes. Además, en el caso sin compresión, se hace necesario agregar 4 bytes más para alinear los datos en la memoria.


¿Qué es este encabezado de objeto? ¿Por qué disminuyó en 4 bytes?



La imagen muestra un encabezado de objeto de 12 bytes, es decir con la opción UseCompressedOops habilitada. El encabezado consta de algunos indicadores internos de JVM, así como un puntero a la clase de este objeto. Se puede ver que el puntero a la clase toma 32 bits. Sin compresión, ocuparía 64 bits y el tamaño del encabezado del objeto ya sería de 16 bytes.


Por cierto, puede ver que hay otra opción para la alineación de 16 bytes. En este caso, puede aumentar la memoria hasta 64 GB.


Contras Compresión de punteros


La compresión de punteros, por supuesto, tiene un inconveniente obvio: el costo de las operaciones de codificación y decodificación cada vez que se accede al puntero. Los números exactos variarán según la aplicación.


Por ejemplo, aquí hay un gráfico de pausas del recolector de basura para punteros comprimidos y no comprimidos, tomados de aquí Java GC en Números - POO comprimidos



Se puede ver que con la compresión activada, las pausas de GC duran más. Puede leer más sobre esto en el artículo en sí (el artículo es bastante antiguo - 2013).


Referencias


Ups comprimidos en el HVSpot JVM
¿Cómo asigna objetos JVM?
CompressedOops: Introducción a las referencias comprimidas en Java
Truco detrás de OV comprimido de JVM
Mejoras de rendimiento de la máquina virtual Java HotSpot

Source: https://habr.com/ru/post/440166/


All Articles