Área de memoria de JVM y cambios de datos en la memoria cuando el programa se está ejecutando

Área de memoria JVM

imagen

Área de datos de tiempo de ejecución de JVM

 

Definición: Cuando JVM ejecuta un programa JAVA, divide el área que administra en varias áreas virtuales diferentes para su administración.

JAVA se enorgullece de su mecanismo automático de gestión de memoria. En comparación con la gestión manual de C ++ y los punteros incomprensibles, los programas JAVA son mucho más fáciles de escribir. Por lo tanto, para comprender la JVM en profundidad, primero debemos comprender el concepto de virtualización de memoria.

En JVM, la memoria se divide principalmente en área de montón, pila y método.

Al mismo tiempo, la división desde la perspectiva de los hilos también se puede dividir en el área privada del hilo y el área compartida del hilo.

Área privada de subprocesos: un solo subproceso corresponde a un solo área, y los subprocesos no se molestan entre sí.

Área compartida de subprocesos: compartida por todos los subprocesos y solo hay una copia.

Aquí también está involucrado un concepto de memoria directa. Esta memoria no pertenece al área de datos en tiempo de ejecución de la JVM, pero también se utiliza con frecuencia. Suponiendo que la memoria de la computadora tiene 8 G y la máquina virtual está dividida en 5 G, entonces la memoria directa son los 3 G. En este momento, la JVM puede usar algunas herramientas para usar la memoria directa.

 

Operación del método JAVA y pila de máquinas virtuales

Hilo privado

Pila de máquinas virtuales

La estructura de datos de la pila: estructura de datos FILO

El rol de la pila de la máquina virtual: durante la ejecución de la JVM, almacena el método que se ejecuta en el subproceso actual, así como los datos, las instrucciones y las direcciones de retorno del método.

La pila de la máquina virtual está basada en subprocesos: incluso si solo hay un método main () en un solo subproceso, se ejecuta como subproceso. En el ciclo de vida de un hilo, todos los datos involucrados en el cálculo se empujarán con frecuencia dentro y fuera de la pila. Por lo tanto, el ciclo de vida de la pila de la máquina virtual es el mismo que el del subproceso.

El tamaño de la pila de la máquina virtual: JVM asignará un tamaño fijo de memoria para la pila de la máquina virtual de cada subproceso, generalmente 1M. (Parámetro -Xss). Por lo tanto, el marco de la pila que puede acomodar la pila de la máquina virtual debe ser limitado. Si el marco de la pila se empuja continuamente hacia la pila pero no fuera de la pila, eventualmente hará que se agote el espacio de memoria de la pila de la máquina virtual del subproceso actual. Una típica llamada de función recursiva sin condiciones finales.

image.png

Marco de pila y sus cuatro áreas principales

Cuando se llama a cada método, se genera un marco de pila y se inserta en la pila. Una vez que se llama al método, abre la pila y libera la memoria. El marco de pila se utiliza para almacenar la tabla de variables locales, la pila de operandos, la conexión dinámica, la salida del método y otra información. La denominada memoria de pila primero en entrar, último en salir se refiere a la inserción de marcos de pila.image.png

Tabla de variables locales

Como sugiere el nombre, almacena nuestras variables locales (variables en métodos). Su longitud es de 32 bits, y almacena principalmente los ocho tipos básicos de nuestro JAVA. Generalmente se pueden dejar de 32 bits. 64 bits utiliza bytes altos y bajos, también se pueden almacenar dos. El método general crea un objeto, solo necesitamos crear una dirección de referencia aquí.

Al ingresar un método, la cantidad de espacio de tabla de variables locales que este método necesita asignar en el marco de la pila está completamente determinada. El tamaño de la tabla de variables locales no cambiará durante la ejecución del método. Nota: El tamaño mencionado aquí se refiere al número de ranuras

Pila de operandos

También es una pila de primero en entrar, último en salir que almacena operandos de ejecución de JAVA. La pila de operandos se utiliza para operandos. El elemento de operación puede ser cualquier tipo de datos JAVA. Entonces, cuando se inició el método, la pila de operandos estaba vacía.

Entiendo la pila de operandos como un área de trabajo del motor de ejecución de JVM . Es decir, la pila de operandos será operada cuando se ejecute el método, si el código no se ejecuta, la pila de operandos estará vacía. (Comprensión personal: para cada marco de pila independiente, la pila de operandos es responsable de procesar la lógica como nuestro programa, y ​​la tabla de variables locales almacena datos como una base de datos).

Enlace dinámico

Polimorfismo de características del lenguaje JAVA (posteriormente , se registrará en detalle junto con los capítulos de despacho dinámico y estático que combinan clases y motores de ejecución y llamadas a métodos, y lo describiré brevemente aquí).

En primer lugar, si ve la conexión dinámica, ¿adivina si hay una conexión estática? Sí, la llamada "conexión", en términos simples, se refiere a la llamada del método. Entonces, ¿por qué solo hay una conexión dinámica en nuestro marco de pila, pero no una conexión estática? Debido a que la vinculación estática se determina durante la etapa de carga de la clase, nunca cambiará durante el tiempo de ejecución. El método dinámico no se determina durante la carga de la clase, pero se determina en tiempo de ejecución. Aquí nuevamente involucra métodos virtuales, no virtuales. El concepto de asignación dinámica y estática. Se explicará en detalle en artículos posteriores. Nota: La determinación aquí se refiere a la conversión de referencias simbólicas a referencias directas, es decir, el acceso posterior en tiempo de ejecución no requiere ningún procesamiento y la dirección se puede obtener directamente.

Complete la exportación (dirección de devolución)

Cuando un método comienza a ejecutarse, puede haber dos formas de salir del método:

1. Salida de finalización normal
2. Salida de finalización anormal La salida de finalización
normal significa que el método se completa normalmente y sale sin generar ninguna excepción (incluidas las excepciones de la máquina virtual Java y las excepciones lanzadas a través de la instrucción throw durante la ejecución). Si el método actual se completa normalmente, de acuerdo con la instrucción de código de bytes devuelta por el método actual, puede haber un valor de retorno pasado al llamador del método (el método que lo llamó), o ningún valor de retorno. Si hay un valor de retorno y el tipo de datos del valor de retorno se determinará de acuerdo con las instrucciones de código de bytes devueltas por el método.

Salida de finalización anormal significa que se encuentra una excepción durante la ejecución del método, y esta excepción no se maneja dentro del cuerpo del método, lo que hace que el método salga. Tome el siguiente código como ejemplo:

imagen

imagen

Obviamente, cuando ocurre una excepción en el programa, el contenido de la captura continuará ejecutándose. Se ignorará el contenido después del método.

Ya sea que se trate de una excepción lanzada por la máquina virtual Java o una excepción generada por la instrucción en el código, siempre que no se busque el manejador de excepciones correspondiente en la tabla de excepciones de este método, el método saldrá.
No importa qué método se use para salir, después de que el método salga, debe regresar al lugar donde se llamó al método antes de que el programa pueda continuar. Cuando el método regrese, es posible que deba guardar cierta información en el marco de pila actual para ayúdalo a restaurar su método superior. ( Nivel de llamada ) Estado de ejecución.

El proceso de salida del método es en realidad equivalente a hacer estallar el marco de pila actual, por lo que las operaciones que se pueden realizar son: restaurar la tabla de variables locales y la pila de operandos del método superior, y empujar el valor de retorno ( si lo hay ) en la operación del llamador en la pila de números, ajuste el valor del contador de PC para que apunte a la siguiente instrucción después de la instrucción de llamada al método.
En términos generales, cuando el método sale normalmente, el valor de recuento de PC de la persona que llama se puede utilizar como dirección de retorno y este valor de recuento se puede almacenar en el marco de pila. Cuando el método sale de forma anormal, la dirección de retorno está determinada por la tabla del manejador de excepciones, y esta parte de la información generalmente no se guarda en el marco de la pila.

imagen

El procedimiento es normal

(La dirección del contador del programa de llamada se devuelve al hilo del usuario)

Trilogía:

1. Restaurar la tabla de variables locales y la pila de operandos del método superior

2. Inserte el valor de retorno (si lo hay) en la pila de operandos del marco de pila de la persona que llama.

3. Ajuste el valor del contador del programa para que apunte a una instrucción que sigue a la instrucción de llamada al método.

Excepción del programa

(A través de la tabla de manejo de excepciones en el <marco que no es de pila>) para tratar.

 

Cambio de marco de pila

La pila de la máquina virtual JAVA almacena la tabla de variables locales, la pila de operandos, la conexión dinámica y la salida de finalización. La conexión dinámica y la salida de finalización coinciden con el contador del programa en el marco de la pila para registrar la ejecución de la CPU. Debido a que la CPU se encuentra en un estado de conmutación de alta velocidad en un entorno de subprocesos múltiples, el contador del programa registrará la posición del subproceso esta vez que se ejecuta, y luego completará el corte de salida, y cuando el subproceso agarre la ejecución correctamente la próxima vez, continúe ejecutando el hilo desde la posición del programa count. El enfoque aquí es la colaboración entre la tabla de variables locales y la pila de operandos.

imagen

Aquí, durante la ejecución del código, el flujo de ejecución del programa se desglosa desde la perspectiva del marco de la pila. Diagrama esquemático de apilamiento y estallido.

imagen

 

Contador de programa

Solo ocupa un pequeño espacio de memoria, registra el número de línea actualmente ejecutado por el hilo, y cada hilo es independiente entre sí y no se afecta entre sí.

El contador de programa solo ocupa un pequeño espacio de memoria y principalmente registra la dirección de código de bytes ejecutada por el hilo de su propio hilo. Por ejemplo: bifurcaciones, bucles, saltos, excepciones, recuperación de datos, etc., todo depende del contador del programa. Dado que JAVA es un lenguaje de subprocesos múltiples, cuando el número de subprocesos de la CPU excede el número de núcleos, los subprocesos competirán por los recursos de la CPU en función de los intervalos de tiempo. Si se agota el intervalo de tiempo de un subproceso, o se roban recursos por adelantado por otras razones, entonces el contador de programa de este subproceso debe registrar la siguiente instrucción en ejecución. El contador de programa es el único lugar en la JVM que no es OOM.

 

Pila de métodos nativos

Dado que JVM es una máquina virtual JAVA, hay un proceso completo de ejecución de instrucciones en su interior. Por lo tanto, debe usar el contador del programa cuando ejecute el método JAVA.

En ese momento, el método de la pila de métodos nativos (modificador nativo) no fue ejecutado por la JVM, por lo que no se necesitó el contador del programa para ejecutarlo. Esto se debe a que el sistema operativo también tiene un contador de programa, que registra la dirección de ejecución del código en el área de método local. Entonces, si se ejecuta el método en la pila de métodos local, se mostrará el contador del programa en la pila de la máquina virtual (Indefinido).

El impacto de la ejecución del marco de pila en la memoria

Proceso de descompilación

Desmontar la clase javap -c XXXX.class

URL de visualización de código de bytes: https://cloud.tencent.com/developer/article/1333540 

Uso de herramientas de descompilación

1. Busque el archivo .class después de que el programa esté precompilado

2. Mantenga presionada la tecla Mayús + tecla derecha para abrir el comando de DOS.

3. Utilice el comando javap -c XXXX.class para desensamblar el archivo de clase, el resultado de la ejecución es el siguiente.

imagen

 

imagen

Proceso:

0: x = 1 en la pila de operandos

1: x = 1 en la tabla de variables locales

2: y = 2 entra en la pila de operandos

3: y = 2 entra en la tabla de variables locales

4: z = 1 + 2 entra en la pila de operandos (no hay más instrucciones para ejecutar 1 + 2)

5: z = 3 en la tabla de variables locales

6: x = 1 entra en la pila de operandos de la tabla de variables locales

7: y = 2 ingresa a la pila de operandos desde la tabla de variables locales

8: Ejecutar x + y (en este momento, la pila de operandos entregará el valor al motor de ejecución para su ejecución y devolverá el resultado de la ejecución a la pila de operandos)

9: z = 3 entra en la pila de operandos desde la tabla de variables locales

10: El resultado de ejecutar (x + y) * z (En este momento, la pila de operandos pasará el valor al motor de ejecución para su ejecución, y el resultado de la ejecución se devolverá a la pila de operandos)

11: El resultado del cálculo h entra en la tabla de variables locales

13: h = 9 ingrese la pila de operandos de la tabla de variables locales (la discontinuidad aquí puede resultar en un resultado mayor, use dos ubicaciones para almacenar)

15: h = 9 regresa de la pila de operandos al marco de pila de llamada

 

En la JVM, si se menciona que la ejecución basada en interpretación se basa en el motor de ejecución basado en pila. El motor basado en pila se refiere a la pila de operandos.

 

El significado de la pila de métodos locales

La función de la pila de métodos local es similar a la de la máquina virtual. La máquina virtual JAVA se utiliza para administrar las llamadas de funciones JAVA. La pila de métodos locales se utiliza para administrar las llamadas a funciones del método local. Pero el método nativo no está escrito en JAVA. Por ejemplo, el método Object.hashcode ().

La pila de métodos nativos sirve específicamente a los métodos modificados por el modificador nativo. Incluso es posible combinar la pila de métodos locales y la pila de máquinas virtuales en una sola. No hay ningún requisito obligatorio en la especificación de la máquina virtual, y cada versión de la máquina virtual es de implementación gratuita. HotSpot combina las dos regiones en una.

 

Compartir hilo

Área de métodos

El área de método es similar al espacio de almacenamiento dinámico y ambas son áreas de memoria compartida. Entonces, el área de método es compartida por subprocesos. Si se carga una clase en la JVM, ambos subprocesos desean acceder a la misma información en el área de métodos. En este momento, solo se permite que un subproceso lo cargue, y los otros subprocesos deben esperar (la JVM implementa la función de bloqueo por sí misma, y ​​el modo de marcador de posición retrasado del modo singleton que se aprenderá en el futuro se aprovechará de esto ). La generación permanente y el metaespacio que mencionamos a menudo se refieren a la realización del área de método. Con la aparición de la generación permanente, los implementadores de HotSpot quieren que sea similar a la memoria de pila, de modo que el recolector de basura pueda administrar áreas de métodos como la memoria de pila. Sin embargo, el tamaño de la generación permanente tiene un límite superior, lo que hace que el área del método sea más propensa a problemas OOM. Después de JDK6, los desarrolladores han invertido mucho esfuerzo, abandonando la generación permanente y usando metaspace en su lugar. Para la realización del metaespacio, los requisitos de la máquina virtual son muy relajados. No es obligatorio establecer el límite superior de memoria, e incluso es posible que no se implemente la recolección de basura. Evite pérdidas de memoria causadas por la recuperación incompleta de objetos inútiles en esta área (¡no lo manipule! ¡No lo haga!).

Referencia simbólica

En cuanto a la cita simbólica y la cita directa, muchos blogs y libros hablan de abstracción. Déjame hablar sobre mi comprensión.

En primer lugar, el grupo de constantes de clase no es nuestro grupo de constantes de tiempo de ejecución. El pool de constantes de clase es una parte de la memoria que existe en el archivo de clase, su función es transferir la información relevante de la clase a nuestra área de métodos durante el proceso de carga de clases. En el proceso de conexión dinámica, accedemos al método en el área de método específico por la referencia directa en el marco de la pila. Pero, ¿puede el método del marco de pila acceder a la información de clase del grupo de constantes de clase?

Ciertamente no, porque en este momento los datos aún no han entrado en el área de datos de tiempo de ejecución de JVM. Luego, cuando los datos en el grupo de constantes de clase se carguen en nuestra área de método, el área de método también tiene una referencia para identificar específicamente el método en el grupo de constantes de clase. Cuando los datos del grupo de constantes de clase se cargan en el área de método a través de una referencia simbólica, la referencia simbólica se convierte en una referencia directa, es decir, se asigna realmente la dirección de un área de datos en tiempo de ejecución. Proporcione acceso directo a las referencias en nuestra área de datos en tiempo de ejecución. (La referencia de símbolo puede ser cualquier tipo de literal, pero no necesitamos conocer las reglas de nomenclatura específicas. Siempre que la referencia de símbolo pueda ayudarnos a cargar con precisión los datos de clase del grupo de constantes de clase en el área de datos de tiempo de ejecución).

Impromptu: el grupo constante de clases es como un traficante de personas. Los diferentes tipos de información son como los niños que captan. Los traficantes de personas no están de humor para recordar sus nombres uno por uno. Simplemente llame a este grupo de niños No. 1, No. 2, No. 3 (referencia de símbolo) ... Cuando alguien vino a comprar un niño, el traficante dijo: "¡Oye! Número 2, ven aquí". El comprador miró mucho al niño, así que compró al niño. Pero si el niño es suyo, todavía no se le puede llamar número 2, por lo que el niño tiene el nombre de Zhang San. Más tarde, cuando los miembros de la familia llamaban a sus hijos, llamaban directamente a Zhang San (cita directamente)

Cita directa

Una referencia directa es una referencia que puede acceder a la dirección real. (¡Zhang San, Zhang San te está llamando!)

Literal

El llamado literal es el valor de la variable. Las cantidades literales solo pueden aparecer como "=" rvalues. "=" El lvalue se llama constante o variable.

 

int i = 0; // i es una variable, 0 es un literal

final int a = 10; // a es una constante, 10 es un literal

string str = "hellow world"; // str es una variable, hello world es un literal

 

Grupo constante y grupo constante en tiempo de ejecución

Cuando la clase se carga en la memoria, la JVM cargará el contenido del grupo de constantes de archivo de clase (el grupo de constantes de archivo está en el archivo de clase y aún no ha alcanzado el área de métodos) en el grupo de constantes de tiempo de ejecución. En la fase de análisis, la JVM resolverá las referencias simbólicas en referencias directas (el valor de índice del objeto).

Por ejemplo: cuando una constante de cadena en una clase está en el archivo de clase, se almacena en la constante del grupo de archivos de clase; después de que la JVM carga la clase, la JVM cargará la constante desde el grupo de constantes del archivo de clase al grupo de constantes de JVM . Y en el proceso de análisis, especifique el valor de índice del objeto de cadena. El grupo de constantes de tiempo de ejecución también se comparte y varias clases comparten un grupo de constantes de tiempo de ejecución. Varias cadenas de la misma cantidad literal almacenadas en el grupo de constantes del archivo clss solo se almacenarán en el grupo de constantes de tiempo de ejecución.

Hay muchos conceptos de piscinas constantes. Por ejemplo, grupo de constantes de clase, grupo de constantes de cadena, grupo de constantes de tiempo de ejecución.

La definición anterior de la especificación de la máquina virtual solo pertenece al área de métodos y no especifica la implementación del fabricante de la máquina virtual.

Estrictamente hablando, el grupo de constantes en Java se divide en dos formas: grupo de constantes estáticas y grupo de constantes de tiempo de ejecución.

1) El denominado grupo de constantes estáticas es el grupo de constantes en el archivo * .class. El grupo de constantes en el archivo de clase no solo contiene el literal de cadena (número), sino que también contiene la información de la clase y el método, que ocupa la mayor parte del espacio del archivo de clase.

     2) El grupo de constantes de tiempo de ejecución es que después de que la máquina virtual jvm completa la operación de carga de clases, carga el grupo de constantes en el archivo de clase en la memoria y lo guarda en el área de métodos. El grupo de constantes que solemos decir se refiere al método The grupo de constantes de tiempo de ejecución en la zona.

El grupo de constantes de tiempo de ejecución se movió a la memoria del montón después de JDK1 y 7. El movimiento aquí se refiere al espacio físico, pero lógicamente sigue siendo un área de método. (El área de método es una partición lógica)

Generación permanente y metaespacio

Antes de JDK1.7, se usaba la generación permanente. Los datos del grupo de constantes de cadena se mueven a la memoria del montón. Pero hacerlo puede causar fácilmente problemas de rendimiento y desbordamiento de la memoria. Se puede especificar el tamaño de la generación permanente, pero debido a que la generación permanente todavía ocupa la memoria del área de datos en tiempo de ejecución, una configuración de tamaño pequeño puede causar fácilmente un desbordamiento permanente de la generación. generación y hacer más frecuente la recolección de basura, lo que se traduce en una reducción de la eficiencia. Por tanto, la teoría de la generación permanente se elimina en el metaespacio posterior. Metaspace usa memoria local (memoria directa), por lo que, en teoría, el metaespacio solo se ve afectado por el tamaño de la memoria local.

Expansión: cuando Orcle está harto de que BEA adquiera la propiedad de la máquina virtual JRockit, está listo para trasladar las excelentes características de JRockit a HotSpot. Pero JRockit no tiene el concepto de generación permanente, por lo que ha traído un gran sufrimiento a la fusión. Después de JDK6, el equipo de desarrollo de HoySpot está decidido a trabajar duro para eliminar el concepto de generación permanente y cambiar gradualmente al metaespacio.

Metaspace ya no ocupa la memoria de nuestra área de datos en tiempo de ejecución, sino que se coloca en la memoria del montón. Es decir, siempre que la memoria total de la máquina sea lo suficientemente grande, la JVM no desbordará la memoria del área de método. Por supuesto, el uso ilimitado seguirá causando la muerte del sistema operativo.

En términos simples, tanto la generación permanente como el metaespacio se refieren a áreas de métodos.

Montón

El montón ocupa la mayor parte del espacio de memoria en la JVM, y casi todos los objetos que solicitamos se almacenan en la memoria del montón. En la recolección de basura solemos decir, el objeto de operación es el montón.

El espacio de pila generalmente se solicita cuando se inicia el programa, pero es posible que no se haya agotado por completo. El montón generalmente está configurado para ser escalable. (El montón normal es generalmente muy pequeño cuando se inicia el programa. A medida que los objetos se crean continuamente, continúan aumentando y expandiéndose).

Con la creación frecuente de objetos, se ocupa cada vez más espacio en el montón, y los objetos que ya no se utilizan deben recuperarse de vez en cuando. Esto se llama GC (recolección de basura) en Java.

Entonces, ¿se asigna un objeto en el montón o en la pila cuando se crea?

    Para tipos básicos: si es un objeto (variable local) con un tipo de datos declarado en el cuerpo del método, será asignado en la pila. Si es en otros casos (como variables miembro), se asignará en el montón.

Para tipos de referencia: el nuevo objeto se creará en el montón y la referencia se almacenará en la tabla de variables locales en la pila de la máquina virtual.

Extensión: Entonces, ¿el nuevo objeto debe estar en el montón?

La descripción en la "Especificación de la máquina virtual de Java" es: "Todas las instancias de objetos y las matrices deben asignarse en el montón". Con el avance de la tecnología de compilación justo a tiempo, especialmente la tecnología de análisis de escape cada vez más poderosa, la asignación de pilas y los métodos de optimización de reemplazo escalar también han hecho que esta afirmación sea menos absoluta.

 

Memoria fuera del montón (memoria directa)

Cuando la JVM se está ejecutando, solicitará una gran cantidad de memoria del sistema operativo para el almacenamiento de datos. Por ejemplo, la pila de la máquina virtual, el área del método local y el contador del programa. Este bloque se llama área de pila. La memoria restante del sistema operativo también es la memoria fuera del montón.

No es parte del área de datos cuando la máquina virtual está en ejecución. Sin embargo, está limitado por la memoria total de la máquina. Entonces también producirá OOM,

 

resumen:

1. La memoria directa utiliza principalmente la memoria solicitada por DirectByteBuffer, puede usar el parámetro "MaxDirectMemorySize" para limitar el tamaño (de lo contrario, esto no se detendrá hasta que se bloquee la ventana)

2. La memoria fuera del montón se puede solicitar directamente a través de Inseguro u otros medios JNI. Las pérdidas de memoria fuera del montón son muy graves. La resolución de problemas es difícil, el impacto es grande e incluso el host muere.

Supongo que te gusta

Origin blog.csdn.net/weixin_47184173/article/details/109550397
Recomendado
Clasificación