Teoría del mecanismo de gestión automática de memoria de la máquina virtual Java

El Sr. Qian Zhongshu dijo: El mecanismo automático de administración de memoria de Java es una ciudad sitiada. La gente de afuera quiere entrar, pero la gente dentro del muro quiere salir ...
(De lo contrario, no aprenderé JVM aquí. Qwq)

1. Una exploración preliminar de la memoria Java

(1) División del área de memoria

Cuando JVM se ejecuta, divide el área de memoria que administra en diferentes áreas, estas áreas de memoria tienen sus propios propósitos y ciclos de vida.
Nota: El modelo de memoria que se proporciona a continuación solo lo proporciona la especificación de la máquina virtual Java, 概念模型y la implementación específica la completará la máquina virtual específica.
Inserte la descripción de la imagen aquí

1. Contador de programas

El contador de programa también se denomina registro de PC. Es la única área de memoria definida en la especificación de la máquina virtual Java que no arroja excepciones OOM . Esta área ocupa una pequeña cantidad de memoria. Cambie el valor del contador para obtener el siguiente byte para ser ejecutado Instrucciones de código .

2. Pila de máquinas virtuales Java

La pila de máquinas virtuales es un modelo de memoria de subprocesos que describe la ejecución de los métodos java. La ejecución de cada método Java corresponde al 栈帧proceso de creación, empuje y extracción de cada una de las pilas de máquinas virtuales . El marco de pila se utiliza para almacenar 局部变量表y 操作数栈otra información, la más importante de las cuales debería ser la información de la tabla de variables locales.
La tabla de variables locales almacena varios tipos de datos básicos y referencias de objetos que se pueden conocer en el momento de la compilación . La unidad de almacenamiento básica es 局部变量槽.

3. Pila de métodos locales

La función de la pila de métodos local es similar a la de la pila de máquinas virtuales, excepto que la pila de métodos locales sirve a la máquina virtual para ejecutar métodos que no son de Java . En la especificación de la máquina virtual Java, no hay disposiciones obligatorias sobre los detalles específicos de la pila de métodos locales, e incluso la pila de métodos locales y la pila de máquinas virtuales se combinan en el HotSpot de la máquina virtual principal.

4 montón de Java

Como la mayor parte de la memoria de Java, el montón de Java existe con el único propósito de almacenar instancias de objetos . Al mismo tiempo, el montón de Java es también un área de memoria administrada por el recolector de basura GC.
Para facilitar la asignación y recuperación de la memoria del montón, el espacio del montón compartido también se puede dividir en búferes de asignación privados para varios subprocesos.
De acuerdo con la "Especificación de la máquina virtual de Java", el montón de Java permite la discontinuidad en la memoria, pero debe ser lógicamente continuo . En diferentes implementaciones de máquinas virtuales, el montón de Java puede ser de un tamaño fijo o expandible, que está determinado por la propia máquina virtual.

5. Área de métodos

5.1

El área de métodos se utiliza principalmente para almacenar información relacionada con el tipo, variables estáticas, constantes, cachés de código, etc. que han sido cargados por la máquina virtual .

Cabe mencionar que en versiones anteriores de jdk8, el área de métodos de la máquina virtual convencional HotSpot se diseñó con generación permanente, comenzando con jdk8 se adoptó el diseño de metaespacio.

注意:在JDK8之后静态变量存放在堆当中,此时的静态变量在方法区就只是存在于逻辑上的概念了

Metaspacio y generación permanente .

5.2 Pool de constantes en tiempo de ejecución

Un fragmento de código, varios literales y referencias de símbolos generados después del período de compilación se almacenan en la tabla de grupo constante en el archivo de clase, y después del proceso de carga de la clase, esta parte de los datos se almacenará en el grupo de constante de tiempo de ejecución. La mayor diferencia entre el grupo de constantes en tiempo de ejecución y el grupo de constantes de archivos de clase es su naturaleza dinámica, que permite que los datos se coloquen dinámicamente en el grupo durante la ejecución.

Suplemento: memoria directa

La memoria directa no forma parte del área de memoria de la JVM. Su tamaño está limitado por la memoria total de la máquina y la capacidad de direccionamiento de la CPU. Es adecuada para ocasiones que requieren mucha memoria y acceso frecuente.
La JVM almacena el objeto de referencia de la dirección de memoria directa en el montón de Java: DirectByteBuffer, que se utiliza para completar el acceso a la memoria fuera del montón.

(2) El procesamiento del objeto en la memoria.

Tomemos las máquinas virtuales convencionales HotSpoty las áreas de uso más común Java堆como ejemplos para comprender el proceso de creación, diseño y acceso a objetos Java en el montón de Java .

1. Creación de objetos

Cuando la máquina virtual encuentra la nueva instrucción, primero procederá 类加载检查(para asegurarse de que el proceso de carga de la clase se debe ejecutar primero), luego el objeto Java 分配内存, al mismo tiempo 将字段信息初始化为零值, luego procederá 对象头信息的初始化(HashCode, información de metadatos, etc.), y finalmente 执行<init>方法, complete la creación del objeto.

Hay dos estrategias principales de asignación de memoria para objetos: 指针碰撞y 空闲列表, que corresponden a la distribución centralizada y la distribución discreta de la memoria, respectivamente.

Dado que la creación de objetos es muy frecuente, debemos garantizar la seguridad absoluta de los hilos. Proporcione principalmente las siguientes dos soluciones:

a. Sincronice la acción de asignar espacio de memoria. La máquina virtual HotSpot utiliza bloqueo CAS + reintento de falla para garantizar la atomicidad de la operación de actualización.
b) Divida la acción de asignación de memoria en diferentes espacios, preasigne un búfer de asignación de subprocesos local (TLAB) para cada subproceso y realice el bloqueo de sincronización después de que se agote la TLAB. El uso de la estrategia se puede -XX:+/-UseTLABdeterminar mediante parámetros

2. Disposición de los datos del objeto (composición)

Además de la 实例数据estructura de los datos del objeto Java en la memoria , también incluye 对齐填充(actuando como un marcador de posición: en la máquina virtual HotSpot, la dirección inicial del objeto debe ser un múltiplo entero de 8) y 对象头数据.

Los datos del encabezado del objeto se almacenan 运行时数据(Mark Word)y 类型指针:

Los datos en tiempo de ejecución almacenan una gran cantidad de información que no tiene nada que ver con los datos definidos por el objeto en sí. Para ahorrar espacio, se adopta una estructura de datos definida dinámicamente (es decir, se representan diferentes estados mediante el establecimiento de bits de bandera y diferentes contenidos se almacenan en diferentes estados).

La máquina virtual determina la clase correspondiente del objeto a través del puntero de tipo, pero no todos los objetos necesitan retener el puntero de tipo (la razón es: cuando se accede al objeto a través del identificador, almacena tanto la dirección de la instancia del objeto como la dirección del tipo de datos). Además, si el objeto es una matriz, la información de longitud de la matriz debe conservarse.

3. Acceso a objetos

Los programas Java reference引用ubican los objetos Java en la pila. Hay dos métodos de acceso principales:
Inserte la descripción de la imagen aquí
Inserte la descripción de la imagen aquí
cada uno tiene sus propias ventajas:

Cuando se utiliza el acceso de identificador, cuando cambia la dirección del objeto, solo se debe cambiar el valor del identificador, no el valor de la referencia.
Cuando se utiliza el acceso directo al puntero, la mayor ventaja es el aumento de velocidad.

Dos. Recolector de basura

Una de las mayores diferencias entre los lenguajes Java, C y C ++ es su mecanismo automático de recolección de basura. Pero cuando nos enfrentamos a un desbordamiento de memoria y pérdidas de memoria en algunos entornos específicos, debemos solucionar los problemas y optimizar el rendimiento nosotros mismos. Ya conocemos la división del área de memoria de Java. La pila de métodos locales, la pila de máquinas virtuales y el contador de programa son partes privadas del hilo. Viven y mueren junto con el hilo. Esta parte del área no necesita GC, el lugar donde se necesita el mecanismo GC es el área de métodos y montón de Java.

Lo primero que hay que confirmar en el pensamiento es que, como las personas, nunca ha habido un recolector de basura que sea perfecto en todas partes . Solo al considerar escenarios de aplicación específicos y elegir el mejor recolector de basura y la mejor combinación de parámetros se puede obtener el mejor rendimiento. (El propósito de aprender la máquina virtual Java también está aquí)

(1) Algoritmo de juicio de supervivencia de objetos (colección de montón)

El algoritmo de decisión más sencillo es el algoritmo de recuento de referencias : el contador es +1 cuando hay una referencia, -1 cuando la referencia no es válida y se determina que el objeto está muerto hasta que el contador es 0. El algoritmo es demasiado simple y no considera algunos casos especiales. Por ejemplo, cuando se hace referencia cíclica a un objeto, incluso si no se puede volver a acceder al objeto, el contador sigue sin ser 0 y el recolector de basura está allí mirándolo y no lo recolecta.
Inserte la descripción de la imagen aquí
El algoritmo de juicio principal actual es 可达性分析算法: tome algunos GC Rootsobjetos raíz llamados " " como el conjunto de nodos inicial, busque hacia abajo de acuerdo con la relación de referencia, y la ruta recorrida se llamará " 引用链". Cuando un objeto ya no tiene una cadena de referencia a GC Roots, indica que el objeto está muerto . (Sin embargo, esto está en período de prueba, y el método finalize () del objeto será juzgado antes de que el objeto sea realmente recuperado. Esta es la última oportunidad del objeto de redimirse! (No se recomienda este método, por diversas razones))
Can ser utilizado como Roots GC son: dentro de las referencias de la máquina virtual, constantes referencias, referencias estáticas, objeto de excepción permanente, referencias a objetos y así sucesivamente .

Suplemento: recolección de basura en el área de método

No se requiere que el área de método implemente la recolección de basura en la "Especificación de la máquina virtual Java", porque en comparación con la recolección de montón, la rentabilidad de la recuperación del área de método es demasiado baja (limitada por duras condiciones de juicio). Sin embargo, en algunas situaciones específicas, es necesario implementar la recolección de basura en el área de métodos: reflexión, proxy dinámico y otros lugares donde se generan frecuentemente cargadores de clases personalizados.
Luego, el área de método almacena constantes e información relacionada con la clase, y los objetos reciclados naturalmente también son constantes e información relacionada con la clase.

(2) Algoritmo de recolección de basura (idea)

1. Prefacio: Teoría de la colección generacional

Tres hipótesis básicas:
1. La mayoría de los objetos mueren día y noche (siempre mueren en la oscuridad antes del amanecer)
2. Los objetos que se han recolectado basura muchas veces pero que no se han recolectado se volverán más difíciles de reciclar (dimensional Jing Yongzhe Beifeng hecho)
3. Las referencias de generaciones cruzadas son solo un pequeño número (dos objetos con la misma frecuencia y el mismo ritmo generalmente existen en el mismo lugar)

Obviamente, si los objetos se almacenan en particiones según la antigüedad , cada partición adopta diferentes mecanismos de recolección de basura según sus diferentes características, lo que mejorará enormemente la eficiencia.
Además, según la tercera hipótesis, para el problema de las referencias de generación cruzada, no necesitamos escanear toda la memoria durante el proceso de recolección de basura, solo necesitamos crear una 记忆集, registrar la unidad de memoria que tiene referencias de generación cruzada , y escanee esta pequeña parte durante la GC. La unidad de registro es suficiente.

2. Ideas comunes de algoritmos de recopilación

2.1 Algoritmo de barrido de marcas

Marque los objetos supervivientes o muertos y luego elimine los objetos marcados o sin marcar.

El principal problema es que lo hará 产生大量的内存碎片.

"Comprensión en profundidad de la máquina virtual Java" dijo que hay otro problema: cuando la mayoría de los objetos en el montón necesitan ser reciclados, se debe hacer mucho marcado y limpieza, y la eficiencia se reducirá en este momento. .
Personalmente, no entiendo muy bien lo que quiere decir el autor. Dado que los objetos recuperados en el montón representan una cantidad relativamente grande, a su vez, solo podemos marcar los objetos que no se recuperan. ¿Por qué se debe hacer mucho trabajo de marcado? Creo que lo que el autor quiere expresar es solo el problema de la baja eficiencia cuando aumenta el número de objetos a eliminar. (Porque el trabajo de retirar una gran cantidad de objetos es inevitable)

2.2 Algoritmo de marca-copia

La memoria se divide en regiones y solo se usa una parte del espacio de la memoria cada vez. Cuando la región está llena, los objetos supervivientes se copian en otra región y la región de memoria original se libera en lotes al final. Adecuado para 新生代este tipo de área de memoria con pocos objetos supervivientes.

El problema de la fragmentación de la memoria está resuelto, pero ha ocurrido 内存浪费. Al mismo tiempo 需要有其他区域提供内存担保(porque en los escenarios de aplicación reales, la relación de división de área es mucho mayor que 1: 1, pero nadie puede garantizar que el área de memoria restante pueda acomodar la actual Todos los objetos supervivientes)

2.3 Algoritmo de clasificación y marcado

Mueva todos los objetos supervivientes a un límite y luego elimine los objetos fuera del límite.

Aunque este algoritmo también es una operación extremadamente onerosa cuando se enfrenta a áreas con altas tasas de supervivencia como la vejez, en comparación con mark-copy, es 不再需要额外的内存担保más adecuado para la basura en áreas con altas tasas de supervivencia como los objetos de la vejez. Una solución concebible es utilizar el algoritmo de marcado y barrido la mayor parte del tiempo y ejecutar el algoritmo de marcado y barrido solo cuando los fragmentos de memoria son demasiado para soportar.

(3) Primer recolector de basura

1. Ideas de resolución de problemas de época

La mayor diferencia entre el colector G1 y los colectores anteriores es que usa 化整为零la idea del sí . El colector G1 conserva el concepto de generación, pero el área de generación no es fija. G1 determina la unidad más pequeña de recuperación de memoria, ya que Regioncada región puede desempeñar el papel de Eden, Survivor y el espacio antiguo, y utiliza diferentes estrategias de recuperación para lidiar con la basura al mismo tiempo.

El colector G1 está bien implementado 停顿时间模型: el mejor efecto de recuperación se logra dentro del tiempo de pausa dado por el usuario. El usuario especifica el tiempo de pausa a través del parámetro -XX: MaxGCPauseMills, y el recolector G1 realiza la recolección de basura de acuerdo con la lista de prioridades (ordenando las regiones según los ingresos máximos de la recolección).

Vale la pena mencionar que el recolector G1 no busca una limpieza absoluta después de cada recolección de basura, siempre que la memoria actual sea suficiente para que la usen los usuarios, esto es suficiente. (Muy en línea con el pensamiento budista de la gente jaja)

Pero debe tenerse en cuenta que el tiempo de pausa no puede configurarse demasiado pequeño, porque al final puede ser que la velocidad de limpieza de la memoria no pueda mantenerse al día con la velocidad de asignación de memoria.

2. Proceso operativo y realización detallada

2.1 Soluciones al problema de la referencia de generación cruzada de objetos

Como se mencionó anteriormente, no necesitamos escanear toda el área del montón en busca del problema de generación cruzada de referencias de objetos. Solo necesitamos registrar las unidades de memoria que tienen objetos de referencia de generación cruzada a través del conjunto de memoria y escanear las unidades de memoria registradas en esta parte.

El conjunto de memoria aquí es una estructura de datos abstracta, que se implementa en la máquina virtual HotSpot en 卡表forma de un conjunto de memoria, manteniendo un cierto tamaño de matriz de bytes como valor de valor (el tamaño predeterminado en HotSpot es 512 bytes). La memoria se considera un área más pequeña. Si hay referencias de generación cruzada de objetos en esta área, se identifican, se dice que el valor clave correspondiente está sucio y, finalmente, el área de valor correspondiente al valor sucio se agrega a GC Raíces para escanear . Sobre esta base, el colector G1 adopta la forma de un medidor de tarjeta bidireccional, no solo registrando "a quién señalo", sino también "quién apunta a mí".

En HotSpot en uso 写屏障para lograr el mantenimiento de los elementos de la mesa de cartas. La simple comprensión de la barrera de escritura es el código lógico adicional para operaciones como marcar antes y después de la declaración de asignación de referencia del objeto . Además, para evitar 伪共享el problema (el procesamiento de datos en la misma línea de caché afectará la eficiencia), puede agregar una declaración de juicio antes de ejecutar la marca sucia: si no hay marca, marque (detalles, detalles verdaderos, cada línea de código Todos están bien pensados). Si la evaluación está habilitada se puede determinar mediante el parámetro -XX: + UseCondCardMark .

2.2 Separación del hilo del usuario y del hilo de la colección en un proceso concurrente

El recolector G1 se realiza simultáneamente en la fase de escaneo y marcado, es decir, no "Parará el mundo". La realización de este efecto necesita resolver dos problemas:

1. Asegúrese de que la estructura del gráfico de objetos original no se cambie durante la ejecución del hilo del usuario.
2. En el proceso de recolección concurrente, se crean constantemente nuevos objetos mientras el subproceso de recolección realiza la recolección de basura, por lo que la asignación de memoria para nuevos objetos también es un problema a considerar.

Para la primera pregunta, primero comprenda la marca de tres colores: el
Inserte la descripción de la imagen aquí
简单理解为 blanco representa el objeto muerto; el negro representa el objeto sobreviviente determinado, el objeto solo puede sobrevivir cuando el objeto negro hace referencia a él, ¡y el objeto negro no volverá a escanearse ! ! ! El gris está entre blanco y negro (equivalente al búfer en el proceso de escaneo).

Entonces viene el problema.Cuando el hilo del usuario y el hilo de la colección se ejecutan al mismo tiempo, y se cumplen las siguientes condiciones al mismo tiempo, 对象消失el problema (¡el objeto que debería haber sobrevivido la hizo morir!) Es el problema:
Inserte la descripción de la imagen aquí
Dicho sin rodeos, la causa principal es que el objeto negro no es. Volverá y escaneará de nuevo. Por estas dos condiciones necesarias se proporcionan dos soluciones: 增量更新和原始快照(SATB).

增量更新: Sirve para registrar cuándo se inserta la referencia y volver a escanear con estos objetos negros como raíz una vez finalizado el escaneo. El resumen incisivo en "Comprensión profunda de la máquina virtual Java": Simplemente comprenda que una vez que un objeto negro se inserta nuevamente una referencia a un objeto blanco, vuelve a ser un objeto gris .

原始快照: Registre también los objetos de referencia eliminados y vuelva a escanear los objetos grises como raíz después de escanear. El mismo resumen incisivo: se elimine o no la relación de referencia, se registrará de acuerdo con la instantánea del gráfico de objeto original .

Para la segunda pregunta:
la máquina virtual sin duda tendrá que crear primero un nuevo proceso en el GC objetos tan vivo, HotSpot diseñado dos para cada región TAMS指针(Top Marcar inicio AT): prevTAMS和nextTAMS指针.

Esta parte de "Comprensión en profundidad de la máquina virtual Java" no es clara, la siguiente imagen es más clara:
Inserte la descripción de la imagen aquí
Nota: El enlace de la imagen original .

2.3 Proceso de operación

Se divide aproximadamente en cuatro pasos:

1. Marca inicial. Esta etapa simplemente se marca en la parte que se puede asociar directamente con GC Roots. Esta etapa necesita detener el hilo, pero lleva poco tiempo.
2. Marcado concurrente. Escanee de forma recursiva todo el gráfico de objetos para analizar la accesibilidad, lo que lleva mucho tiempo pero se puede ejecutar al mismo tiempo que los hilos del usuario.
3. Nota final. Regrese y vuelva a escanear las raíces GC heredadas en el SATB (instantánea original). El proceso también necesita pausar el hilo.
4. Cribado y reciclaje. Ordene la recolección de acuerdo con los ingresos máximos del reciclaje de la región y use el algoritmo de marca-copia para la recolección de basura entre las dos regiones.
Inserte la descripción de la imagen aquí

Suplemento: enumeración de nodos raíz, punto de seguridad y zona de seguridad

Ya sabemos que la concurrencia del proceso de búsqueda de la cadena de referencia se puede lograr mediante el escaneo incremental y la instantánea original (SATB), pero para el escaneo del nodo raíz, es necesario suspender el hilo del usuario . (Si la referencia del objeto del nodo raíz también está en constante modificación, no se garantizará la confiabilidad del análisis de todos los resultados posteriores)

Para mejorar la eficiencia del proceso de enumeración del nodo raíz, HotSpot calculará y retendrá la información de referencia del objeto después del proceso de carga de la clase (la relación de referencia también se registrará durante el proceso de compilación justo a tiempo).

安全点En términos sencillos, es donde el hilo del usuario se detiene para la recolección de basura . En consecuencia 安全区域, se puede considerar como un punto de seguridad alargado. La relación de referencia del objeto en esta área no cambiará, por lo que la recolección de basura se puede realizar en cualquier momento en esta área . La existencia de la zona segura se puede utilizar para resolver la situación en la que el hilo del usuario se ve obligado a detenerse debido a varias razones (p. Ej., Estado de suspensión o estado bloqueado). Si la máquina virtual inicia la recolección de basura en este momento, no tardará cuidado de los usuarios que declaran estar en zona segura.

3. Comparación del colector G1 y el colector CMS

En primer lugar, la realización del colector G1 fue originalmente un hito histórico. G1 realiza el diseño de la memoria de acuerdo con las regiones e implementa el modelo de pausa. Los usuarios pueden especificar el tiempo de pausa por sí mismos y clasificar las regiones de acuerdo con los ingresos de recuperación para formar una colección de devolución . En segundo lugar, G1 adopta un algoritmo de organización de marcas como un todo y un algoritmo de copia de marcas en parte, pero no importa cuál sea, la continuidad del espacio de memoria está garantizada .
Sin embargo, la cantidad de G1 generada para la recolección de basura 内存占用y el tiempo de ejecución del programa es mayor 执行负载que la de CMS.

La desgracia viene a ser confiable, la bendición viene a la desgracia

El colector G1 ya no distingue estrictamente entre la nueva generación y la generación anterior, y adopta uniformemente la estructura de memoria de la Región. En lo que respecta al conjunto de memoria, cada Región corresponde a una tabla de tarjetas (en cambio, CMS solo registra la objetos de la generación anterior a la nueva generación. Referencia. Debido a que la nueva generación de referencias de objetos cambia con demasiada frecuencia, este enfoque es más rentable), y es más complicado que la tabla de cartas de CMS, lo que en última instancia da como resultado la El uso de memoria de G1 es mucho mayor que el de CMS.

Supongo que te gusta

Origin blog.csdn.net/m0_46550452/article/details/110386219
Recomendado
Clasificación