Implementación del algoritmo GCRoot de la serie de recolección de basura JVM

secuencia

feliz dia nacional con amigos
inserte la descripción de la imagen aquí

introducción

Este artículo presentará en detalle cómo la máquina virtual HotSpot implementa el algoritmo de accesibilidad raíz (GCROOT)

Libro de referencia: "Comprensión profunda de Java Virtual Machine"

Proyecto personal de intercambio de conocimientos de java: dirección de gitee

Proyecto personal de intercambio de conocimientos de Java: dirección de github

Enumerar nodos raíz

Tomando como ejemplo la operación de encontrar cadenas de referencia de los nodos de GC Roots en el análisis de accesibilidad, los nodos que se pueden usar como GC Roots se encuentran principalmente en referencias globales (como constantes o atributos estáticos de clase) y contextos de ejecución (como tablas de variables locales). en marcos de pila), muchas aplicaciones ahora solo tienen cientos de megabytes en el área de método. Si desea verificar las referencias aquí una por una, inevitablemente consumirá mucho tiempo.

Además, la sensibilidad del análisis de accesibilidad al tiempo de ejecución también se refleja en las pausas del GC, porque este análisis debe realizarse en una instantánea que pueda garantizar la consistencia; aquí "consistencia" significa que todo el sistema de ejecución parece estar congelado en un determinado punto en el tiempo, y no se permite que la relación de referencia del objeto cambie durante el proceso de análisis. Si este punto no se cumple, no se puede garantizar la precisión de los resultados del análisis. Esta es una de las razones importantes por las que todos los subprocesos de ejecución de Java deben detenerse cuando el GC está en progreso (Sun llama a esto "Stop The World"), incluso en el recopilador de CMS que dice (casi) que no hay pausas. También es necesario para hacer una pausa al enumerar el nodo raíz.

Dado que las máquinas virtuales Java convencionales actuales utilizan GC preciso, cuando el sistema de ejecución se detiene, no hay necesidad de verificar todos los contextos de ejecución y las ubicaciones de referencia global sin omisiones.La máquina virtual debe tener una forma de saber directamente dónde se almacenan las referencias de objetos. . En la implementación de HotSpot, se utiliza un conjunto de estructuras de datos llamado OopMap para lograr este propósito. Cuando se completa la carga de la clase, HotSpot calcula qué tipo de datos se encuentran en qué desplazamiento en el objeto y los compila en JIT. Durante el proceso, qué ubicaciones en la pila y los registros son referencias también se registran en ubicaciones específicas. De esta forma, el GC puede conocer directamente la información al escanear. El siguiente es un código local del método String.hashCode() generado por HotSpot Client VM. Puede ver que la instrucción de llamada en 0x026eb7a9 tiene un registro OopMap, que indica el registro EBX y el área de memoria en el desplazamiento 16 en la pila. Una referencia a un puntero de objeto ordinario (Puntero de objeto ordinario), el rango efectivo es desde la instrucción de llamada hasta 0x026eb730 (la posición inicial del flujo de instrucciones) + 142 (el desplazamiento del registro OopMap) = 0x026eb7be, es decir, el instrucción hlt.

inserte la descripción de la imagen aquí

punto seguro

Con la ayuda de OopMap, HotSpot puede completar de forma rápida y precisa la enumeración de raíces de GC, pero surge un problema muy real: hay muchas instrucciones que pueden causar cambios en la relación de referencia o cambios en el contenido de OopMap, si para cada instrucción Ambos generan el OopMap correspondiente, lo que requerirá mucho espacio adicional, por lo que el costo de espacio de GC será muy alto.

De hecho, HotSpot no genera un OopMap para cada instrucción, como se mencionó anteriormente, solo registra esta información en una "ubicación específica", estas ubicaciones se denominan puntos seguros (Safepoint), es decir, no están en todos los lugares cuando el El programa se ejecuta. Puede pausar para iniciar GC, y solo pausar cuando se alcanza un punto seguro. La selección de Safepoint no puede ser demasiado pequeña para que el GC espere demasiado, ni demasiado frecuente para que la carga del tiempo de ejecución aumente excesivamente. Por lo tanto, la selección de puntos seguros se basa básicamente en el estándar de "si el programa tiene las características de permitir que el programa se ejecute durante mucho tiempo"; dado que el tiempo de ejecución de cada instrucción es muy corto, es poco probable que el programa se ejecute. ejecutado debido a la longitud del flujo de instrucciones La característica más obvia de la "ejecución larga" es la multiplexación de secuencias de instrucciones, como llamadas a métodos, saltos de bucle, saltos de excepción, etc., por lo que las instrucciones con estas funciones generarán Safepoint.

Para Sefepoint, otra cuestión que debe tenerse en cuenta es cómo hacer que todos los subprocesos (sin incluir los subprocesos que ejecutan llamadas JNI) "ejecuten" hasta el punto seguro más cercano y luego se detengan cuando se produzca GC. Hay dos opciones para elegir: suspensión preventiva y suspensión voluntaria. La suspensión preventiva no requiere la cooperación activa del código de ejecución del subproceso. Cuando ocurre GC, todos los subprocesos se interrumpen primero. , si se encuentra que el lugar donde se interrumpe el subproceso no está en el punto seguro, simplemente reanude el hilo y déjelo "correr" hasta el punto seguro. Prácticamente no hay implementaciones de máquinas virtuales que utilicen interrupciones preventivas para suspender subprocesos en respuesta a eventos de GC.

La idea de la interrupción activa es que cuando el GC necesita interrumpir el subproceso, no opera directamente en el subproceso, sino que simplemente establece un indicador, y cada subproceso sondea activamente este indicador cuando se ejecuta, y cuando se encuentra el indicador de interrupción. para ser verdad, se interrumpe y se suspende. El lugar donde la bandera de sondeo coincide con el punto seguro, más el lugar donde se debe asignar memoria para crear un objeto. El comando de prueba en el siguiente código es un comando de sondeo generado por HotSpot. Cuando el subproceso debe suspenderse, la máquina virtual establece la página de memoria en 0x160100 como ilegible. Cuando el subproceso ejecuta el comando de prueba, generará una trampa automática. señal de excepción El subproceso se suspende en el controlador de excepciones para implementar la espera, de modo que una instrucción de ensamblaje complete el sondeo de punto seguro y active la interrupción del subproceso.

inserte la descripción de la imagen aquí

Area segura

El uso de Safepoint parece haber resuelto perfectamente el problema de cómo ingresar al GC, pero la situación real no lo es necesariamente. El mecanismo Safepoint asegura que cuando se ejecuta el programa, encontrará un Safepoint que puede ingresar al GC en un corto período de tiempo. Sin embargo, ¿qué pasa cuando el programa "no se ejecuta"? La llamada no ejecución del programa significa que no se asigna tiempo de CPU. Un ejemplo típico es que el subproceso está en estado de suspensión o estado bloqueado. En este momento , el subproceso no puede responder a la solicitud de interrupción de la JVM y "camina" a una caja fuerte. Donde la interrupción está pendiente, es obviamente menos probable que la JVM espere a que se reasigne tiempo de CPU al subproceso. En este caso se necesita una región segura (Safe Region) para solucionarlo.

Un área segura significa que dentro de un fragmento de código, la relación de referencia no cambiará. Es seguro iniciar GC en cualquier lugar de esta región. También podemos considerar Safe Region como un Safepoint extendido.

Cuando el subproceso ejecuta el código en la Región segura, primero marca que ha ingresado a la Región segura, de modo que cuando la JVM inicia GC durante este período, no necesita preocuparse por el subproceso que se marca a sí mismo como el estado de Región segura. . Cuando el hilo está a punto de salir de la Región Segura, verifica si el sistema ha completado la enumeración del nodo raíz (o todo el proceso de GC), si se completa, el hilo continúa ejecutándose, de lo contrario, debe esperar hasta que reciba la caja fuerte para salir de la Región Segura Señal de la Región.

La implementación del algoritmo GCRoot en este artículo se extrajo del libro "Comprensión profunda de la máquina virtual Java"

Supongo que te gusta

Origin blog.csdn.net/a_ittle_pan/article/details/127195130
Recomendado
Clasificación