Java Face Classic 03-Virtual Machines-jvm Estructura de memoria y recolección de basura, desbordamiento de memoria y carga de clase, referencia y bloqueo pesimista y tabla hash, referencia y finalización

Máquina virtual

1. Estructura de memoria JVM

Requerir

  • División de estructura de memoria Master JVM
  • En particular, debe conocer la relación entre el área de métodos, la generación permanente y el metaespacio.

Combinado con la ejecución de una pieza de código Java para comprender la división de memoria

inserte la descripción de la imagen aquí


inserte la descripción de la imagen aquí

  • Ejecute el comando javac para compilar el código fuente en bytecode
  • Ejecute el comando Java
    1. Cree una JVM, llame al subsistema de carga de clases para cargar la clase y almacene la información de la clase en el área de métodos
    2. Cree el subproceso principal, el área de memoria utilizada es la pila de la máquina virtual JVM y comience a ejecutar el código del método principal
    3. Si encuentra una clase que no se ve, continuará activando el proceso de carga de clases y también se almacenará en el área de métodos.
    4. Los objetos deben crearse y la memoria del montón se usará para almacenar objetos.
    5. El recolector de elementos no utilizados recuperará los objetos que ya no se utilicen cuando no haya suficiente memoria.
    6. Al llamar a un método, las variables locales y los parámetros del método en el método usan la memoria del marco de pila en la pila de la máquina virtual JVM
    7. Al llamar a un método, primero debe ir al área del método para obtener las instrucciones del código de bytes del método, y el intérprete interpreta las instrucciones del código de bytes en códigos de máquina para su ejecución.
    8. Cuando se llama al método, el número de línea de la instrucción que se ejecutará se leerá en el contador del programa , de modo que cuando se produzca un cambio de hilo, puede continuar desde la posición interrumpida al reanudar
    9. Para las llamadas a métodos no implementados en Java, la memoria utilizada se denomina pila de métodos locales (consulte la descripción)
    10. Para llamadas a métodos calientes o códigos de bucle frecuentes, el compilador JIT compila estos códigos en cachés de código de máquina para mejorar el rendimiento de la ejecución (de lo contrario, cada vez que se ejecuta el mismo código, el intérprete debe interpretar repetidamente las instrucciones de bytecode ya que la ejecución de código de máquina es equivalente al almacenamiento en caché instrucciones de código de bytes)

Área de método: almacene información relevante de la clase (nombre de clase, relación de herencia, símbolos de otras clases a las que se hace referencia, variables de miembro, códigos de byte de método, anotaciones agregadas a clases, métodos y variables de miembro, etc.) montón: almacene nueva salida El objeto
JVM
virtual pila de máquina: almacena las variables locales en el método y los parámetros de método de las variables de método ordinarias implementadas por Java. En el pasado, los métodos especiales que necesitan interactuar con el sistema operativo deben ejecutarse en la pila de método local, pero ahora Oracle's Hotspot máquina virtual La implementación ya no usa la pila de métodos locales, o las dos pilas se combinan en una, y la memoria variable requerida por todos los métodos está en la pila de la máquina virtual JVM

ilustrar

  • Las fuentes en negrita representan los componentes de la máquina virtual JVM
  • Para la implementación de la máquina virtual Hotspot de Oracle, no se hace distinción entre la pila de la máquina virtual y la pila de métodos nativos.

Áreas donde pueden ocurrir pérdidas de memoria

Sin memoria: la memoria en esta área está agotada y se informa un error

Fuga de memoria: el recolector de basura no puede recuperar una determinada parte de la memoria.Este fenómeno se denomina fuga de memoria;

Las cinco áreas de memoria en la figura anterior, excepto el contador de programa, provocarán un desbordamiento de memoria.

  • El área donde no se producirá un desbordamiento de la memoria: el contador del programa
  • En caso de OutOfMemoryError
    • Agotamiento de la memoria del montón: cada vez se utilizan más objetos todo el tiempo y no se pueden recolectar basura
    • La memoria en el área de métodos está agotada: se cargan más y más clases y muchos marcos generarán dinámicamente nuevas clases durante el tiempo de ejecución.
    • Acumulación de pila de máquina virtual: cada subproceso ocupará hasta 1 M de memoria, la cantidad de subprocesos aumenta y no se destruirá cuando se ejecute durante mucho tiempo
  • Áreas donde se produce StackOverflowError
    • pila de máquina virtual JVM, el motivo es que la llamada recursiva del método no finaliza correctamente, y la referencia circular al deserializar json (el método en el subproceso se llama continuamente y se consume 1M de memoria en cada subproceso, un StackOverflowError ser informado)

Área de método, generación permanente, metaespacio

  • El área de método es un área de memoria definida en la especificación JVM , que se utiliza para almacenar metadatos de clase , códigos de byte de método , información requerida por el compilador justo a tiempo, etc.
  • La generación permanente es la implementación de la especificación JVM por parte de la máquina virtual Hotspot (antes de 1.8)
  • Metaspace es otra implementación de la especificación JVM por parte de la máquina virtual Hotspot (después de 1.8), que utiliza la memoria local como espacio de almacenamiento para esta información.

El área de método es solo una definición en la especificación JVM (tienes que tenerla, no me importa cómo implementarla) La
generación permanente y el metaespacio son la implementación física de la especificación.

inserte la descripción de la imagen aquí

Aprende tres cosas de esta imagen.

Metadatos de clase: los datos que describen la clase (qué miembros, qué tipo, cuánto tiempo...) se almacenan en el metaespacio (la implementación física del área del método)
nombre de clase.objeto de código de byte de clase, ya que es un objeto, es almacenada naturalmente en el montón
La información original de la clase (metadatos de la clase) se almacena en el metaespacio y no se puede acceder a ella directamente. Se debe acceder a ella a través del objeto java. Este objeto es el objeto bytecode.

  • Cuando una clase se usa por primera vez, el cargador de clases lee la metainformación de la clase del archivo de clase y la almacena en el metaespacio.
  • La metainformación de clase de X e Y se almacena en el metaespacio y no se puede acceder directamente
  • X.class e Y.class se pueden usar para acceder indirectamente a la metainformación de la clase, los cuales pertenecen a objetos java (objetos bytecode), que se pueden usar en nuestro código

inserte la descripción de la imagen aquí

De esta imagen podemos aprender

  • En la memoria del montón: cuando no se hace referencia a un objeto cargador de clases , todos los objetos de clase cargados por este objeto cargador de clases y todos los objetos de instancia correspondientes a estos objetos de clase , la memoria del montón ocupada por ellos se liberará durante la GC
  • En el metaespacio: la memoria se libera en unidades de cargadores de clases Cuando se libera la memoria del cargador de clases en el montón, también se libera la metainformación de la clase correspondiente en el metaespacio

Por lo general, el cargador de clases del sistema no se lanzará, y nuestro cargador de clases personalizado se lanzará cuando ya no se use (¿qué liberar? Memoria Metaspace)

2. Parámetros de memoria JVM

Requerir

  • Familiaridad con los parámetros comunes de JVM, especialmente aquellos relacionados con el tamaño

Pregunta:inserte la descripción de la imagen aquí

Memoria de pila, establecida por tamaño

inserte la descripción de la imagen aquí

explicar:

  • -Memoria mínima Xms JVM (incluyendo nueva generación y generación anterior)
  • -Memoria máxima Xmx JVM (incluyendo nueva generación y generación anterior)
  • Por lo general, se recomienda configurar -Xms y -Xmx para que tengan el mismo tamaño, es decir, no hay necesidad de reservar memoria y no hay necesidad de crecer de pequeño a grande, por lo que el rendimiento es mejor.
  • -XX:NewSize y -XX:MaxNewSize establecen los valores mínimos y máximos de la nueva generación, pero generalmente no se recomienda establecerlo, y está controlado por la propia JVM
  • -Xmn establece el tamaño de la nueva generación, lo que equivale a configurar -XX:NewSize y -XX:MaxNewSize al mismo tiempo y los valores son iguales
  • La reserva significa que no ocupará tanta memoria al principio, y a medida que se usa más y más memoria, esta parte de la memoria reservada se usará gradualmente. Lo mismo a continuación

Desde la perspectiva de la edad, JVM divide la memoria en nueva generación y generación anterior
: la n de Xmn es nueva nueva generación

Memoria de pila, configurada proporcionalmente

Nuevo en la figura a continuación es la nueva generación. La memoria de la nueva generación se puede dividir en eden y Survivor. Survivor se puede subdividir en: from, to
old, que es naturalmente la memoria de la generación anterior.

inserte la descripción de la imagen aquí

explicar:

  • -XX:NewRatio=2:1 significa que la generación anterior ocupa dos y la nueva generación ocupa uno
  • -XX:SurvivorRatio=4:1 significa que la nueva generación se divide en seis partes, Eden ocupa cuatro partes, y desde y hacia cada una ocupa una parte
  • (Nota 1: el 8:1 predeterminado es 8:1:1) (Nota 2: el 4:1 anterior se refiere a eden:from=eden:to=4:1 porque from y to son siempre iguales)

Configuración de la memoria del metaespacio

inserte la descripción de la imagen aquí

explicar:

  • El espacio de clase almacena la información básica de la clase, el valor máximo está controlado por -XX:CompressedClassSpaceSize
  • el espacio que no es de clase almacena información distinta de la información básica de la clase (como el código de bytes del método, anotaciones, etc.)
  • El tamaño total del espacio de clase y el espacio que no es de clase está controlado por -XX:MaxMetaspaceSize

Aviso:

  • Aquí -XX:CompressedClassSpaceSize Esta sección de espacio también está relacionada con si la compresión de puntero está habilitada. No entraré en detalles aquí. Simplemente puede pensar que la compresión de puntero está habilitada de manera predeterminada.

Configuración de memoria caché de código

Compilador instantáneo JIT, que compila código caliente en código de máquina y lo almacena en caché, luego lo almacena en el área de búfer de código CodeCache

inserte la descripción de la imagen aquí

explicar:

  • Si -XX:ReservedCodeCacheSize < 240m, todo el código de máquina optimizado existe junto indiscriminadamente
  • De lo contrario, se divide en tres áreas (el código optimizado se subdivide en 3 partes) (el método de error tipográfico en la figura está mal escrito, falta una e)
    • non-nmethods: código utilizado por la propia JVM (código propio del compilador JIT)
    • nmethods perfilados - código de máquina parcialmente optimizado
    • nmethods no perfilados - código de máquina completamente optimizado

Ajustes de memoria de hilo

Es decir, la memoria de la pila de la máquina virtual JVM
: Xss establece la memoria ocupada por cada subproceso.
Si no se establece, el sistema Linux tiene como valor predeterminado 1 MB, es decir, cada subproceso ocupa 1 MB de memoria de manera predeterminada.

inserte la descripción de la imagen aquí

Documentación Oficial de Referencia

3. Recolección de basura JVM

Requerir

  • Domina el algoritmo de recolección de basura
  • Domina la idea del reciclaje generacional
  • Comprender el marcado de tres colores y el procesamiento de etiquetas faltantes
  • Comprensión de los recolectores de basura comunes

por ejemplo: algunos objetos en la memoria del montón ya no tienen ninguna referencia en la memoria de la pila que los apunte, y el GC puede reciclarlos

Tres algoritmos de recolección de basura

marcar y barrer

inserte la descripción de la imagen aquí

explicar:

  1. Encuentre objetos raíz de GC, es decir, objetos que no se deben reciclar, como objetos a los que hacen referencia las variables locales en el método de ejecución, objetos a los que hacen referencia las variables estáticas
  2. Etapa de marcado: siga la cadena de referencia del objeto GC Root y marque los objetos referenciados directa o indirectamente
  3. Fase de limpieza: libera memoria ocupada por objetos no marcados

El objeto al que la variable local hace referencia o utiliza no debe reciclarse y puede utilizarse como objeto raíz. La
variable estática siempre debe existir y no puede reciclarse y puede utilizarse como objeto raíz.

Puntos principales:

  • Relación lineal entre la velocidad de marcado y los objetos supervivientes
  • La velocidad de limpieza está relacionada linealmente con el tamaño de la memoria
  • La desventaja es que generará fragmentación de memoria (la memoria no marcada tiene una alta probabilidad de ser discontinua y generará una gran cantidad de fragmentación de memoria, por lo que básicamente ha sido abandonada)

método de marcado

inserte la descripción de la imagen aquí

explicar:

  1. La fase de marcado y la fase de limpieza anteriores son similares al método de marcado y eliminación.
  2. Un paso más para terminar la acción, moviendo los objetos sobrevivientes a un extremo, puede evitar la fragmentación de la memoria.

Características:

  • Relación lineal entre la velocidad de marcado y los objetos supervivientes

  • La velocidad de limpieza y limpieza es lineal con el tamaño de la memoria

  • La desventaja es un rendimiento más lento.

replicación etiquetada

inserte la descripción de la imagen aquí

explicar:

  1. Divide toda la memoria en dos áreas de igual tamaño, desde y hacia, donde hasta siempre está libre y desde almacena objetos recién creados
  2. La fase de marcado es similar al algoritmo anterior
  3. Una vez que se encuentran los objetos sobrevivientes, se copiarán de un área a otra, y la desfragmentación se completará naturalmente durante el proceso de copia (después de copiar, todas las áreas se pueden borrar)
  4. Una vez completada la copia, simplemente intercambie las posiciones de from y to (las dos áreas se usan alternativamente, y nunca habrá problemas de fragmentación de memoria, qué bueno es)

Características:

  • Las velocidades de marcado y replicación son lineales con los objetos supervivientes
  • La desventaja es que ocupará el doble de espacio.

GC y algoritmo de recolección generacional

El propósito de GC es realizar la liberación automática de la memoria de objetos inútiles, reducir la fragmentación de la memoria y acelerar la asignación.

puntos GC:

  • El área de recuperación es la memoria del montón , excluyendo la pila de la máquina virtual (la memoria en la pila de métodos, que liberará automáticamente la memoria ocupada por el método al final de la llamada al método)
  • Determine los objetos inútiles, utilice el algoritmo de análisis de accesibilidad , el método de marcado de tres colores para marcar los objetos supervivientes y recicle los objetos no marcados.
  • La implementación específica de GC se llama recolector de basura.
  • La mayoría de los GC adoptan la idea del reciclaje generacional
    • La base teórica es que la mayoría de los objetos son perecederos y pueden reciclarse tan pronto como se agoten, mientras que una pequeña cantidad de objetos sobrevivirá durante mucho tiempo y será difícil reciclarlos cada vez.
    • De acuerdo con las características de estos dos tipos de objetos, el área de reciclaje se divide en la nueva generación y la generación anterior.La nueva generación adopta el método de copia de marca , y la generación anterior generalmente adopta el método de acabado de marca
  • Según la escala de GC, se puede dividir en Minor GC , Mixed GC y Full GC

Nueva generación: Hay muchos objetos basura (objetos locales que a menudo son nuevos en el método)
Vieja generación: Hay muchos objetos que sobreviven, que son difíciles de reciclar o que no necesitan reciclarse con frecuencia, y la clasificación no será particularmente requiere mucho tiempo (por ejemplo, objetos estáticos, en el marco Objetos utilizados durante mucho tiempo) (Hay muchos objetos sobrevivientes en la generación anterior, y el método de copia de marca también desperdiciará memoria extremadamente) Algoritmo de análisis de accesibilidad: encuentre la raíz GC
y márquelo (primero encuentre el objeto que no se reciclará, y luego siga su cadena de referencia. Encuentre, márquelo nuevamente)
Método de marcado de tres colores: vea a continuación
Hay muchos tipos de recolectores de basura, vea a continuación
. nueva generación, recolección de basura a pequeña escala, tiempo de pausa corto y poco impacto en el sistema GC completo: nueva generación y La generación anterior se quedó sin memoria, y llegó una recolección de basura integral. El tiempo de pausa fue largo y el sistema estaba obviamente atascado En general, no querían ver el GC mixto de GC completo: se encuentra entre los dos anteriores, en referencia a: la recolección de basura recién nacida se ha producido en la generación, y la recolección de basura también se ha producido en algunas generaciones antiguas, una recolección de basura mixta, un método de reciclaje único del recolector de basura G1


Resuelva personalmente los conceptos relacionados con GC y la memoria del montón:
GC simplemente reclama los
nuevos objetos en la memoria del montón y los coloca en la memoria del montón.División de la
memoria del montón:
desde la perspectiva de la edad, JVM divide la memoria del montón en nueva generación y antigua generación
Dividido en: eden y Survivor, Survivor se puede subdividir en: desde, hasta
la primera descripción general, hay un marco general: luego mire lentamente el
inserte la descripción de la imagen aquí
diagrama de proceso detallado a continuaciónamarilloinactivo, se asigna el color blanco
El marcado se puede resumir en una oración: Averigüe si el objeto raíz hace referencia directa o indirectamente

reciclaje generacional

  1. Garden of Eden, donde los objetos se asignan inicialmente aquí, junto con el área de sobrevivientes (dividida en desde y hacia) se llama la nueva generación,

inserte la descripción de la imagen aquí

  1. Cuando el recuerdo del Edén sea insuficiente, marque los objetos sobrevivientes del Edén y de (no en esta etapa)

inserte la descripción de la imagen aquí

  1. Copie los objetos sobrevivientes a usando el algoritmo de copia. Una vez completada la copia, tanto la memoria de Eden como la de Eden se liberan.

inserte la descripción de la imagen aquí

  1. cambiar de y a

inserte la descripción de la imagen aquí

  1. Después de un tiempo, el recuerdo del Edén vuelve a ser insuficiente.

inserte la descripción de la imagen aquí

  1. Marque los objetos sobrevivientes del Edén y de (no en esta etapa)

inserte la descripción de la imagen aquí

  1. Copie el objeto sobreviviente a usando el algoritmo de copia

inserte la descripción de la imagen aquí

  1. Después de copiar, tanto eden como from memory se liberan.

inserte la descripción de la imagen aquí

  1. cambiar de y a

inserte la descripción de la imagen aquí

  1. Vieja generación vieja, cuando los objetos en el área de sobrevivientes sobreviven varias colecciones (hasta 15 veces), se promocionan a la generación anterior (la memoria insuficiente en el área de sobrevivientes o los objetos grandes darán lugar a una promoción temprana )

Superficie superviviente insuficiente: si no alcanza para ser copiado, hay que trasladarlo a la vejez (es muy grande, la escasez debe ser por la existencia de objetos que han sobrevivido al reciclaje antes) y no hay forma de promocionar objetos grandes por
adelantado: cada vez que se debe copiar y copiar el GC, consume demasiado, es mejor avanzar a la generación anterior por adelantado

escala GC

  • El GC menor ocurre en la nueva generación de recolección de basura, con un tiempo de pausa corto

  • GC mixto de nueva generación + recolección de basura en algunas áreas de la generación anterior, exclusivo del recolector G1

  • Recolección de basura completa (completa) de nueva generación + generación anterior de GC completa , tiempo de pausa prolongado, debe tratar de evitar

marca de tres colores

Es decir, el estado de marcado del objeto se registra en tres colores.

  • negro - marcado
  • gris - marcado
  • blanco – aún no marcado

Marcado en negro: a lo largo de la cadena de referencia del objeto raíz, se ha encontrado este objeto y también se han procesado otras referencias dentro de este objeto. Marcado en
gris: a lo largo de la cadena de referencia del objeto raíz, se ha encontrado este objeto, pero otras las referencias dentro de este objeto aún no se han procesado
Blanco - aún no marcado: es el último objeto restante que se ha marcado

  1. Los primeros tres objetos aún no han sido procesados, indicados en gris

inserte la descripción de la imagen aquí

  1. Se ha procesado la referencia del objeto, indicado en negro, y el objeto al que hace referencia el negro se vuelve gris

Marque su referencia directa como gris, se considera que su procesamiento de referencia está completo y se puede marcar directamente como negro

inserte la descripción de la imagen aquí

  1. Etcétera

inserte la descripción de la imagen aquí

  1. Marcado a lo largo de la cadena de referencias

inserte la descripción de la imagen aquí

  1. El último objeto blanco sin marcar es basura.

inserte la descripción de la imagen aquí

Problema de etiqueta faltante concurrente

El GC anterior no es concurrente. Cuando el GC está funcionando, el subproceso del usuario se suspende, por lo que el subproceso del usuario no afectará al subproceso del GC. Es decir, cuando el GC está marcando, el subproceso del usuario se suspende
y no tendrá ningún impacto en el marcado (no modificará la cadena de referencia)
El GC no concurrente es ineficiente, y el GC concurrente, es decir, el marcado concurrente, definitivamente es necesario.
Entonces, cuando el GC está marcando, el subproceso del usuario sigue funcionando. En caso de que el el hilo del usuario modifica la relación de referencia durante el proceso de marcado, es fácil causar marcas faltantes.

Los recolectores de elementos no utilizados más avanzados admiten el marcado simultáneo , es decir, los subprocesos de usuario aún pueden funcionar durante el proceso de marcado. Pero esto trae un nuevo problema, si el subproceso del usuario modifica la referencia del objeto, entonces hay un problema de falta de etiquetas. Por ejemplo:

  1. El trabajo de marcado como se muestra aún no está completo

inserte la descripción de la imagen aquí

  1. El subproceso de usuario está trabajando al mismo tiempo, desconectando la referencia entre los objetos 3 y 4 en la primera capa. En este momento, para el subproceso de recolección de basura que está procesando el objeto 3, tratará el objeto 4 como basura blanca

En realidad, es razonable reciclar 3 en este momento
, pero en caso de que otros objetos hagan referencia a él después de desconectarlo (no es que no lo usemos, pero lo uso para otros), no se puede reciclar (ver más abajo)

inserte la descripción de la imagen aquí

  1. Sin embargo, si otros subprocesos de usuario crean referencias a los objetos 2 y 4, porque el objeto 2 es un objeto procesado en negro , el subproceso de recolección de basura no se dará cuenta del cambio en la relación de referencia , lo que resultará en una marca faltante.

inserte la descripción de la imagen aquí

  1. Si el subproceso del usuario hace que el objeto negro se refiera a un objeto recién agregado, también habrá un problema de marca faltante

El objeto negro ha sido procesado (marcado como negro, se considerará como procesado), y el objeto que ha sido procesado no se volverá a procesar (no encontrará repetidamente su referencia directa y lo marcará como gris)

inserte la descripción de la imagen aquí

Por lo tanto, para el marcado simultáneo , se debe resolver el problema del marcado faltante, es decir, se deben registrar los cambios en el proceso de marcado. Hay dos soluciones:

El núcleo de la resolución de marcas faltantes es: registrar cambios en el proceso de calificación + procesamiento secundario

  1. Actualización incremental método de actualización incremental , el recolector de elementos no utilizados de CMS utiliza
    • La idea es interceptar cada acción de asignación, mientras la asignación ocurra, el objeto asignado será registrado y confirmado nuevamente en la etapa de remarcado.
  2. Instantánea al principio, método de instantánea original de SATB, utiliza el recolector de elementos no utilizados G1
    • La idea es interceptar cada acción de asignación, pero los objetos registrados son diferentes y estos objetos deben procesarse dos veces en la etapa de remarcado.
    • Los objetos recién agregados se registrarán
    • Los objetos cuya relación de referencia se elimina también se registran

La flecha roja negra -> objeto blanco y negro en la imagen de arriba es el objeto asignado (asigne el objeto blanco al objeto negro)

inserte la descripción de la imagen aquí

Recolector de basura - GC paralelo

  • Se produce un GC menor debido a la memoria insuficiente en eden, y se usa el algoritmo de copia de marca, que necesita suspender los subprocesos del usuario

  • Old Full GC ocurre debido a memoria insuficiente y se usa el algoritmo de clasificación de marcas, que necesita suspender los subprocesos de usuario

  • Concéntrese en el rendimiento (tiempo de respuesta, el tiempo de pausa está bien, pero en general, un tiempo de pausa más corto está bien)

Parallel GC: en realidad consta de 2 recolectores de basura, uno funciona en la generación joven y el otro funciona en el
GC menor de edad avanzada. Cuando solo el recolector de basura de nueva generación funciona en
el GC completo, tanto la basura de nueva generación como la de generación anterior los colectores funcionarán
Copia de marca y Acabado de marca (lento) no tendrán fragmentación de memoria

Recolector de basura - ConcurrentMarkSweep GC

  • Es un reciclador que funciona en la vejez y admite el marcado concurrente , y utiliza un algoritmo de compensación concurrente

    • No es necesario suspender los subprocesos de los usuarios durante el marcado simultáneo (puede provocar que falte el marcado)
    • Todavía es necesario suspender el subproceso de usuario al volver a marcar (el subproceso de usuario ya no puede ser concurrente cuando se trata de marcas faltantes y debe suspenderse, de lo contrario será interminable)
  • Si la concurrencia falla (es decir, la velocidad de reciclaje no puede seguir el ritmo de la creación de nuevos objetos), se activará Full GC

  • Preste atención al tiempo de respuesta (es decir, el beneficio de esto es que el tiempo de respuesta es rápido y no necesita esperar mucho tiempo)

ConcurrentMarkSweep GC Este es un recolector de basura antiguo.
ConcurrentMarkSweep GC se conoce como recolector de basura CMS . Concurrente significa que
Concurrent:并发 Mark:标记 Sweep: 扫描,打扫
los subprocesos de usuario se detienen durante un breve período de tiempo durante GC y se pueden ejecutar simultáneamente . Se refiere a borrar y reciclar objetos basura blancos. Sin embargo , debido a que las personas usan el método de marcar y borrar y tienen problemas de fragmentación de la memoria, el último JDK lo ha marcado como obsoleto.

STW (Detener el mundo)
inserte la descripción de la imagen aquí

Recolector de basura - G1 GC

  • Equilibrio entre el tiempo de respuesta y el rendimiento
  • Dividido en múltiples áreas, cada área puede actuar como edén, sobreviviente, viejo, gigantesco, entre los cuales enorme está especialmente preparado para objetos grandes
  • Dividido en tres fases: recogida de nueva generación, marcado concurrente, recogida mixta
  • Si la concurrencia falla (es decir, la velocidad de reciclaje no puede seguir el ritmo de la creación de nuevos objetos), se activará Full GC

G1 GC dice: G uno Recolector de basura
gigantesco: enorme

Descripción general:
inserte la descripción de la imagen aquí

G1 también tiene una política de fondo garantizada: velocidad de reciclaje <velocidad de creación de nuevos objetos, es decir, falla concurrente: FailBack Full GC realiza un reciclaje como un todo, y el tiempo de pausa será más largo

Fase de colección G1 - Colección de nueva generación

  1. Inicialmente, todas las regiones están inactivas

inserte la descripción de la imagen aquí

  1. Se crean algunos objetos y se seleccionan algunas áreas libres como el área Eden para almacenar estos objetos

inserte la descripción de la imagen aquí

  1. Cuando Eden necesite recolección de basura, elija un área libre como área de supervivientes , use el algoritmo de copia para copiar los objetos supervivientes y necesite suspender el subproceso del usuario
    (la nueva generación adopta el método de copia de marcas, la copia debe ser STW, no concurrente)
    (todos los objetos sobrevivientes en el área de Edén se copian en un área de sobreviviente (el área de destino y luego el estado de intercambio de las áreas de origen y destino))

inserte la descripción de la imagen aquí

  1. Una vez completada la copia, se liberará la memoria anterior de Eden

inserte la descripción de la imagen aquí

  1. A medida que pasa el tiempo, Eden se queda sin memoria otra vez.

inserte la descripción de la imagen aquí

  1. Copie los objetos sobrevivientes en el Jardín del Edén y el área sobreviviente anterior a la nueva área sobreviviente utilizando el algoritmo de copia, y los objetos más antiguos se promoverán a la generación anterior

(Todos los objetos en el área eden y el área superviviente se copian en la nueva área superviviente (similar a))

inserte la descripción de la imagen aquí

  1. Libera la memoria de Eden y la zona de supervivientes anterior

inserte la descripción de la imagen aquí

Fase de cobro G1 - marca concurrente y cobro mixto

La premisa es que la memoria en la generación anterior es casi insuficiente, y es necesario comenzar a reciclar la generación anterior.La estrategia de marcado de la generación anterior es: marcado concurrente

  1. Cuando la memoria ocupada por la generación anterior supera el umbral, se activa el marcado simultáneo y no es necesario suspender los subprocesos de usuario en este momento.

inserte la descripción de la imagen aquí

No se trata de recuperar directamente todas las áreas de generación anterior, sino de seleccionar algunas áreas de generación anterior con alto valor de recuperación (pocos objetos sobrevivientes) para reciclar primero

  1. Después del marcado simultáneo, habrá una fase de remarcado para resolver el problema de las marcas faltantes.En este momento, los hilos de los usuarios deben suspenderse. Después de completar todo esto, sabemos qué objetos sobrevivientes están en la generación anterior y luego ingresamos a la etapa de colección mixta. En este momento, no se reciclarán todas las áreas de generación anterior, pero las áreas con alto valor (menos objetos sobrevivientes) se recuperarán primero de acuerdo con el objetivo de tiempo de pausa (este también es el origen del nombre Gabage First).

inserte la descripción de la imagen aquí

Recolección mixta, no solo recolectando la generación antigua seleccionada con alto valor de reciclaje (rojo en la imagen de arriba), sino también recolectando la nueva generación (eden+survivor)

  1. En la fase de recolección mixta, edén, sobreviviente y viejo están involucrados en la replicación. La siguiente figura muestra la replicación de los objetos sobrevivientes en Eden y el área de sobrevivientes.

inserte la descripción de la imagen aquí

  1. La siguiente figura muestra la replicación de los objetos sobrevivientes promovidos en las áreas de generación anterior y sobreviviente

inserte la descripción de la imagen aquí

  1. La copia está completa y la memoria está liberada. Ingrese a la próxima ronda de colección de nueva generación, marca concurrente, colección mixta

inserte la descripción de la imagen aquí

4. Sin memoria

Sin memoria: la memoria en esta área está agotada y se informa un error

Requerir

  • Ser capaz de nombrar varias situaciones típicas que conducen al desbordamiento de la memoria.

situación típica

  • 1) Desbordamiento de memoria causado por el uso indebido del grupo de subprocesos
    • Referencia day03.TestOomThreadPool
      inserte la descripción de la imagen aquí
      LinkedBlockingQueue es una cola ilimitada (el tipo de entero no se desborda, no se desbordará)

inserte la descripción de la imagen aquí
El código anterior crea continuamente nuevos sitios y los envía. Dado que cada subproceso tiene que bloquearse durante 30 ms, la cola de bloqueo se hace cada vez más grande y crecerá sin límite, lo que conducirá a una explosión de memoria.

inserte la descripción de la imagen aquí
inserte la descripción de la imagen aquí

  • 2) Desbordamiento de memoria causado por demasiados datos de consulta
    • Día de referencia 03.TestOomTooManyObject

Hay demasiadas entradas en la base de datos. Si encuentra Todo, puede encontrar 1 millón de elementos en una búsqueda, que es 1 millón de colecciones de productos POJO muy comunes, y también ocupa 363 MB de memoria. No importa cuán grande sea la memoria del servidor, no puede soportar tal creación. 10 usuarios Tiene que ocupar 3G de memoria,
por lo que el desarrollo de back-end no debe encontrar Todo (no lo escriba usted mismo, y no lo llame)
para escribir código en el futuro, y el sql la consulta debe agregar límite (no funciona si hay condiciones, las condiciones pueden fallar)

Estos errores no se pueden detectar en el entorno de prueba, solo en el entorno de producción puede haber millones de datos y los problemas quedarán expuestos.

Por lo tanto, después de que se complete el proyecto, también es necesario hacer una prueba de estrés.La entrevista le preguntará

  • 3) Desbordamiento de memoria causado por clases generadas dinámicamente
    • Día de referencia 03.TestOomTooManyClass
      inserte la descripción de la imagen aquí
      inserte la descripción de la imagen aquí

5. Carga de clase

Requerir

  • Dominar la fase de carga de clases
  • Dominar cargadores de clase
  • Comprender el mecanismo de delegación de los padres

Tres fases del proceso de carga de clases

  1. carga

    1. Cargue el código de bytes de la clase en el área del método y cree un objeto class.class
    2. Si la clase principal de esta clase no está cargada, cargue la clase principal primero
    3. La carga se realiza de forma perezosa (solo se carga cuando esta clase se usa realmente)

类.class对象Hay una serie de métodos de reflexión en él, que pueden conocer toda la información de la clase: qué miembros están allí y qué métodos
类.class对象están almacenados en el montón.

  1. Enlace

    1. Verificación: verifique si la clase se ajusta a la especificación de clase, legalidad, verificación de seguridad
    2. Preparación: asigne espacio para variables estáticas y establezca valores predeterminados (pero la declaración de asignación escrita manualmente no se ejecutará en este momento, se ejecutará en la fase de inicialización, aquí es solo para asignar espacio para variables estáticas (las variables finales son excepciones, se ejecutará en este momento) asignación))
    3. resolver: resuelve referencias simbólicas a grupos constantes en referencias directas
  2. inicialización

    1. Los bloques de código estático, las asignaciones de variables modificadas estáticas y las asignaciones de variables de tipo de referencia modificadas finales estáticas se combinarán en un <cinit>método, que se llamará durante la inicialización.
    2. La asignación de variables de tipo básico modificado por final estático se completa en la etapa de enlace
    3. La inicialización es ejecución perezosa (la ejecución perezosa se inicializará cuando la clase se use realmente, y se dividirá en ceros, qué bueno)

medios de verificación

  • Use jps para ver el número de proceso
  • Use jhsdb para depurar, ejecute el comando jhsdb.exe hsdbpara abrir su interfaz gráfica
    • Class Browser puede ver qué clases están cargadas en el jvm actual
    • Comando de universo de consola para ver el rango de memoria en montón
    • Use el comando g1regiondetails de la consola para ver los detalles de la región
    • scanoops 起始地址 结束地址 对象类型Puede encontrar la dirección del objeto en un rango según el tipo
    • El comando de la consola inspect 地址puede ver los detalles del objeto correspondiente a esta dirección
  • Use el comando javap para ver el código de bytes de la clase

descripción del código

  • day03.loader.TestLazy: verifique que la carga de la clase sea diferida y que la carga de la clase se active cuando se use
  • day03.loader.TestFinal - Verifica que las variables finales no activen la carga de clases

El objeto de código de bytes está de hecho en el espacio de almacenamiento dinámico (área eden), no en el área de método

inserte la descripción de la imagen aquí
inserte la descripción de la imagen aquí

inserte la descripción de la imagen aquí
inserte la descripción de la imagen aquí

  • Métodos de inicialización de clase (miembros estáticos (tipos comunes no finales) y bloques de código estático)

Los miembros estáticos y las declaraciones en el bloque de código estático se integrarán en un método (método cinit), que se llama cuando se inicializa la clase. Nota: las variables estáticas
finales de tipo sin referencia se cargan cuando se carga la clase (crear bytecode Object) se inicializará, no hay necesidad de integrar aquí

inserte la descripción de la imagen aquí

  • Las variables de tipo que no son de referencia decoradas con final no activarán la carga de clases

Las primeras dos declaraciones de impresión parecen usar clases. Student.c y Student.m en realidad no usan clases, por lo que las clases no se cargan en este momento y no hay clases en la memoria, lo que prueba completamente que la carga de clases es una carga diferida
. En este punto, la carga de la clase está completa y puede ver la información del código de bytes de la clase (estructura de la clase: qué miembros, qué métodos)

inserte la descripción de la imagen aquí
Cuando una clase A usa la variable estática final de tipo ordinario de otra clase B, que en realidad es una constante , en este momento, la clase A copia directamente la constante de la clase B en su propia clase, y realmente no usará otra clase B en absoluto. .

Si el valor de la constante es relativamente pequeño, escríbalo directamente en el método. Si el valor es relativamente grande y excede el rango máximo de corto (> 32767), se colocará en el grupo de constantes y estará bien.
para obtenerlo del conjunto de constantes cuando sea necesario.
Es decir, cuanto mayor sea el valor, se copiará en el conjunto de constantes de la clase A, cada clase tiene su propio conjunto de constantes (una lista de constantes y 1, 2, 3, ... numere cada constante, dé el número, vaya directamente al grupo de constantes para obtener el valor de esa constante)

inserte la descripción de la imagen aquí

Análisis: referencia de símbolo: "la referencia directa es un proceso que continúa con la ejecución del código y no se puede completar de una vez. Las
referencias a las variables miembro estáticas de la clase se colocan todas en el
grupo constante. No hay referencias directas, solo referencias simbólicas (los punteros nulos solo saben a qué tipo apuntar, pero no hay memoria real)

Cargador de clases para jdk 8

nombre que clase cargar ilustrar
Cargador de clases Bootstrap JAVA_HOME/jre/lib sin acceso directo
Extensión ClassLoader JAVA_HOME/jre/lib/ext El padre es Bootstrap, mostrando nulo
Cargador de clases de aplicaciones ruta de clase El superior es Extensión
cargador de clases personalizado personalizar El superior es la aplicación.

Los cargadores de clases como String.class, Application y Extension no están en el cargador de clases y no se pueden cargar. En este momento, debe pedirle a Bootstrap que inicie el cargador de clases para cargarlo, y luego el nivel inferior puede verlo (tipo de cadena es jdk, y el nivel superior necesita usarlo. Todo es visible y razonable)
Al igual que la clase Student.class escrita por mí mismo, también seguirá las reglas y preguntará hacia arriba paso a paso. El cargador de capa superior no tiene esta clase , y el cargador de clases de la aplicación está calificado para cargar Student.class y cargarlo (el cargador de clases superior no es visible y no necesita serlo, este tipo de blindaje es muy razonable)

Mecanismo de Delegación de Padres

La denominada delegación parental se refiere a la delegación de prioridad del cargador de clases de nivel superior para cargar, si el cargador de clases de nivel superior

  • El nivel superior puede encontrar y cargar esta clase. Después de la carga, la clase también es visible para el cargador del nivel inferior.
  • Si no se puede encontrar esta clase, el cargador de clases de nivel inferior es elegible para realizar la carga

El propósito de la delegación de los padres es doble

  1. Permita que las clases en el cargador de clases de nivel superior se compartan con el nivel inferior (y viceversa), lo que significa que sus clases pueden confiar en las clases principales proporcionadas por jdk (y viceversa: jdk definitivamente no necesita confiar en las clases las escribes tu mismo)

  2. Deje que la carga de clases tenga un orden de prioridad para garantizar que las clases principales se carguen primero

Las clases en el cargador de clases superior son visibles para la clase inferior
, pero las clases en el cargador de clases inferior no son visibles para la clase superior

Conceptos erróneos sobre la delegación de los padres

Las respuestas a las siguientes preguntas de la entrevista son incorrectas

inserte la descripción de la imagen aquí

¿Qué ocurre?

  • ¿Puedo cargar un java.lang.System falso escribiendo yo mismo un cargador de clases?La respuesta es no.

  • Suponiendo que su propio cargador utilice la delegación principal , entonces el cargador de clases de inicio cargará primero el java.lang.System real, y el falso no se cargará de forma natural.

  • Asumiendo que su propio cargador de clases no usa la delegación de padres , entonces cuando su cargador de clases carga el falso java.lang.System, primero necesita cargar la clase padre java.lang.Object, pero no usa la delegación y no puede encontrar java .lang.Object por lo que la carga fallará

  • Lo anterior son solo suposiciones . De hecho, encontrará que cuando el cargador de clases personalizadas carga una clase que comienza con java., generará una excepción de seguridad. En las versiones anteriores a jdk9, estos nombres de paquetes especiales están vinculados al módulo, e incluso la compilación no puede pasar (en operación real, se lanza una excepción de seguridad directamente, o la compilación falla, y no se alcanza el paso hipotético, jdk ya ha tomado medidas de seguridad para evitar que haga esto, y no puede volver a escribir el nombre del paquete java.lang)

descripción del código

  • day03.loader.TestJdk9ClassLoader - Demostración de la relación vinculante entre el cargador de clases y el módulo = "Conclusión: no reescriba el nombre del paquete y el nombre de la clase que ya tiene jdk

6. Cuatro tipos de referencias

Requerir

  • Domina las cuatro citas

fuerte referencia

  1. La asignación de variables ordinarias es una referencia sólida, como A a = new A();

  2. A través de la cadena de referencia de GC Root, si el objeto no se puede referenciar fuertemente, el objeto se puede reciclar

inserte la descripción de la imagen aquí

Referencia suave (SoftReference)

  1. Por ejemplo: SoftReference a = new SoftReference(new A()); (Hay un objeto SoftReference en el medio para el tránsito, y a está asociado indirectamente con el objeto new A())

  2. Si solo hay una referencia suave al objeto, la primera recolección de elementos no utilizados no reclamará el objeto. Si la memoria aún es insuficiente, el objeto se liberará cuando se recicle nuevamente (el GC se activará cuando la memoria sea insuficiente, la primera vez te perdonaré, y la segunda vez la memoria es insuficiente El GC se activa nuevamente y el objeto de referencia suave se reciclará (el objeto señalado por la referencia fuerte no puede ser reciclado por GC))

  3. La referencia suave en sí debe liberarse junto con la cola de referencia (como se muestra en la figura a continuación, el objeto a es una referencia suave, pero la referencia suave en sí sigue siendo una referencia fuerte y el GC no puede reciclar la referencia suave en sí)

  4. Un ejemplo típico son los datos de reflexión (los datos obtenidos a través de la reflexión son todos datos de referencia suave, como: class name.class= "las variables miembro obtenidas, los métodos y otra información de datos son todas referencias suaves)

inserte la descripción de la imagen aquí

Referencia débil (WeakReference)

  1. Ejemplo: WeakReference a = new WeakReference(new A());

  2. Si solo las referencias débiles se refieren al objeto, el objeto se liberará cada vez que se produzca la recolección de elementos no utilizados.

  3. La referencia débil en sí debe liberarse con la cola de referencia (igual que arriba)

  4. Un ejemplo típico es el objeto Entry en ThreadLocalMap

inserte la descripción de la imagen aquí

Referencia fantasma (PhantomReference)

  1. Ejemplo: PhantomReference a = new PhantomReference(new A(), referenceQueue);

  2. Debe usarse junto con la cola de referencia.Cuando el objeto al que hace referencia la referencia virtual se recicla, el subproceso del controlador de referencia pondrá en cola el objeto de referencia virtual, para que pueda saber qué objetos se reciclan y procesar más sus recursos asociados.

  3. Un ejemplo típico es que Cleaner libera la memoria directa asociada con DirectByteBuffer

inserte la descripción de la imagen aquí

Cola de referencia detallada: como se muestra en la figura, después de que se liberan los objetos a y b asociados con la referencia virtual, la referencia virtual en sí se colocará en la cola de referencia, y el subproceso del controlador de referencia es responsable de recuperarlos, ya que pueden también estar asociado con algunos otros recursos (no solo un objeto y un objeto b)

descripción del código

  • day03.reference.TestPhantomReference: demuestra el uso básico de las referencias fantasma
  • day03.reference.TestWeakReference: simule ThreadLocalMap, use la cola de referencia para liberar la memoria de entrada
String str = new String("hello"); // "hello"在堆内存中 (new出来的都在堆中)
String str = "hello"; // "hello" 在常量池中 

El objeto de entrada en ThreadLocalMap, la clave es una referencia débil y el valor es una referencia fuerte. La
inserte la descripción de la imagen aquí
figura anterior es una solución típica de fuga de memoria
: use una cola de referencia para asociar una entrada con una cola de referencia. Cuando la clave de la entrada se recicla, toda la Entrada El objeto se colocará en la cola de referencia y luego eliminará directamente la referencia del Mapa del objeto Entrada que ya está en la cola de referencia (o verificará si la Entrada actual está en el Mapa y luego registrará la referencia correspondiente de la matriz de entrada en el mapa establecido en nulo), no hay ninguna referencia que lo apunte, se reciclará la próxima vez que se recicle
jdk no se implementa de esta manera, y el costo será relativamente alto

★★★La clave es el propio objeto ThreadLocal. Cuando el hilo se está ejecutando, debe estar fuertemente referenciado por otros objetos, por lo que no hay miedo de que se establezca como una referencia débil. Antes de que finalice el hilo, la clave (con otras referencias sólidas) no se publicarán. Pero una vez que el valor se establece como una referencia débil, en realidad solo existe esta referencia débil. Es muy probable que el GC recicle el subproceso antes de que finalice. ★★★

7. finalizar

Requerir

  • Domina el principio de funcionamiento y las desventajas de finalizar.

finalizar

  • Respuesta general: es un método en Object.Si una subclase lo anula, este método se llamará durante la recolección de basura y los recursos se pueden liberar y limpiar en él.
  • Excelente respuesta: es muy malo poner la liberación y limpieza de recursos en el método de finalización, lo que afectará en gran medida el rendimiento e incluso causará OOM (memoria insuficiente) en casos graves. Se ha marcado como @Deprecated desde Java9 y no se recomienda para ser utilizado.

Seguimiento: ¿Por qué es muy malo y afecta el rendimiento?
Vea el principio a continuación:

Suplemento: subproceso de daemon, cuando el subproceso principal ha terminado, el subproceso de daemon ya no se ejecutará (incluso si hay código que no se ha ejecutado)

principio de finalización

  1. La lógica principal para procesar el método finalize se encuentra en la clase java.lang.ref.Finalizer, que contiene una variable estática llamada unfinalized (estructura de lista doblemente enlazada), y Finalizer también se puede considerar como otro objeto de referencia (estado y software, Débil e imaginario, pero no externo, no se puede usar directamente)
  2. Cuando se reescribe el objeto del método de finalización, cuando se llama al método de construcción, la JVM lo envolverá en un objeto Finalizer y lo agregará a la lista no finalizada (lo que significa que el método de finalización de estos objetos aún no se ha llamado, no t liberarlo fácilmente (también Lo que hace esta cadena de referencia))

inserte la descripción de la imagen aquí

  1. Hay otra variable estática importante en la clase Finalizer, es decir, la cola de referencia ReferenceQueue (similar a la cola de referencia en las cuatro referencias anteriores, que ayuda a liberar el objeto de referencia en sí mismo (ayudando a liberar algunos otros recursos asociados). La diferencia es que el objeto asociado temporalmente no se puede reciclar, porque primero se debe llamar al método de finalización) , está vacío al principio. Cuando los objetos de perro se pueden recolectar basura, los objetos Finalizer correspondientes a estos objetos de perro se agregarán a esta cola de referencia.
  2. Pero en este momento, el objeto Dog no se puede reciclar de inmediato, porque la cadena de referencia no está finalizada -> Finalizer todavía hace referencia a él, para [no se apresure a reciclar, espere hasta que termine de ajustar el método de finalización y luego recicle]
  3. El subproceso FinalizerThread tomará cada objeto Finalizer de ReferenceQueue uno por uno, los desconectará de la lista vinculada y realmente llamará al método finalize.

inserte la descripción de la imagen aquí

  1. Dado que todo el objeto Finalizer se ha desconectado de la lista vinculada no finalizada, nadie puede hacer referencia a él y al objeto perro, por lo que se reciclará en el próximo gc

finalizar desventajas

  • No se puede garantizar la liberación de recursos: FinalizerThread es un subproceso daemon, y es posible que el código no se ejecute a tiempo, y el subproceso finalizará
  • No se puede juzgar si se produjo un error: cuando se ejecuta el método de finalización, se tragará cualquier excepción (se traga el intento de captura arrojable)
  • La memoria no se libera a tiempo: cuando el objeto que reescribe el método finalize es gc por primera vez, la memoria que ocupa no se puede liberar a tiempo, porque tiene que esperar a que FinalizerThread llame a finalizar, eliminarlo de los no finalizados. cola, y la segunda vez Solo cuando gc realmente puede liberar la memoria
  • Algunos artículos mencionaron [El subproceso Finalizer competirá con nuestro subproceso principal, pero debido a que tiene una prioridad más baja y obtiene menos tiempo de CPU, nunca alcanzará el ritmo del subproceso principal] Esto obviamente es incorrecto, la prioridad de FinalizerThread es mayor que el de los subprocesos ordinarios (max-2 = 8, los subprocesos ordinarios son solo 5), la razón debe ser la combinación de una ejecución en serie lenta de finalizar y otras razones (tome uno en la cola y llame a uno finalizar)

descripción del código

  • day03.reference.TestFinalize - código de prueba para finalizar

inserte la descripción de la imagen aquí

Supongo que te gusta

Origin blog.csdn.net/hza419763578/article/details/130630961
Recomendado
Clasificación