recolector de basura JVM (1)

Tabla de contenido

1. Cómo considerar GC

2. Cómo determinar si un objeto está "muerto"

3. Teoría de la colección generacional

4. Algoritmo de recolección de basura

5. Detalles de implementación del algoritmo HotSpot


1. Cómo considerar GC

Garbage Collection (GC) tiene una historia más larga que Java y nació en el MIT en 1960.

Tres cosas a considerar con GC

  • ¿Qué memoria hay que recuperar?
  • Momento del reciclaje
  • como reciclar

1. ¿Qué memoria es necesario reciclar?

Espacio privado de subprocesos: el contador de programas, la pila de máquinas virtuales, la pila de métodos locales y los marcos de la pila se introducen y salen a medida que los métodos entran y salen. La cantidad de memoria asignada en cada marco de pila se puede determinar después de cargar la clase. Cuando finaliza el método o hilo, toda la memoria ocupada se puede reciclar naturalmente sin pensar demasiado.

El área de método y montón de Java, el espacio de memoria compartido por estos dos subprocesos, es muy incierto. Porque sus contenidos sólo se conocen en tiempo de ejecución y cambian constantemente. El recolector de basura se centra en esta parte de la memoria, la asigna y la recicla.

2. GC conservador y GC preciso

El método de posicionamiento de acceso de los objetos está determinado por el método GC utilizado por la máquina virtual:

  • GC conservador: use el acceso al identificador
  • GC exacto: uso de acceso directo al puntero

1. General conservador

Durante la GC, el análisis de accesibilidad se utiliza generalmente para marcar los objetos supervivientes, lo que implica el problema del recorrido de la cadena de referencia de GCRoots.

Esto implica la cuestión de cómo determinar si los datos en una pila son una dirección de referencia o datos básicos.

Hay dos maneras:

  • Verificación de alineación: verifique el número de bits en los datos. Las direcciones en la JVM son todas de 32 bits. Si no son de 32 bits, definitivamente no son direcciones de referencia.
  • Verificación de límites superior e inferior: si el número de bits de los datos coincide con el número de bits de la dirección, verifique si está dentro del rango de la memoria del montón. Si excede el rango del montón, significa que no está una dirección de referencia.

Sin embargo, si los datos son datos de 32 bits que cumplen con el rango de memoria del montón, la JVM no puede determinar si pertenecen a la dirección de referencia. El GC basado en esta idea se llama "GC conservador".

Características del GC conservador:

  • No se puede determinar con precisión si un dato es información básica o una dirección de referencia
  • Problema de referencia falsa. Si no se puede juzgar, se creerá de manera conservadora que los datos son una dirección de referencia, lo que generará registros adicionales de las relaciones de referencia de algunos objetos, lo que provocará que no se limpien.
  • Necesidad de introducir handle pool como capa intermedia

¿Por qué el GC conservador debe introducir grupos de identificadores?

Por ejemplo, hay una dirección de referencia A, que apunta a un objeto B, y resulta que hay un dato básico C cuyo valor es el mismo que la dirección de referencia del objeto B.

Si se modifica la dirección de B, la máquina virtual cree que tanto A como C son referencias a B, por lo que se deben modificar sus valores.

El problema es que C son datos básicos y ciertamente no deberían modificarse.

La máquina virtual no puede juzgar, por lo que se introduce un grupo de identificadores. Todas las referencias deben apuntar al grupo de identificadores y luego se encuentra el objeto real en el grupo de identificadores.

De esta manera, para cambiar la dirección del objeto, solo necesita cambiar la dirección de mapeo del grupo de identificadores y no es necesario modificar los datos en la pila, lo que elimina la posibilidad de modificar incorrectamente los datos básicos.

JDK 1.0 utiliza identificadores para la ubicación de objetos.

2. GC preciso

La característica de un GC preciso es que puede saber con precisión si un dato en la pila representa datos básicos o una dirección de referencia.

¿Cómo hacerlo?

  • Durante el proceso de compilación, puede conocer el tipo de variable, registrar la información del tipo y almacenarla en OOPMap.
  • Posteriormente, según OOPMap, se podrá conocer el significado de los datos.

Por lo tanto, un GC preciso puede localizar objetos directamente a través de punteros y acceder a los objetos es más eficiente.

2. Cómo determinar si un objeto está "muerto"

Casi todas las instancias de objetos se almacenan en el montón. El recolector de basura primero debe determinar qué objetos en el montón todavía están "vivos (útiles)" y qué objetos están "muertos (ya no se puede hacer referencia a ellos por ningún medio)".

1. Algoritmo de recuento de referencias

El algoritmo de conteo de referencias tiene un principio simple y una alta eficiencia de juicio, pero 主流的JVM都没有使用它.

El método de recuento de referencias consiste en establecer un contador para cada objeto. Cada vez que se hace referencia a algún lugar, el contador es +1; cuando la referencia expira, el contador es -1. Una vez que el contador llega a 0, el objeto está muerto.

beneficio:

  • Amortice las operaciones de gestión de memoria en cada operación de referencia cuando el programa se esté ejecutando.
  • Al administrar la memoria, no necesita conocer los detalles específicos de los objetos en tiempo de ejecución, es decir, no necesita conocer la ubicación de cada objeto, solo necesita verificar el recuento de referencias de cada objeto.

defecto:

  • Es difícil resolver el problema de las referencias circulares entre objetos.
    • Mientras haya una referencia circular, los contadores de los dos objetos nunca serán 0, pero en realidad están desconectados del programa y deben reciclarse.
  • Necesidad de garantizar la seguridad del hilo.
    • Debido a que en un entorno de subprocesos múltiples, habrá múltiples subprocesos que realizarán diversas operaciones en el mismo lote de objetos, las modificaciones en el recuento de referencias deben garantizar la atomicidad y la visibilidad.
  • Cada objeto ocupa memoria adicional para almacenar el recuento de referencia.
  • Al reciclar estructuras de datos grandes, aún enfrentará el problema de STW, porque los objetos reciclados en una sola vez pueden ser muy grandes.

2. Algoritmo de análisis de accesibilidad

Todos los subsistemas de gestión de memoria de los principales lenguajes de programación comerciales actuales (como Java y C#) utilizan análisis de accesibilidad para determinar si los objetos están vivos o muertos.

La idea básica es 一系列称为“Gc Roots”的根对象buscar hacia abajo de acuerdo con la relación de referencia utilizándola como conjunto de nodos iniciales. El camino recorrido por la búsqueda se denomina "cadena de referencia".

Si no hay una cadena de referencia entre un objeto y Gc Roots (es decir, es inalcanzable), significa que no se puede hacer referencia a él y entonces puede morir.

1. ¿Qué objetos se pueden utilizar como Gc Roots?

En pocas palabras, todos los objetos de Gc Roots cumplen una condición: no se pueden reciclar ni en este momento ni nunca.

  • Referencias dentro de la JVM (por ejemplo: objetos de tipo contenedor correspondientes a tipos básicos, objetos de excepción residentes, cargadores de clases del sistema)

  • Objetos a los que se hace referencia en la tabla de variables locales en el marco de la pila

  • Variable estática del tipo de referencia de clase en el área de método

  • 方法区中的常量引用的对象(Por ejemplo: referencias en el grupo constante de cadenas)

  • El objeto al que hace referencia el método nativo en la pila de métodos nativos

  • Objeto retenido por bloqueo de sincronización (palabra clave sincronizada)

  • 分代收集时,需要考虑其他区域对本区域的跨代引用

Además de estos, según el recolector de basura seleccionado por el usuario y el área de memoria actualmente recuperada, se pueden agregar temporalmente otros objetos para formar una colección completa de Gc Roots.

Por ejemplo, recolección generacional y reciclaje parcial, si la recolección de basura solo se realiza en un área determinada en el montón de Java, los objetos en esta área también pueden ser referenciados por objetos en otras áreas, por lo que los objetos en estas áreas asociadas deben agregarse a Gc Raíces En la colección, los objetos útiles no serán reciclados.

Para evitar que la colección de Gc Roots sea demasiado grande, diferentes recolectores de basura también han realizado sus propias optimizaciones.

3. ¿Qué es una cita?

Ya sea que se trate de un recuento de referencias o un análisis de accesibilidad, si el objeto está vivo está determinado por "si el objeto está referenciado".

1. Definición temprana

En JDK 1.2, la definición de referencia aún era muy escasa: si el valor almacenado en los datos de tipo de referencia representa la dirección inicial de otra memoria, se dice que los datos de tipo de referencia representan una determinada memoria o un objeto.

Un objeto tiene sólo dos estados: "referenciado" y "sin referencia". Razonable, pero no lo suficientemente flexible.

Limitaciones de las primeras definiciones

En muchos escenarios, es necesario realizar la recolección de basura tanto como sea posible. Esperamos que la máquina virtual pueda realizar un procesamiento especial en algunos objetos:

  • Si el espacio de memoria es suficiente, no se reciclarán.
  • El espacio de memoria aún es limitado después de la recolección de basura, así que recíclelos para liberar algo de espacio en la memoria.

2. Cuatro estados de referencia

  • Fuertemente referencia

    • Esta es la definición más tradicional de referencia, que se refiere a la operación de asignación de referencia en el código del programa.
    • No importa cuál sea la situación, el recolector de basura nunca recuperará objetos a los que se haga referencia fuerte.
  • Referencia suave

    • Describe algunos objetos que son útiles pero que no necesariamente existen.
    • Representado en Java por la clase java.lang.ref.SoftReference
    • Los objetos a los que se hace referencia suavemente se incluirán en el alcance del reciclaje para el reciclaje secundario antes del OOM del sistema. Si la memoria aún es insuficiente, se lanzará OOM.
  • Referencia débil

    • Similares a las referencias suaves, pero más débiles. Los objetos a los que se hace referencia débil solo pueden sobrevivir hasta que se produzca la siguiente recolección de basura.
    • Cuando el recolector de basura comienza a funcionar, los objetos a los que solo se hace referencia débilmente se reciclarán independientemente de si la memoria actual es suficiente.
    • Las referencias débiles deben usarse junto con una cola de referencia (ReferenceQueue). Si el objeto al que hace referencia una referencia débil se recolecta como basura, la máquina virtual Java agregará la referencia débil a la cola de referencia asociada a ella.
  • Referencia fantasma

    • Las referencias virtuales deben usarse junto con una cola de referencia (ReferenceQueue)

    • Si un objeto tiene una referencia virtual, la referencia virtual se agrega a la cola de referencia asociada a él antes de reciclarse.

      El programa puede saber si el objeto al que se hace referencia será recolectado como basura determinando si se ha agregado una referencia virtual a la cola de referencia y luego hacer algo.

    • Utilice PhantomReference para implementar referencias virtuales en Java

4. ¿El sujeto “debe morir”?

Los objetos que el algoritmo de análisis de accesibilidad determine que son inalcanzables no se reciclarán inmediatamente, sino que estarán en la etapa de "retraso de muerte".

要真正判断一个对象死亡,要经过两次标记过程:

  • Por primera vez, se realizó un análisis de accesibilidad y se encontró que no había ninguna cadena de referencia conectada a GC Roots.
  • La segunda vez, se realiza una evaluación para determinar si es necesario ejecutar el método finalize() en este objeto. La ejecución exitosa del método finalize() es la última oportunidad que tiene el objeto de evitar la muerte.

finalizar()

Si este objeto no cubre el método finalize(), o la máquina virtual ha llamado al método finalize(), entonces "no es necesario ejecutar el método finalize()".

Si se considera que este objeto es "necesario para ejecutar el método finalize()", se colocará en una cola F-Queue y luego será ejecutado por un subproceso Finalizer para ejecutar su método finalize(). Este subproceso Finalizer lo crea la máquina virtual y tiene una prioridad de programación baja.

Al ejecutar su método finalize(), la máquina virtual solo se asegurará de que el método comience a ejecutarse, pero no esperará a que se complete la ejecución, porque el método finalize() puede ejecutarse lentamente o en un bucle infinito si espera. para que cada finalize() complete la ejecución, puede hacer que otros objetos de F-Queue esperen para siempre, o incluso hacer que todo el subsistema de reciclaje de memoria colapse.

Después de ejecutar finalize(), el recopilador marcará los objetos en la cola F dos veces. Mientras el objeto se una con éxito a la cadena de referencia durante la fase finalize(), podrá continuar sobreviviendo.

Solo hay una posibilidad para este tipo de autorrescate, porque la máquina virtual llama automáticamente al método finalize() de un objeto una vez como máximo.

(¡En realidad, este método no se recomienda en absoluto!)

5. Área del método de reciclaje

La especificación de la máquina virtual menciona que no es obligatorio que la máquina virtual implemente la recolección de basura en el área del método.

La eficiencia de la recolección de basura en el área del método es relativamente baja: una recolección de basura en el espacio del montón puede liberar entre el 70 y el 99% del espacio de memoria, pero no se puede recuperar mucha memoria en el área del método.

El área de método recicla principalmente dos partes:

  • constantes obsoletas
  • Clase que ya no se usa

como reciclar

Una constante es fácil de detectar, siempre que no se haga referencia a ella en ningún lugar, se puede reciclar.

La descarga de tipos es problemática y requiere que se cumplan tres condiciones al mismo tiempo:

  1. Todos los objetos de esta clase han sido reciclados.
  2. El cargador de clases que cargó esta clase ha sido reciclado (más difícil de implementar)
  3. No se hace referencia al objeto Class de esta clase. es decir, no es posible acceder a esta clase a través de la reflexión

Es necesario el reciclaje de memoria en el área de métodos.

En escenarios donde se utilizan ampliamente la reflexión, el proxy dinámico, CGLib y otros marcos de código de bytes, y en escenarios donde los cargadores de clases se personalizan con frecuencia, la máquina virtual debe tener la capacidad de descargar tipos; de lo contrario, el área del método puede ser OOM.

3. Teoría de la colección generacional

1. Dos hipótesis de primera generación

La teoría de la Colección Generacional se basa en dos hipótesis generacionales:

  1. Hipótesis de la generación débil: la mayoría de los objetos "viven y mueren"
  2. Fuerte hipótesis generacional: cuantas más veces un objeto sobrevive a la recolección de basura, más difícil será morir

Tenga en cuenta que existe una hipótesis importante que no se ha mencionado en este momento.

2. El significado práctico de la hipótesis.

Estas dos hipótesis sientan los principios de diseño de los recolectores de basura de uso común:

  • El recolector debe dividir el montón de Java en diferentes áreas y luego asignar los objetos reciclados a diferentes áreas para su almacenamiento según su antigüedad (la cantidad de veces que han sobrevivido a la recolección de basura).
    • Si la mayoría de los objetos de un área nacen y se destruyen rápidamente, júntelos y recíclelos con más frecuencia.
    • Si los objetos restantes son objetos difíciles de eliminar, júntelos y la máquina virtual reciclará esta área con menos frecuencia.

Esto tiene en cuenta la sobrecarga de tiempo de la recolección de basura y el uso efectivo de la memoria.

3. Mejora de la teoría de la colección generacional.

Después de dividir el montón de Java en diferentes áreas, el recolector de basura solo puede reciclar una o varias áreas a la vez. Esto es lo que sucede:

  • GC menor: solo recolección de basura para la nueva generación
  • Major GC: solo para recolección de basura en la generación anterior
  • GC completo: colección de montón completa

Para diferentes áreas, también existen algoritmos de recolección de basura coincidentes "marcar-copiar", "marcar-borrar" y "marcar-organizar".

4. Nueva generación y vieja generación

La teoría de la colección generacional implementada por la JVM comercial dividirá al menos el montón de Java en dos partes:

  • Generación joven
  • Vieja generación

El significado de estas dos partes es: una gran cantidad de objetos morirán durante cada recolección de basura en la nueva generación, y una pequeña cantidad de objetos que sobrevivan a cada recolección se promoverán gradualmente a la generación anterior para su almacenamiento.

5. Colección generacional y referencia intergeneracional

La recopilación generacional no puede simplemente dividir áreas y luego recopilarlas por separado, porque 对象不是孤立的,对象之间可能存在跨代引用.

Para considerar este factor, entonces 要进行一次新生代的GC,为了找到被老年代引用的对象,就必须遍历整个老年代,这是非常耗时的.

Así añadido 第三条假说:跨代引用相对于同代引用来说,占极少数.

Esta hipótesis no surge de la nada, sino que es una base implícita que se deduce de las dos primeras hipótesis: dos objetos que tienen una relación de referencia mutua tienden a sobrevivir o morir al mismo tiempo.

Por ejemplo, si un objeto de la nueva generación tiene una referencia de generación cruzada, sobrevivirá a cada recolección de basura. Después de algunos ciclos, será promovido a la generación anterior y la referencia de generación cruzada se borrará.

6. Recolección de basura bajo referencias intergeneracionales.

La teoría muestra que el número de citas intergeneracionales es relativamente pequeño.

Conjunto recordado

En lugar de escanear toda la generación anterior en busca de una pequeña cantidad de referencias entre generaciones, se puede establecer una estructura de datos global (conjunto recordado) en la nueva generación.

Divida la generación anterior en varios bloques pequeños e identifique qué bloque tiene referencias de generación cruzada. Cuando ocurre un GC de nueva generación, solo necesita atravesar esta pequeña área.

Estos objetos de antigua generación se agregarán a GC Roots para realizar un análisis de accesibilidad y evitar que se reciclen los objetos de nueva generación a los que hacen referencia.

Obviamente, esto es más eficiente que registrar cada referencia o recorrer toda la generación anterior.

Mesa de cartas

La solución proporcionada por HotSpot es una tecnología llamada Card Table.

Esta tecnología divide todo el montón en tarjetas con un tamaño de 512 bytes y mantiene una tabla de tarjetas para almacenar un bit de identificación para cada tarjeta.

Esta bandera indica si la tarjeta correspondiente puede tener una referencia al objeto de nueva generación. Si es posible, consideramos que la tarjeta está sucia.

Al realizar el GC de nueva generación, busque cartas sucias en la mesa de cartas y luego agregue los objetos en las cartas sucias a GC Roots of Minor GC.

Después de completar el escaneo de todas las tarjetas sucias, la máquina virtual Java borrará los bits de bandera de todas las tarjetas sucias.

Tenga en cuenta que durante la nueva generación de GC, los objetos se copiarán y la dirección del objeto cambiará, por lo que el bit de identificación de la tarjeta donde se encuentra la referencia también debe actualizarse en este momento para garantizar que la tarjeta sucia deba contener un referencia al objeto de nueva generación.

7. Recolección de basura para diferentes generaciones.

  • GC parcial: se refiere a que el objetivo no es todo el montón de Java, específicamente dividido en lo siguiente:
    • GC menor/GC joven: solo recolección de basura para la generación joven (muy común)
    • Recolección de la vieja generación (Major GC/Old GC): solo recolección de basura para la vieja generación. (Solo el recopilador CMS tiene este comportamiento de recopilar la generación anterior por separado)
    • GC Mixto: Recolección de basura para toda la nueva generación y parte de la vieja generación. (Solo el recopilador G1 tiene este comportamiento)
  • Colección de montón completo (GC completo): recolección de basura para todo el área de método y montón de Java

8. Juicio dinámico de edad generacional

El umbral de edad para que un objeto pase a la generación anterior se puede establecer mediante el parámetro -XX:MaxTenuringThreshold. El valor predeterminado es 15

Cuando Hotspot atraviesa todos los objetos, acumula el tamaño que ocupan en orden ascendente de edad.

当累积的某个年龄大小超过了 survivor 区的一半时, tome el valor más pequeño entre esta edad y MaxTenuringThreshold como el nuevo umbral de edad de promoción.

Luego, los objetos de la edad generacional que superen este nuevo umbral se promoverán a la generación anterior, liberando espacio para sobrevivientes por adelantado.

4. Algoritmo de recolección de basura

1. Información general

Desde la perspectiva de cómo determinar la muerte de un objeto, los algoritmos de recolección de basura se pueden dividir en dos categorías:

  • Recolección de basura de recuento de referencias (recolección de basura directa), la JVM convencional no utiliza este método
  • Seguimiento de la recolección de basura (recolección de basura indirecta), la recolección de basura común pertenece a este método

2. Algoritmo de borrado de marcas

El algoritmo Mark-Sweep (Mark-Sweep) es el algoritmo de GC más antiguo y básico.

Se divide en dos etapas: marcar y borrar.

  • Marque todos los objetos vivos y luego recopile todos los objetos no marcados de manera uniforme

Defectos del algoritmo de marcar y barrer

  1. 执行效率不稳定Si el montón de Java contiene una gran cantidad de objetos que deben reciclarse, la eficiencia de las fases de marcado y limpieza será muy baja.
  2. Después de marcar y borrar, 产生大量不连续的内存碎片la siguiente recolección de basura se activará por adelantado si no hay suficientes objetos grandes para asignar.

La mayoría de los algoritmos de recolección de basura posteriores se basan en el barrido de marcas y se han mejorado en él.

3. Algoritmo de marca-copia

1. Algoritmo de replicación inicial

Para resolver el problema de la baja eficiencia del algoritmo de limpieza cuando se enfrenta a una gran cantidad de objetos, la idea del algoritmo de copia es:

  • “半区复制”,将可用内存按容量划分为大小相等的两块,每次只使用其中的一块
  • 当这块空间用完,就把这里面还存活的对象复制到另一块内存中,然后把已使用的内存空间全部清理掉

Ventajas y desventajas

ventaja:

  • Si es necesario reciclar la mayoría de los objetos en la memoria, solo es necesario copiar una pequeña cantidad de objetos, lo cual es muy eficiente.
  • Cada vez, se recicla toda la mitad del área.不会产生空间碎片

defecto:

  • Si la mayoría de los objetos en la memoria necesitan sobrevivir, se producirá una gran cantidad de copias de memoria, lo cual es ineficiente.
  • La memoria disponible se ha reducido a la mitad del tamaño original.空间浪费太大了

2. Edén y superviviente

La mayoría de las JVM utilizan un algoritmo de copia para reciclar la nueva generación.

IBM descubrió que el 98% de los objetos de la nueva generación no sobrevivirán a la primera recolección de basura, por lo que no es necesario "asignar memoria a la mitad".

El método específico es 把新生代分为一块较大的Eden空间,和两块较小的Survivor空间,.

Cada vez que se asigna memoria, solo se utilizan Eden y uno de los Supervivientes. Se produce la recolección de basura, se copian los objetos supervivientes en Eden y Survivor a otro Superviviente, y luego se limpia Eden y el último Superviviente directamente.

La relación de tamaño predeterminada de Eden y Survivor de HotSpot es 8:1, es decir, el espacio de memoria disponible de cada nueva generación representa el 90% de todo el espacio de nueva generación. Se utiliza un espacio Survivor redundante para copiar los objetos supervivientes. Este desperdicio de espacio es permitido.

Si hay muchos objetos supervivientes y el Superviviente no puede colocarlos, se necesitarán otras áreas de memoria (principalmente la generación anterior) para la "garantía de asignación".

3. Detalles de la recolección de basura de nueva generación.

  • La JVM activa un GC menor, y los objetos supervivientes en el área de Eden y Survivor from se copiarán al Survivor to área.
  • Luego intercambie los punteros desde y hacia para asegurarse de que el área de Superviviente señalada por todavía esté vacía durante el próximo GC menor.
  • La máquina virtual Java registra cuántas veces los objetos en el área Survivor se han copiado de un lado a otro.
    • Si el número de veces que se copia un objeto es 15, el objeto pasará a la generación anterior.
    • Además, si una sola área de Superviviente ya está ocupada al 50%, los objetos con una mayor cantidad de copias también se promocionarán a la generación anterior por adelantado.

4. Mecanismo de garantía de asignación

Dado que se utiliza un área de memoria más pequeña para copiar los objetos supervivientes, si esta área no puede contener todos los objetos supervivientes, se utilizará el espacio de la generación anterior para garantizar la asignación.

El método específico consiste en promover directamente algunos objetos supervivientes a la generación anterior.

  • Antes de JDK 1.6:
    • Antes de que ocurra el GC joven, primero verificará si el espacio continuo máximo disponible en la generación anterior es mayor que el espacio de todos los objetos en la nueva generación. Si es mayor que esto, entonces el joven GC definitivamente está a salvo esta vez.
    • De lo contrario, verifique si el parámetro "Permitir falla de garantía" de la máquina virtual está activado.
    • Si está habilitado, continuará verificando si el espacio continuo máximo disponible en la generación anterior es mayor que el tamaño promedio de los objetos promovidos a la generación anterior. Si es mayor, se intentará una GC menor.
    • Si no está habilitado, se realizará una GC completa.
  • Después de JDK 1.6:
    • Siempre que el espacio continuo de la generación anterior sea mayor que el tamaño total de los objetos de la nueva generación o el tamaño promedio de las promociones anteriores, se realizará una GC menor; de lo contrario, se realizará una GC completa.

4. Algoritmo de clasificación de marcado

Para la generación anterior, la tasa de supervivencia de los objetos cada vez que se reciclan es relativamente alta, por lo que no es adecuado utilizar el algoritmo de copia de objetos supervivientes. Además, no hay espacio de memoria "garantizado" para la generación anterior.

Para la vejez, se propuso un "algoritmo de intercalación". También lo marca primero, pero en lugar de eliminarlo directamente en su lugar, copia todos los objetos supervivientes cuidadosamente en un lado de la memoria para lograr el efecto de organizar el espacio de la memoria.

Mover objetos vivos tiene ventajas y desventajas:

defecto:

  • Después de la recolección de basura en la generación anterior, una gran cantidad de objetos sobrevivirán y mover estos datos lleva mucho tiempo.
  • Mover la dirección de memoria del objeto superviviente significa que es necesario volver a modificar la dirección de referencia, y esta operación de actualización también es muy costosa.
  • Este tipo de operación de movimiento de objetos debe suspender la aplicación del usuario durante todo el proceso (las barreras de lectura pueden resolver este problema)

ventaja:

  • Sin fragmentación de la memoria

La diferencia entre este método y "marcar-borrar" es sólo la forma de procesar los "objetos marcados": uno los elimina en su lugar y el otro los mueve.

Mudarse lleva más tiempo, pero el acceso es muy conveniente, pero 如果要针对碎片化的内存进行特殊设计,则比较麻烦reduce significativamente la eficiencia del acceso, lo que en realidad reduce la eficiencia general.

Un enfoque equilibrado consiste en utilizar habitualmente el algoritmo de limpieza para permitir la existencia de fragmentos pero no optimizar el acceso a ellos. Si la fragmentación de la memoria afecta la asignación de objetos, utilice un algoritmo de desfragmentación para obtener un espacio de memoria regular. El recopilador de CMS es esta estrategia.

5. Resumen

  • Marcar claro:
    • Marque objetos activos y luego elimine los objetos no marcados.
    • La desventaja es que se generará una gran cantidad de fragmentos de memoria. Cuando sea necesario asignar objetos más grandes en el futuro, la siguiente recolección de basura puede activarse con anticipación.
  • Marcar-copiar:
    • Divida todo el montón en dos partes y use solo una pieza a la vez.
    • Marque los objetos supervivientes y cópielos en la otra mitad de la memoria, borrando todo el bloque de memoria anterior.
    • La ventaja es que no es necesario considerar la fragmentación de la memoria y la memoria se puede asignar directamente en secuencia.
    • La desventaja es que la utilización de la memoria no es alta. La razón para dividir el montón por la mitad es para que no haya una situación en la que haya demasiados objetos supervivientes y no se pueda acomodar la otra mitad del espacio.
    • La idea del área de Enen y el área de supervivientes es que si el área de supervivientes no puede acomodar los objetos supervivientes, se utilizará la garantía de antigua generación. De esta manera, el área de supervivientes se puede reducir para mejorar la utilización de la memoria.
  • Organizar etiquetas:
    • Marque todos los objetos vivos, muévalos a un extremo de la memoria y limpie la memoria fuera del límite.
    • La ventaja es que puedes obtener un espacio de memoria normal.
    • La desventaja es que si hay muchos objetos supervivientes, el proceso de movimiento llevará mucho tiempo.

6. ¿Qué algoritmo se utiliza generalmente en la nueva generación y en la vieja generación?

La nueva generación generalmente usa el algoritmo de "marcar copia", y la generación anterior generalmente usa los algoritmos de "marcar borrar" y "marcar clasificar".

1. ¿Por qué la nueva generación no utiliza el algoritmo de compensación?

En la nueva generación, una gran cantidad de objetos morirán durante cada recolección de basura y solo unos pocos sobrevivirán.

Entonces si 删除大量的死亡对象,效率肯定不如复制少量的存活对象更高.

Y borre el algoritmo 会带来大量内存碎片. La memoria se asignará con frecuencia para nuevos objetos en la nueva generación 碎片过多肯定会导致垃圾回收多次触发.

Además, el algoritmo de copia se ha mejorado y puede comprimir el área de Survivor, y la tasa de utilización de la memoria no es baja. Si no hay suficiente espacio durante la copia, lo garantiza la generación anterior.

2. ¿Por qué la vieja generación no utiliza el algoritmo de limpieza?

La característica de la vieja generación es que hay más objetos supervivientes. Si marca objetos muertos y luego los limpia, la eficiencia es realmente buena.

Sin embargo, el algoritmo de limpieza traerá muchos fragmentos de memoria. Para este entorno, se debe diseñar una lista libre para asignar memoria, lo que reducirá la eficiencia de la asignación de memoria normal.

Por lo tanto, elegimos un algoritmo de clasificación que es menos eficiente durante la GC, que puede generar espacio de memoria regular y es más conveniente de usar en la vida diaria.

3. ¿Por qué la vieja generación no utiliza el algoritmo de replicación?

Por un lado, es absolutamente imposible dividir a toda la generación anterior en dos bloques del mismo tamaño, ya que ese desperdicio de espacio de memoria es demasiado grande. Además, la gran zona del Edén y la pequeña zona de supervivientes de la nueva generación no se pueden utilizar porque no hay espacio adicional para asignarla y garantizarla.

Por otro lado, la tasa de supervivencia de los objetos de la generación anterior es alta y la replicación no es eficiente.

Por lo tanto, opte por utilizar el algoritmo de "limpieza de marcas" o "clasificación de marcas" para el reciclaje.

5. Detalles de implementación del algoritmo HotSpot

1. Enumeración de nodos raíz y OOPMap

Antes de que la máquina virtual realice el GC, primero debe marcar los objetos supervivientes mediante un análisis de accesibilidad, y el análisis de accesibilidad se divide en dos etapas:

  • Enumeración de nodos raíz para determinar todos los GCRoots
  • Encuentre la cadena de referencia a lo largo del nodo raíz

La enumeración del nodo raíz requiere STW

  • Todos los recolectores de basura primero deben pausar el hilo del usuario al enumerar el nodo raíz. El propósito aquí es garantizar la coherencia y lograr una recolección de basura precisa.
  • Debido a que la cadena de referencia del nodo raíz cambia constantemente durante la ejecución del proceso del usuario, se debe extraer un estado en un momento determinado para garantizar la precisión del análisis.
  • Encontrarlos cada vez lleva mucho tiempo y se debe minimizar el tiempo dedicado a suspender los hilos de los usuarios.

Cómo acortar el tiempo para que el nodo raíz enumere STW

Hay dos categorías principales de nodos fijos que se pueden utilizar como GC Roots:

  • Referencias globales (constantes, propiedades estáticas de clases)
  • Contexto de ejecución (objeto de referencia en el marco de pila)

El método más estúpido requiere un escaneo completo del área del método y el espacio de la pila para encontrar estos objetos, lo que hace que STW demore mucho tiempo.

Una idea es intercambiar espacio por tiempo, que es la idea de OOPMap.

OOPMapa

Registre el tipo de referencia y su información de ubicación correspondiente en una tabla hash, de modo que la tabla hash se pueda leer directamente durante la GC en lugar de escanear área por área.

Lo que hace HotSpot es utilizar un conjunto de estructuras de datos llamado "OopMap".

Una vez que se completa la acción de carga de clases, HotSpot registra el desplazamiento en la memoria de cada tipo de datos en la clase.

De esta manera, OOPMap se puede utilizar para completar rápidamente la enumeración del nodo raíz durante la GC, reduciendo así el tiempo STW.

2. Punto seguro

Con la ayuda de OopMap, HotSpot puede completar rápidamente la enumeración del nodo raíz.

Sin embargo, durante la ejecución del programa, a menudo hay algunas operaciones que hacen que la referencia cambie, por lo que es necesario actualizar el contenido del OopMap; de lo contrario, se producirá un error al llevar este OOPMap al GC.

Por lo general, existen muchas operaciones de este tipo. Si se genera un OopMap correspondiente para cada momento cambiante, el costo de este espacio adicional será demasiado alto.

HotSpot no hace esto, solo registra la información relevante de la referencia del objeto en el "punto seguro".

Esto significa que solo cuando el programa alcanza un punto seguro el hilo del usuario puede pausarse. El OopMap correspondiente a este punto seguro se utiliza para enumerar el nodo raíz e iniciar la recolección de basura.

Cuando la máquina virtual Java recibe una solicitud de Stop-the-world, esperará a que todos los subprocesos alcancen el punto seguro antes de permitir que el subproceso que solicita Stop-the-world realice un trabajo exclusivo.

En un punto seguro, no se ejecuta ningún código de bytes.

¿Dónde aparece el lugar seguro?

  • Los puntos seguros no deben ser demasiado escasos, de lo contrario la recogida de basura no se realizará a tiempo.
  • No puede ser demasiado intensivo, de lo contrario, el costo de mantenimiento adicional de OOPMap será muy alto y no es necesario realizar GC con tanta frecuencia.

Los criterios para seleccionar puntos seguros son:

  • ¿Tiene características que permitan que el programa se ejecute durante mucho tiempo?
  • Por ejemplo, la reutilización de instrucciones incluye llamadas a métodos, bucles, manejo de excepciones, etc. Sólo las instrucciones con estas capacidades generarán puntos seguros.

Cómo funcionan los puntos seguros

¿Cómo hacer que todos los subprocesos del usuario se ejecuten hasta el punto seguro más cercano y luego se detengan cuando ocurre GC?

Hay dos formas de pausar el hilo del usuario:

  • interrupción preventiva
    • Cuando ocurre GC, el sistema primero interrumpe todos los subprocesos del usuario.
    • Si se descubre que la posición de interrupción de algunos subprocesos de usuario no está en un punto seguro, deje que continúe ejecutándose, ejecútelo hasta un punto seguro y luego interrumpa nuevamente.
  • interrupción proactiva
    • El hilo no se interrumpirá directamente, pero se establecerá una bandera globalmente.
    • El hilo del usuario sondeará continuamente este bit de bandera. Cuando se determine que el bit de bandera es verdadero, el hilo se interrumpirá y suspenderá activamente en el punto seguro más cercano.

Comparación de dos métodos:

  • La desventaja de las interrupciones preventivas es que el rendimiento es inestable, lo que resulta en un consumo de tiempo incontrolable para la acción de pausar el hilo del usuario, e implica interrupciones repetidas, lo que no es eficiente. Ninguna máquina virtual está diseñada así
  • La interrupción activa es más razonable, las máquinas virtuales están diseñadas de esta manera

3. Zona segura

1. ¿Por qué se necesita un área segura?

Los hilos de usuario generalmente se interrumpen automáticamente después de alcanzar un punto seguro mediante una interrupción activa.

Sin embargo, algunos subprocesos de usuarios inactivos se bloquean o suspenden durante la GC. Cuando están inactivos, no tienen forma de sondear el bit de bandera y no pueden interrumpirse en un punto seguro.

Esto genera un problema: es posible que después de que el GC inicie la enumeración del nodo raíz, estos subprocesos reanuden su ejecución y modifiquen las referencias, lo que provocará una recolección de basura inexacta.

Este problema debe resolverse mediante la introducción de una zona segura.

La idea de la zona segura es garantizar que la relación de referencia no cambie en un determinado fragmento de código, por lo que es seguro iniciar la recolección de basura desde cualquier lugar de esta zona.

Es decir, el área segura es similar a un punto seguro alargado.

2. Cómo funciona la zona segura

  • Cuando el hilo del usuario ejecuta el código en el área segura, primero identificará que ha ingresado al área segura.
  • Después de eso, la máquina virtual iniciará GC y establecerá el bit de indicador de interrupción global.
  • Si un hilo quiere salir del área segura, primero verificará el bit del indicador de interrupción activo.
  • Si un hilo está bloqueado o suspendido en un área segura, después de que se reanude su ejecución, primero verificará el bit del indicador de interrupción activo.
    • Si el bit de bandera es verdadero, significa que GC se iniciará o se está iniciando, entonces el hilo actual continuará bloqueándose.
    • Si la bandera es falsa, se reanuda la operación normal.

Supongo que te gusta

Origin blog.csdn.net/m0_62609939/article/details/130659317
Recomendado
Clasificación