JVM - carga de clases y recolección de basura

Tabla de contenido

prefacio

Introducción a JVM

División del área de memoria de JVM

Mecanismo de carga de clase JVM

1. carga

Modelo de delegación de padres

2. Verificación

opciones de verificación

3. Prepárate

4. Análisis

5. Inicialización

carga de clase de disparador

Estrategia de recolección de basura JVM GC

Uno: averiguar quién es basura 

1. Conteo de referencia

2. Análisis de accesibilidad (esta solución es adoptada por Java).

Dos: Suelta el objeto basura

Tres estrategias típicas

Ideas de implementación de JVM


prefacio

Cuando estamos aprendiendo JVM, en realidad hay una gran cantidad de contenido, pero la mayor parte del contenido está estereotipado. Si desea comprenderlo a fondo, debe leer una gran cantidad de código fuente sobre JVM. El código fuente de JVM está escrito en C++. Si desea profundizar, puede leer el libro "Comprensión detallada de la máquina virtual de Java".

Este artículo se centra en las preguntas comunes de las entrevistas en JVM.

Introducción a JVM

JVM es la abreviatura de Java Virtual Machine, que significa máquina virtual de Java.
Una máquina virtual se refiere a un sistema informático completo que se simula mediante software y tiene funciones de hardware completas y se ejecuta en un entorno completamente aislado.
Máquinas virtuales comunes: JVM, VMwave, Virtual Box.
La diferencia entre la JVM y las otras dos máquinas virtuales:

  1. VMwave y VirtualBox simulan el conjunto de instrucciones de la CPU física a través del software y habrá muchos registros en el sistema físico;
  2. La JVM simula el conjunto de instrucciones del código de bytes de Java a través del software. Solo el registro de la PC se reserva principalmente en la JVM, y se recortan otros registros.

La JVM es una computadora personalizada que no existe en la realidad.

División del área de memoria de JVM

La JVM es en realidad un proceso Java, y el proceso Java, es decir, la JVM solicitará un gran espacio de memoria del sistema operativo para que lo use el código Java.

La JVM divide aún más el espacio de memoria solicitado por el sistema operativo y proporciona diferentes usos de cada espacio dividido.

Entre ellos, el núcleo es el área de pila, montón y metadatos (área de método).

  • El código Java utiliza la pila de la máquina virtual, almacenando principalmente algunas variables locales y manteniendo la relación de llamada entre los métodos.
  • La pila de métodos nativos es utilizada por métodos nativos dentro de la JVM.
  • Almacenados en el montón hay nuevos objetos y variables miembro.
  • Lo que se almacena en el contador del programa es una dirección de memoria, esta dirección de memoria es la dirección donde se ejecutará el siguiente bytecode, su función es registrar la instrucción que ejecuta el programa actual.

Cabe señalar que solo hay una copia del montón y el área de metadatos en una JVM, es decir, varios subprocesos comparten el área del montón y el área de metadatos.

Hay múltiples copias de la pila (pila de método local y pila de máquina virtual) y contador de programa, es decir, cada subproceso tendrá uno.

Existe una relación de uno a uno entre la operación de subprocesos de la JVM y la operación de subprocesos del sistema operativo. Es decir, cada hilo creado en código Java tendrá un hilo correspondiente en el sistema operativo.

Las preguntas de la entrevista aquí son principalmente para determinar en qué área de la JVM se encuentra una determinada variable u objeto.

Por ejemplo el siguiente código:

void func() {
    Test t1 = new Test();
}

En el código anterior, instanciamos un objeto Test en un método.

 El método func se almacena como algunas instrucciones binarias en el área de metadatos.

Podemos ver que la variable t1 está definida en el método, por lo que es una variable local y la variable local se almacena en la pila.

Y new Test(); El cuerpo de este objeto está en el montón.

De hecho, al igual que las preguntas de la entrevista sobre el área de JVM aquí, solo necesitamos saber qué se almacena en cada área de JVM.

  • El código Java utiliza la pila de la máquina virtual, almacenando principalmente algunas variables locales y manteniendo la relación de llamada entre los métodos.
  • La pila de métodos nativos es utilizada por métodos nativos dentro de la JVM.
  • Almacenados en el montón hay nuevos objetos y variables miembro.
  • Lo que se almacena en el contador del programa es una dirección de memoria, esta dirección de memoria es la dirección donde se ejecutará el siguiente bytecode, su función es registrar la instrucción que ejecuta el programa actual.

Mecanismo de carga de clase JVM

Para una clase, su ciclo de vida es el siguiente:

 Los 5 pasos anteriores también son el proceso de carga de clases y el orden fijo. Principalmente estudiamos los 5 pasos anteriores.

Específicamente, la carga de clase consiste en cargar un archivo .class, es decir, un archivo de clase compilado, en la memoria, y el proceso de obtención de un objeto de clase se denomina carga de clase.

Para que un programa se ejecute, necesita cargar instrucciones y datos en la memoria. Esto es lo que hace la carga de clases.

Estos son los 5 pasos de la carga de clases:

1. carga

El proceso de carga aquí es realmente simple, es decir, encontrar el archivo .class y luego leer el contenido del archivo.

Pero en el proceso de encontrar archivos .class, habrá un mecanismo muy importante: el modelo de delegación principal

Modelo de delegación de padres

En la JVM, la carga de clases requiere un conjunto especial de módulos: cargadores de clases.

En la JVM, hay tres cargadores de clases integrados.

  • BootStrap ClassLoader es responsable de cargar clases en la biblioteca estándar de Java
  • Extension ClassLoader es responsable de cargar algunas clases no estándar que son bibliotecas de extensión de Sun/Oracle
  • La aplicación ClassLoader es responsable de cargar clases escritas en el proyecto y clases en bibliotecas de terceros.

Cuando carga específicamente una clase, su proceso es así:

Primero se debe proporcionar un nombre de clase completamente calificado de una clase, y el nombre de clase "java.lang.String" tiene la forma de una cadena.

Si un cargador de clases recibe una solicitud de carga de clases, no intentará cargar la clase por sí mismo primero, sino que delegará la solicitud al cargador de clases principal para que la complete. Este es el caso para cada nivel del cargador de clases, por lo que todas las solicitudes de carga eventualmente debe transmitirse al cargador de clases BootStrap ClassLoader de nivel superior. Solo cuando el cargador principal informa que no puede completar la solicitud de carga (no encuentra la clase requerida en su ámbito de búsqueda), el cargador secundario lo intentará. Hacer la carga tú mismo.

Para obtener más información, consulte la siguiente figura:

2. Verificación

Dado que el archivo .class tiene un formato de datos claro (binario), el objetivo principal de esta etapa es garantizar que la información contenida en el flujo de bytes en el archivo Class cumpla con todas las restricciones de la "Especificación de máquina virtual Java".

opciones de verificación

Validación de formato de archivo

verificación de código de bytes

Verificación de referencia simbólica...

3. Prepárate

La etapa de preparación es la etapa de asignación formal de memoria para las variables definidas en la clase (es decir, variables estáticas, variables modificadas por estática) y establecimiento del valor inicial de las variables de clase.

Por ejemplo, el siguiente código:

public static int value = 123;

En este momento, el valor de valor en la fase de preparación no es 123, sino 0.
 

4. Análisis

La fase de análisis es el proceso en el que la máquina virtual Java reemplaza las referencias de símbolos en el conjunto de constantes con referencias directas, es decir, el proceso de inicialización de constantes.

  • Referencias simbólicas: las constantes de cadena ya existen en el archivo .class, pero solo conocen sus posiciones relativas entre sí, no sus posiciones específicas en la memoria.
  • Referencia directa: cuando realmente se carga en la memoria, la constante de cadena se llenará en una dirección específica en la memoria. En este momento, la referencia de cadena es una referencia directa (es decir, una referencia común en Java).

5. Inicialización

En la fase de inicialización, la JVM realmente ejecuta el código Java escrito en la clase y entrega el dominio a la aplicación.La fase de inicialización es el proceso de ejecución del método de construcción de la clase. (Si una clase tiene una clase principal, primero debe inicializar la clase principal y luego inicializar la subclase).

carga de clase de disparador

Nota: La acción de carga de clases no significa que la JVM se cargará tan pronto como se inicie, porque la JVM en su conjunto es una estrategia de carga diferida, es decir, no es necesaria y no se carga.

Se cargarán las siguientes tres condiciones:

  1. Creó una instancia de esta clase.
  2. Se utilizan métodos estáticos/propiedades estáticas de esta clase
  3. El uso de una subclase activará la carga de la clase principal

Estrategia de recolección de basura JVM GC

La recolección de basura en Java es un mecanismo que nos ayuda a liberar memoria automáticamente.

Pregunta de la entrevista: ¿Por qué se necesita un mecanismo de recolección de basura?

Porque durante la ejecución del programa, se aplicará una gran cantidad de espacio de memoria al sistema operativo, pero estos espacios también pueden agotarse, porque el espacio de memoria se asigna continuamente sin reciclar, es como producir constantemente basura doméstica sin limpiarla . .

Anteriormente hablamos sobre varias áreas de la JVM, entonces, ¿qué área libera la recolección de basura?

Cabe señalar que cada hilo tendrá una copia de la pila y el contador del programa. Serán destruidos junto con la destrucción del hilo.

Los objetos de clase almacenados en el área de metadatos rara vez se destruyen.

Entonces lo que liberamos es el espacio en el montón. Mencionamos anteriormente que el montón almacena principalmente nuevos objetos.

GC se libera en unidades de objetos. (objeto de liberación)

GC se divide principalmente en dos fases:

Uno: averiguar quién es basura 

Java usa referencias para determinar si es un objeto basura, si no hay ninguna referencia, se determina que el objeto es basura.

1. Conteo de referencia

Organice un espacio adicional para el objeto y guarde un número entero, lo que indica que el objeto tiene varias referencias que apuntan a él. Java en realidad no adopta tal esquema (Python y PHP adoptan este esquema).

Test t1 = new Test();

 En este momento, hay una referencia que apunta hacia él, por lo que el contador de referencia es 1.

Si el código se vuelve así:

Test t1 = new Test();
Test t2 = t1;

 Es decir, a medida que aumenta la referencia, aumentará el contador, y se destruirá la referencia, y el contador disminuirá.

Cuando el contador sea 0, se considerará que el objeto no tiene punto de referencia, es basura.

Pero las desventajas también son obvias:

  1. desperdiciando espacio de memoria
  2. Existen referencias circulares

2. Análisis de accesibilidad (esta solución es adoptada por Java).

Comprenda la relación de referencia entre los objetos como una estructura de árbol, comience desde algunos puntos de inicio especiales, atraviese, siempre que se pueda acceder, sea accesible, no basura, y luego trate lo inalcanzable como basura.

 En este momento, se puede acceder a cualquier nodo de todo el árbol a través de la referencia de root.

El punto clave del análisis de accesibilidad es que para realizar el recorrido anterior, se requiere un punto de partida.

Un punto de partida puede ser:

  1. Variables locales en la pila (cada variable local por pila es un punto de partida)
  2. Objetos a los que se hace referencia en el grupo de constantes
  3. Objetos a los que hacen referencia los miembros estáticos en el área de método

El análisis de accesibilidad, en general, consiste en comenzar desde todos los puntos de partida, ver qué referencias en el objeto pueden acceder a esos objetos, seguir las vides para visitar todos los objetos accesibles y marcar el objeto como "accesible" mientras se atraviesa ".

Análisis de accesibilidad, que supera dos deficiencias del conteo de referencias

Pero también tiene sus propios problemas:

  • Consume más tiempo, por lo que incluso si un objeto se convierte en basura, no se puede encontrar la primera vez, ya que lleva tiempo durante el proceso de escaneo.
  • Al realizar un análisis de accesibilidad, debe seguir las vides. Una vez que la relación de referencia de los objetos en el código actual cambia durante el proceso, pueden aparecer errores.

Por lo tanto, para completar mejor este proceso de seguimiento, ¡es necesario suspender el trabajo de otros subprocesos comerciales! ! ! (STW)

(STW) ¡detén el mundo!

Pero después de todo, Java se ha desarrollado durante tantos años, y también se optimiza continuamente cuando se recicla.El problema de STW también se puede abordar mejor.

Dos: Suelta el objeto basura

Tres estrategias típicas

1: marcar claro

 Si ahora solicito un espacio como el siguiente en la memoria, entonces lo que marco es un objeto basura que debe borrarse.

 Esta estrategia consiste en liberar directamente la memoria del objeto basura.

Pero esta forma simple y cruda generará fragmentación de la memoria.

Fragmentación de la memoria: el espacio de la aplicación es un bloque continuo de espacio, ahora el espacio libre en la figura anterior está disperso en un espacio independiente. Ahora el espacio libre total puede exceder 1G, pero quiero solicitar 500M, pero no puedo solicitar.

2: Algoritmo de copia

El enfoque es dividir el espacio en dos partes. Use solo la mitad a la vez.

El algoritmo de copia consiste en copiar objetos que no son basura a un lado y luego liberar toda el área de manera uniforme.

 En este momento, lo que quiero liberar es el 2 y el 4, y necesito copiar el 1 y el 3 restantes al otro lado. Entonces suéltalo todo aquí.

 El algoritmo de copia resuelve el problema de la fragmentación de la memoria, pero también tiene desventajas:

  • baja utilización de la memoria
  • Si la mayoría de los objetos están reservados y hay poca basura, el costo de copiar es relativamente alto en este momento

3: Acabado de marcas

Similar a la tabla de pedidos para eliminar el elemento intermedio, hay un proceso de manejo

 El problema de la fragmentación de la memoria está resuelto, pero la sobrecarga general de manejo es relativamente grande.

Ideas de implementación de JVM

De hecho, la implementación de la JVM es un método que combina las ideas mencionadas anteriormente.

Ideas de reciclaje generacional

detalles:

  • Un concepto como la edad se establece en un objeto para describir cuánto tiempo ha existido el objeto. Si un objeto acaba de nacer, entonces tiene 0 años.
  • Cada vez que se realiza un escaneo (análisis de accesibilidad), si no se marca como objeto basura, la antigüedad del objeto aumenta en un año.
  • El tiempo activo de este objeto se distingue por la edad.

Regla empírica: cuanto más antiguo sea el objeto, más durará.

Adoptar diferentes estrategias de reciclaje para diferentes edades.

La JVM implementa diferentes estrategias para estas áreas.

1: El objeto recién creado se coloca en el área de Eden

Después de los escaneos de recolección de elementos no utilizados en el área de Eden, GC eliminará la mayoría de los objetos en la primera ronda de escaneo.

2: Si el objeto en el área de Eden sobrevive a la primera ronda de GC, se copiará al área de supervivencia a través del algoritmo de copia.

La sala de estar se divide en dos mitades (de igual tamaño), y solo se usa la mitad de ellas a la vez.

Si el GC escanea la sala de estar y encuentra objetos basura, se eliminará. Si no es basura, se copiará al otro lado de la sala de estar a través del algoritmo de copia.

3: Cuando el objeto sobrevive a varios GC en el área de vivienda, la edad también aumenta. En este momento, se copiará a la generación anterior a través del algoritmo de replicación.

4: Después de ingresar a la vejez, debido a que la edad es relativamente antigua, el concepto de ser marcado como un objeto de basura también es muy pequeño, por lo que la frecuencia de escaneo de GC para la vejez también se reducirá.

Caso especial: si el objeto es muy grande, ingrese directamente a la generación anterior (el costo de copiar el objeto grande es muy alto y no hay muchos objetos grandes).

Supongo que te gusta

Origin blog.csdn.net/qq_63525426/article/details/131725086
Recomendado
Clasificación