Comprensión exhaustiva de la recolección de basura JVM: comprensión de conceptos importantes (9)

Enumeración del nodo raíz

Los nodos que pueden usarse como GC Roots existen principalmente en referencias globales (como constantes o propiedades estáticas de clase) y contextos de ejecución (como tablas de variables locales en marcos de pila). Aunque el objetivo es claro, no es fácil encontrar estos nodos de manera eficiente. Para 所有收集器的根节点枚举这一步都需要暂停用户线程的,毫无疑问枚举根节点需要面临”Stop the world“的困扰la fecha, . Ahora, el algoritmo de análisis de accesibilidad tarda más tiempo en encontrar la cadena de referencia que se puede lograr simultáneamente con el subproceso de usuario (CMS), pero la enumeración del nodo raíz siempre debe ser de manera de garantizar la coherencia (ejecución durante toda la enumeración El subsistema parece estar congelado en un determinado momento). Solo puede llevarse a cabo en la instantánea. No habrá cambios en el conjunto de nodos raíz y la preocupación de referencia del nodo durante el proceso de análisis. Si este punto no se puede cumplir, No se puede garantizar la precisión de los resultados del análisis. Esta es una de las razones importantes por las que el proceso de recolección de basura debe pausar todos los subprocesos del usuario. Incluso si el CMS, G1, ZGC y otros recolectores que afirman que el tiempo de pausa es controlable, o casi no hay pausa, el nodo raíz también debe estar en pausa. .
En la actualidad, las máquinas virtuales Java convencionales utilizan una recolección de basura precisa. Cuando se detiene el subproceso de usuario, no es necesario verificar todos los contextos de ejecución y las posiciones de referencia global. La máquina virtual debe tener una forma de obtenerlo directamente. Donde están las referencias a los objetos almacenados. 在HotSpot的解决方案中,是使用一组成为OopMap(Ordinary Object Pointer,OOP)的数据结构来达到这个目的的. Una vez que se completa la acción de carga de clase, HotSpot calculará qué tipo de datos está en el desplazamiento en el objeto, y durante el proceso de compilación en tiempo real, también registrará en la posición específica en la pila y registrará qué posiciones son referencias . De esta manera, el recopilador puede conocer directamente esta información al escanear, y no necesita una búsqueda verdadera y completa desde las Raíces del GC, como el área del método.

Punto de seguridad

Con la ayuda de OopMap, HotSpot puede completar de forma rápida y precisa la enumeración de Roots GC, pero se produce un problema muy real: puede provocar cambios en la relación de referencia o cambios en el contenido de OopMap. Hay muchas instrucciones, si es que cada una Todas las instrucciones generan OopMap, que requerirá mucho espacio de memoria adicional para almacenar.
De hecho, HotSpot no genera un OopMap para cada instrucción, sino que solo registra esta información en una ubicación específica, que se llama 安全点(SafePoint). Con la configuración del punto de seguridad, se determina que cuando se ejecuta el programa de usuario, no es posible hacer una pausa en ninguna posición en el flujo de instrucciones de código para la recolección de basura, pero es obligatorio que el punto de seguridad se ejecute antes de que pueda suspenderse. Por lo tanto, la selección de puntos de seguridad no puede ser demasiado pequeña para hacer que el recolector espere demasiado ni aumentar demasiado la carga de memoria en tiempo de ejecución. La selección de la ubicación del punto de seguridad se basa básicamente en "si tiene las características de permitir que el programa se ejecute durante un tiempo prolongado" como estándar, porque el tiempo de ejecución de cada instrucción es muy corto, y es poco probable que el programa sea porque la longitud del flujo de instrucciones es demasiado larga Y durante una ejecución prolongada, cuando ”长时间执行“的最明显特征就是指令序列的复用,例如方法调用、循环跳转、异常跳转等属于指令序列复用,所以只有这些工功能的指令才会产生安全点。
ocurre la recolección de basura, cómo hacer que todos los hilos (excluyendo el hilo que ejecuta la llamada JNI [Java Native Interface]) se ejecuten hasta el punto seguro más cercano, y luego pause, hay dos maneras:
(1) 抢断式中断: La interrupción de robo no requiere el código de ejecución del subproceso. Cuando se recolecta basura, el sistema primero interrumpe todos los subprocesos del usuario. Si se descubre que el lugar donde se interrumpe el subproceso del usuario no está en un punto seguro, reanudará la ejecución de este subproceso y lo dejará correr. Al lugar seguro más cercano. Ahora casi ninguna máquina virtual implementa una interrupción de robo para pausar la línea en respuesta a eventos de GC
(2) 主动式中断: cuando la recolección de basura necesita interrumpir el hilo del usuario, no hay necesidad de operar directamente el hilo, solo establece un bit de bandera, cada hilo ejecuta el proceso , Sondeará activamente esta bandera . Una vez que el indicador de interrupción sea verdadero, se suspenderá activamente en el punto seguro más cercano. La ubicación de la marca de votación y el punto de seguridad son coincidentes.Además, se deben agregar todos los objetos creados y otros lugares que necesitan asignar memoria en el almacenamiento dinámico de Java. Esto es para facilitar la comprobación de si la recolección de basura está a punto de ocurrir y evitar que no haya suficiente memoria para asignar nuevos objetos.

Zona segura

El uso de puntos de seguridad parece resolver perfectamente el problema de cómo pausar hilos de usuario y permitir que la máquina virtual ingrese al estado de recolección de basura, pero la situación real no es necesariamente la misma.El mecanismo del punto de seguridad asegura que cuando se ejecute el programa, se encuentre en mucho tiempo. entrar en el proceso de recolección de basura punto de seguridad 但是程序“不执行”的时候呢?程序不执行就是没有分配处理器时间,典型的场景就是用户线程处于Sleep状态或者Blocked状态,这时候线程无法响应虚拟机的中断请求,不能再走到安全的地方再中断挂起自己,虚拟机也显然不可能等待线程被重新激活分配处理器时间,对于这种情况采用安全区域(Safe Region)来解决.
Cuando el hilo del usuario se ejecuta en el fragmento de código del área segura, la relación de referencia no cambiará, por lo que es seguro iniciar teléfonos basura en cualquier parte de esta área. También podemos considerar el área segura como un punto seguro extendido.
Cuando el hilo del usuario ejecuta el código en el área segura, primero identificará que ha entrado en el área segura, de modo que cuando la máquina virtual inicia la recolección de basura durante este tiempo, no tiene que administrar estos hilos que se han declarado en el área segura. Cuando el subproceso quiere abandonar el dominio de seguridad, comprueba si la máquina virtual ha completado la enumeración del nodo raíz (u otras etapas en el proceso de recolección de basura que necesitan suspender el subproceso de usuario), si se completa, el subproceso continúa ejecutándose; de ​​lo contrario, espera Hasta recibir una señal que pueda abandonar el dominio de seguridad.

Juego de memoria y mesa de cartas

分代垃圾收集中为了解决对象跨代引用的问题,垃圾收集器在新生代中建立了名为记忆集(Remembered Set)的数据结构, Para evitar agregar toda la generación anterior al rango de exploración de GC Roots. De hecho, no solo la nueva generación y la vieja generación tienen problemas de referencia entre bandas: todos los recolectores de basura que involucran el comportamiento del teléfono móvil en algunas regiones, como los recolectores G1, ZGC y Shenandoah, enfrentarán problemas de referencia entre generaciones.
记忆集是用于记录从非收集区域指向收集区域的指针集合的抽象数据结构. Si no se consideran la eficiencia y el costo, la implementación más simple puede implementar esta estructura de datos con una matriz de objetos referenciados a través de generaciones contenidas en el área de no recolección. Esto solo registra el esquema de implementación que contiene todos los objetos de referencia de generación cruzada, tanto en términos de ocupación de espacio como de costos de mantenimiento. En el escenario de recolección de basura, el recolector solo necesita determinar si existe un puntero al área de recolección en un área que no es de recolección a través del conjunto de memoria, y no necesita conocer todos los detalles de estos punteros de banda cruzada. El diseñador puede elegir una granularidad de registro más gruesa para ahorrar los costos de almacenamiento y mantenimiento del conjunto de memoria. La siguiente es una lista de las opciones de precisión de registro (por supuesto, también puede elegir fuera de este rango):
(1) 字长精度: el registro es exacto para una máquina Longitud de la palabra (es decir, los bits de direccionamiento del procesador, como el común de 32 bits o 64 bits, esta precisión determina la longitud del puntero al que la máquina accede a la dirección de memoria física), la palabra contiene punteros de generación cruzada.
(2) 对象精度: cada registro es exacto para un objeto, y hay campos en el objeto que sufren punteros de generación cruzada.
(3) 卡精度: cada registro es preciso para un área de memoria. Hay objetos en esta área que contienen punteros de generación cruzada.

Entre ellos, la precisión de la tarjeta es una 卡表(Card Table)forma de realizar el conjunto de memoria, que también es la forma más utilizada de implementación del conjunto de memoria. El conjunto de memoria es una "estructura de datos" abstracta, y la tabla de tarjetas es una implementación concreta del conjunto de memoria, que define la precisión de grabación del conjunto de memoria, la relación de mapeo con la memoria de almacenamiento dinámico, etc. La forma más simple de la tabla de tarjetas puede ser solo una matriz de bytes, la máquina virtual HotSpot hace exactamente lo mismo. El siguiente código es la implementación de la tabla de tarjetas predeterminada de HotSpot

CARD_TABLE [this address >> 9] =0

Cada elemento de la matriz de bytes CARD_TABLE corresponde a un bloque de memoria de un tamaño específico en su área de memoria identificada. Este bloque de memoria se llama 卡页. En general, el tamaño de la página de la tarjeta es el número de bytes de la potencia de N, y se puede ver en el código anterior que la página de la tarjeta utilizada en HotSpot es la potencia de 9, que es de 512 bytes (la dirección se desplaza a la derecha en 9 bits, que es equivalente Yu dividido por 512). Si la dirección inicial de la memoria de la tabla de tarjetas es 0x0000, los elementos 0, 1 y 2 de la matriz CARD_TABLE corresponden a las tarjetas 0x0000 ~ 0x001FF, 0x0200 ~ 0x03FF y 0x0400 ~ 0x05FF. Como se muestra en la figura:
Tabla de cartas y página de cartas
la memoria de una página de tarjeta generalmente contiene más de un objeto, siempre que haya un (o más) campo de objeto en la página de tarjeta con este puntero de banda cruzada, entonces el valor del elemento de matriz de la tabla de tarjeta correspondiente Marcado como 1, este elemento se llama sucio, ninguna marca es 0. Cuando se produce la recolección de basura, siempre que los elementos sucios en la tabla de tarjetas se filtren, es fácil averiguar qué bloques de memoria de páginas de tarjetas contienen punteros de generación cruzada y agregarlos a las Raíces del GC para escanear.

Barrera de escritura

Utilizamos el método de conjunto de memoria para resolver el problema del rango de exploración de GC Roots, pero no hemos resuelto el problema del mantenimiento de la "tabla de tarjetas", como cuándo se ensucian y quién es responsable de ensuciarlas.
Cuando la tabla de cartas se ensucia es clara, cuando hay objetos en otras áreas generacionales que se refieren a objetos en esta área, el elemento correspondiente de la tabla de cartas debe estar sucio. Sucio es el punto en el tiempo que debe ocurrir en la asignación del tipo de referencia En ese momento, pero ¿cómo ensuciarse, es decir, cómo actualizar la tabla de la tarjeta de mantenimiento en el momento de la asignación de objetos? Unirse es explicar la ejecución del bytecode, que es relativamente fácil de manejar. La máquina virtual es responsable de la ejecución de cada bytecode, con suficiente espacio de intervención, pero en el contexto de compilación y ejecución, el código después de la compilación instantánea ya es una máquina puramente rota. El flujo de instrucciones, esto debe encontrar un medio a nivel de código de máquina, poner la acción de mantener la tabla de tarjetas en cada operación de asignación.
En la máquina virtual HotSpot, el 写屏障(Write Barrier)estado de la tabla de tarjetas se mantiene a través de la tecnología. La barrera de escritura puede verse como un aspecto AOP de la acción de "asignación de campo de tipo de referencia" a nivel de máquina virtual. Cuando se realiza la asignación de tipo de referencia, se genera una notificación envolvente (Alrededor) para que el programa realice acciones adicionales, es decir, antes y después de la asignación Dentro de la cobertura de la barrera de escritura. 在赋值前的部分的写屏障称为写前屏障(Pre-write Barrier),在赋值之后的称为写后屏障。Además del recopilador G1 de la máquina virtual HotSpot, los otros recopiladores solo usan la barrera de escritura. El siguiente código es para actualizar la tabla de tarjetas después de escribir la barrera:

void oop_field_store(oop* field,oop new_value){
   //引用类型字段赋值
   *field = new_value;
   // 写后屏障,更新卡表信息
   post_write_barrier(field,new_value);
}

Una vez que se aplica la barrera de escritura, la máquina virtual genera las instrucciones correspondientes para todas las operaciones de asignación. Una vez que el recopilador agrega una operación de tabla de tarjeta de actualización a la barrera de escritura, independientemente de si se actualiza la referencia de la generación anterior a la nueva generación, solo la referencia debe actualizarse cada vez , Habrá una sobrecarga adicional, pero esta sobrecarga es mucho menor que el costo de escanear toda la generación anterior cuando GC menor.
Además de la sobrecarga de las barreras de escritura, las tablas de tarjetas también se enfrentan al problema del "intercambio falso" en escenarios de alta concurrencia. El seudocompartimiento es un problema que a menudo debe tenerse en cuenta cuando se trata de detalles de concurrencia de bajo nivel. Ahora el sistema de caché del procesador central se almacena en unidades de líneas de caché (Línea de caché). Cuando varios hilos modifican variables mutuamente independientes, si estas variables se comparten Una línea de caché se afectará entre sí (reescritura, no válida o sincronización) y conducirá a un rendimiento reducido, que es un problema de seudocompartición.
Suponiendo que el tamaño de la línea de caché del procesador es de 64 bytes, dado que un elemento de la tabla de tarjetas ocupa un byte, los elementos de la tabla de 64 tarjetas compartirán la misma línea de caché. La memoria total de las páginas de la tarjeta que corresponde a estos elementos de la tabla de 64 tarjetas es de 32 KB (64 * 512 bytes), lo que significa que se escriben diferentes tablas de tarjetas en la misma línea de caché y afectan el rendimiento, solo cuando el elemento de la tabla de tarjetas no está marcado. Se marcará como sucio cuando esté desactualizado, es decir, la actualización de la tabla de tarjetas agregará la siguiente lógica de juicio:

if(CARD_TABLE[this address>>9] !=0 ){
	CARD_TABLE[this address>>9] =0;
}

Después de JDK1.7, la máquina virtual HotSpot agregó un nuevo parámetro -XX:+UseCondCardMarkpara determinar cuándo abrir la lógica de juicio de actualización de la tabla de tarjetas. Después de abrir, se agregará una sobrecarga de juicio adicional, pero puede evitar el problema del intercambio falso. Pérdida, tiempo de activación para probar las compensaciones en función de las condiciones de funcionamiento reales.

Análisis de accesibilidad concurrente

Los recolectores de basura de los lenguajes de programación actuales se basan básicamente en el algoritmo de análisis de accesibilidad para determinar si el objeto está vivo. El algoritmo de análisis de accesibilidad teóricamente requiere que todo el proceso se analice en función de una instantánea que pueda garantizar la coherencia. Significa que todo el proceso debe basarse en una instantánea que garantice la coherencia para poder analizar, lo que significa que la operación del hilo del usuario debe congelarse durante todo el proceso. En el paso de enumeración del nodo raíz, debido a que GC Roots es relativamente raro en comparación con todos los objetos en el montón completo, y también hay varios métodos de optimización (OopMap), la pausa que trae ya es muy corta y relativamente fija. (Sin crecimiento con la racionalización del montón). 可是从GC Roots再往下遍历对象图,这一步骤的停顿时间必定与Java堆空间容量成正比:堆空间越大,存储的对象越多,对象图结构越复杂,要标记更多对象而产生的停顿时间自然就更长久。
La etapa de "marcado" es una característica común de todos los algoritmos de recolección de basura de la cadena de seguimiento. Si esta etapa aumentará el tiempo de pausa en proporción al montón, el impacto afectará a casi todos los recolectores de basura. Si esta parte se puede debilitar Si hay una pausa, los beneficios serán sistemáticos.
Para resolver o reducir la pausa del hilo del usuario, primero descubra por qué se puede atravesar el gráfico del objeto en una instantánea que puede garantizar la coherencia. Aquí presentamos la 三色标记(Tri-color Marking)mayoría de las herramientas para ayudar en la derivación. De acuerdo con la condición "has visitado", se divide en los siguientes tres colores:
(1) 白色: indica que el objeto no ha sido visitado por el recolector de basura . Obviamente, al comienzo del análisis de accesibilidad, todos los objetos son blancos. Si al final del análisis, el objeto sigue siendo blanco, significa inalcanzable.
(2) 黑色: indica que el objeto ha sido accedido por el recolector de basura, y todas las referencias a este objeto han sido escaneadas. El objeto negro representa que ha sido escaneado y es seguro sobrevivir. Si hay otras referencias de objetos que apuntan al objeto negro, no hay necesidad de volver a escanearlo. Un objeto negro no puede apuntar directamente (sin pasar por un objeto gris) a un objeto blanco.
(3) 灰色: indica que la recolección de basura ha accedido al objeto, pero al menos una referencia a este objeto no se ha escaneado .

Durante el análisis de accesibilidad, si el subproceso del usuario está congelado, solo el subproceso del recopilador funciona y no habrá problemas. Si el subproceso de usuario y el recopilador funcionan simultáneamente, el recopilador marca el color en el objeto y el subproceso de usuario está modificando la relación de referencia, es decir, modificando la estructura gráfica del objeto, lo que puede tener dos consecuencias. Una de ellas es marcar erróneamente los objetos que originalmente se marcaron como moribundos como vivos. Esto es tolerable, excepto que se ha generado algo de basura flotante que se ha escapado de la recolección, y se limpiará la próxima vez. La otra es marcar erróneamente los objetos que estaban vivos como muertos. Esta es una consecuencia muy fatal, y el programa definitivamente cometerá errores. La siguiente figura es un diagrama esquemático del proceso de generar un error cuando el objeto se marca simultáneamente:
Inserte la descripción de la imagen aquí
solo cuando se cumplen ambas condiciones al mismo tiempo, el problema de "el objeto desaparece", es decir, el objeto originalmente negro se marca erróneamente como blanco:
(1) Se insertó un evaluador Múltiples referencias nuevas de objetos negros a objetos blancos.
(2) El evaluador elimina todas las referencias directas o indirectas de objetos grises a objetos blancos.
Por eso resolvemos el problema de la desaparición de objetos durante el escaneo concurrente, solo necesitamos destruir alguna de estas dos condiciones. Esto dio como resultado dos soluciones: 增量更新(Incremental Update)y 原始快照(Snapshot At The Beginning,SATB).
La primera condición que se destruirá mediante la actualización incremental es la primera condición: cuando un objeto negro inserta una nueva relación de referencia que apunta a un objeto blanco, se graba la referencia recién insertada y, una vez que finaliza el escaneo simultáneo, se escanea nuevamente. Puede simplificarse entender que una vez que un objeto negro se inserta nuevamente con una referencia blanca, cambiará nuevamente a un objeto gris .
La segunda condición es destruir la instantánea original. Cuando el objeto gris va a eliminar la relación de referencia del objeto blanco, registre la referencia que se va a eliminar. Después de que finalice la exploración simultánea, hable sobre la relación de referencia en estos registros. El objeto gris es la raíz, y escanea nuevamente. Puede entenderse simplemente como: no importa si la relación de referencia se elimina o no, la búsqueda se realizará de acuerdo con la instantánea del gráfico de objeto en el momento en que se inicia el escaneo .
Independientemente de la inserción o eliminación del registro de relación de referencia, la operación de grabación de la máquina virtual se logra a través de la barrera de escritura. En la máquina virtual HotSpot, se aplican tanto la actualización incremental como la instantánea original. Por ejemplo, CMS se basa en la actualización incremental para el marcado concurrente, y G1 y Shenandoah se implementan con la instantánea original.

Inserte la descripción de la imagen aquí

41 artículos originales publicados · Me gustaron 14 · Visitantes más de 10,000

Supongo que te gusta

Origin blog.csdn.net/Yunwei_Zheng/article/details/105296375
Recomendado
Clasificación