Análisis del modelo de gestión de memoria de máquina virtual

En el proceso de ejecución de un programa Java, la máquina virtual Java divide la memoria administrada por el programa Java en varias áreas de datos diferentes, que se pueden dividir en cinco partes: pila de máquina virtual, montón, área de método, pila de método local, contador de programa , como se muestra en la figura:

pila de máquinas virtuales

La pila de máquina virtual de Java (Java Virtual Machine Stack) es privada para el subproceso y su ciclo de vida es el mismo que el del subproceso. Es decir, cuando se ejecuta cada método, la máquina virtual Java creará sincrónicamente un marco de pila (Stack Frame) 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. El proceso de cada método que se llama hasta que se completa la ejecución corresponde al proceso de un marco de pila desde que se empuja hasta que se coloca en la pila de la máquina virtual. Expliquemos el contenido de la pila de la máquina virtual:

tabla de variables locales

La tabla de variables locales almacena varios tipos de datos básicos de la máquina virtual Java (boolean, byte, char, short, int, float, long, double) y referencia de objeto (tipo de referencia, que no es equivalente al objeto en sí, y puede es un puntero de referencia que apunta a la dirección de inicio del objeto, o puede apuntar a un identificador que representa el objeto u otra ubicación relacionada con el objeto) y el tipo returnAddress (que apunta a la dirección de una instrucción de código de bytes).

El espacio de almacenamiento de estos tipos de datos en la tabla de variables locales está representado por un slot de variable local (Slot), en el que los datos de tipo doble y largo de 64 bits ocuparán dos slots de variables, y el resto de tipos de datos sólo ocuparán uno. El espacio de memoria requerido por la tabla de variables locales se asigna durante la compilación. Al ingresar un método, se determina completamente cuánto espacio de variables locales necesita el método para asignar en el marco de la pila, y el tamaño de la tabla de variables locales no cambiará durante la compilación. el funcionamiento del método. Tenga en cuenta que el "tamaño" mencionado aquí se refiere a la cantidad de ranuras variables, cuánto espacio de memoria usa realmente la máquina virtual (por ejemplo, 1 ranura variable ocupa 32 bits, 64 bits o más) para implementar una ranura variable, que queda totalmente a discreción de la implementación específica de la máquina virtual.

El tipo returnAddress actualmente es raro. Sirve instrucciones de código de bytes jsr, jsr_w y ret, y apunta a la dirección de una instrucción de código de bytes. Aunque long y double se asignan en dos ranuras variables, debido a que están dentro del subproceso, no habrá competencia de datos ni problemas de seguridad del subproceso.

pila de operandos

La pila de operandos (Pila de operandos) también suele denominarse pila de operaciones, que es una pila de último en entrar, primero en salir (Último en entrar, primero en salir, LIFO). Al igual que la tabla de variables locales, la profundidad máxima de la pila de operandos también se escribe en el elemento de datos max_stacks del atributo Código durante la compilación. Cuando un método recién comienza a ejecutarse, la pila de operandos de este método está vacía.Durante la ejecución del método, habrá varias instrucciones de código de bytes para escribir y extraer contenido de la pila de operandos, es decir, la operación popping y Push. Por ejemplo, al realizar operaciones aritméticas, se lleva a cabo empujando la pila de operandos involucrada en la operación en la parte superior de la pila y luego llamando a la instrucción de operación.Por ejemplo, al llamar a otros métodos, los parámetros del método se pasan a través de la pila de operandos . Por ejemplo, como la instrucción de código de bytes de suma de enteros iadd, esta instrucción requiere que los dos elementos más cercanos a la parte superior de la pila en la pila de operandos se hayan almacenado con dos valores int al ejecutar esta instrucción, los dos valores int se extraerá y se agregará, y luego el resultado de la suma se volverá a colocar en la pila.

Escribe un pequeño caso:

 
 

paquete com.coraje; public class DeOperandStack { public static void main(String[] args) { int i = 1; int j = 2; int k = yo + j; } }

En este momento, solo hay un subproceso (principal) en la clase DeOperandStack y las variables en la tabla de variables locales:

Los argumentos predeterminados son la variable 0, por lo que hay cuatro variables locales en este hilo, entonces, ¿cómo usar la pila de operandos para sumar y restar?

Primero inserte la primera constante en la pila, luego almacene la variable No. 1 en la tabla de variables locales, luego inserte la segunda constante en la pila, luego almacene la variable No. 2 en la tabla de variables locales y luego cargue los dos valores ​​​​de la tabla de variables locales 1 y 2 Empuje la pila, extraiga la suma y empuje el resultado en la pila, y luego almacene los datos superiores en la pila en la variable No. 3.

enlace dinámico

Cada marco de pila contiene una referencia al método al que pertenece el marco de pila en el conjunto de constantes de tiempo de ejecución. Esta referencia se mantiene para admitir la vinculación dinámica (enlace dinámico) durante la llamada al método. Sabemos que hay una gran cantidad de referencias simbólicas almacenadas en el grupo de constantes del archivo Class, y la instrucción de llamada al método en el código de bytes usa la referencia simbólica que apunta al método en el grupo de constantes como parámetro. Algunas de estas referencias simbólicas se convertirán en referencias directas durante la fase de carga de la clase o cuando se utilicen por primera vez, esta conversión se denomina resolución estática. La otra parte se convertirá en referencias directas durante cada ejecución, esta parte se denomina vinculación dinámica.

exportar método

Después de que un método comienza a ejecutarse, solo hay dos formas de salir del método:

  • Se encuentra una instrucción de código de bytes devuelta por un método
  • Encontró una excepción

La primera forma es que el motor de ejecución encuentre cualquier instrucción de código de bytes devuelta por cualquier método. En este momento, el valor devuelto puede pasarse al llamador del método de nivel superior (el método que llama al método actual se llama llamador o método de llamada) , el método Si hay un valor de retorno y el tipo del valor de retorno se determinará de acuerdo con la instrucción de retorno del método que se encuentra. Esta forma de salir del método se denomina "Finalización de invocación de método normal".

Otra forma de salir es que se encuentra una excepción durante la ejecución del método y esta excepción no se ha manejado correctamente en el cuerpo del método. Ya sea que se trate de una excepción generada dentro de la máquina virtual Java o una excepción generada mediante el uso de la instrucción de código de bytes athrow en el código, siempre que no se encuentre un controlador de excepciones coincidente en la tabla de excepciones de este método, el método se cerrará. El método se denomina "finalización de invocación de método abrupto (Finalización de invocación de método abrupto)".

Un método que finaliza con una salida de finalización de excepción no proporciona ningún valor de retorno a su llamador de nivel superior. Independientemente del método de salida que se utilice, una vez que el método sale, debe volver a la posición en la que se llamó al método original antes de que el programa pueda continuar ejecutándose. ayudar a restaurar su principal de nivel superior El estado de ejecución del método de llamada.

montón

El montón de Java es un área de memoria compartida por todos los subprocesos y creada cuando se inicia la máquina virtual. El único propósito de esta área de memoria es almacenar instancias de objetos, y "casi" todas las instancias y matrices de objetos en el
mundo de Java asignan memoria aquí.

Desde la perspectiva de la asignación de memoria, varios búferes de asignación privados de subprocesos (Búfer de asignación local de subprocesos, TLAB) se pueden dividir en el montón de Java compartido por todos los subprocesos para mejorar la eficiencia de la asignación de objetos. Sin embargo, no importa desde qué punto de vista, no importa qué tan dividido, no cambiará el carácter común del contenido de almacenamiento en el montón de Java. No importa en qué área, solo puede almacenar instancias de objetos. El propósito de subdividir el montón de Java es solo para reciclar mejor la memoria o asignar memoria más rápido.

área de método

Method Area (Área de método), como el montón de Java, es un área de memoria compartida por cada subproceso. Se utiliza para almacenar datos como información de tipo, constantes, variables estáticas y cachés de código compilados por el compilador instantáneo que han sido cargados por la maquina virtual

Al igual que el montón de Java, no requiere memoria continua y puede elegir un tamaño fijo o ampliable. Incluso puede optar por no implementar la recolección de basura. El objetivo de recuperación de memoria en esta área es principalmente para la recuperación del grupo constante y la descarga de En términos generales, el efecto de recuperación de esta área es relativamente difícil de ser satisfactorio, especialmente la descarga de tipos, las condiciones son bastante duras, pero la recuperación de esta parte del área a veces es necesaria, y la recuperación incompleta de esta área dar lugar a fugas de memoria.

La relación entre área de método, generación permanente y metaespacio

La razón por la que estos tres están juntos es que es fácil confundirlos aquí. Para la máquina virtual Hotspot, el área de métodos es  PermGen(generación permanente) en JDK6 y JDK7, y el área de métodos es  Metaspace(metaespacio) en JDK8. ?

El área del método es la especificación de la JVM y todas las máquinas virtuales deben cumplirla. Máquinas virtuales JVM comunes Hotspot, JRockit (Oracle), J9 (IBM)

El espacio PermGen es la implementación del área de métodos de la máquina virtual HotSpot basada en la especificación JVM, y solo HotSpot tiene el espacio PermGen. Las máquinas virtuales como JRockit (Oracle) y J9 (IBM) tienen áreas de método, pero no hay espacio PermGen.

El espacio PermGen es JDK7 y antes, una implementación de aterrizaje de la máquina virtual HotSpot para el área de métodos, que se eliminó en JDK8.

Metaspace (metaspace) es una nueva implementación del área de métodos de la máquina virtual HotSpot en JDK8 y versiones posteriores.

La generación permanente y el metaespacio se pueden usar para almacenar objetos de larga duración en el montón. La mayor diferencia entre el metaespacio y la generación permanente es que el metaespacio no está en la máquina virtual, sino que usa la memoria local.

informacion de la clase

Cada clase tiene un objeto Class, que se genera durante la compilación y se guarda en un archivo .class con el mismo nombre. Estos objetos Class contienen información detallada, como la clase principal, la interfaz, el constructor, el método y el atributo de este tipo. ClassLoader cargará estos archivos de clase en la JVM cuando el programa se esté ejecutando y se representarán como una clase. objeto en la JVM La JVM utiliza El objeto Clase crea todos los objetos regulares de esta clase, y la información de este objeto se almacena en la información de clase del área de método.

piscina constante

El conjunto de constantes de tiempo de ejecución forma parte del área de métodos. Además de la versión de clase, los campos, los métodos, las interfaces y otra información de descripción en el archivo de clase, también hay una tabla de agrupación de constantes (Tabla de agrupación de constantes), que se utiliza para almacenar varios literales y referencias de símbolos generadas durante la compilación . El contenido se almacenará en el grupo de constantes de tiempo de ejecución en el área de métodos después de cargar la clase . Dado que el grupo de constantes de tiempo de ejecución es parte del área de métodos, naturalmente está limitado por la memoria en el área de métodos. Cuando el grupo de constantes ya no puede solicitar memoria, se lanzará una excepción OutOfMemoryError.

área variable estática

Las variables estáticas también se denominan variables de clase y todas las instancias de la clase son compartidas.Esta área está dedicada a almacenar variables estáticas y bloques estáticos.

Los modificados estáticos se cargan en la memoria cuando se ejecuta la JVM, por lo que no se requiere ninguna clase de instancia.

Las variables estáticas asignan memoria y establecen el valor inicial de las variables de clase en la fase de preparación de la carga de clases. Conceptualmente, la memoria utilizada por estas variables debe asignarse en el área de método, pero debe tenerse en cuenta que el área de método en sí misma es una entrada lógica. JDK 7 y anteriores, cuando HotSpot utiliza la generación permanente para implementar el área de métodos, la implementación se ajusta completamente a este concepto lógico; mientras que en JDK 8 y posteriores, las variables de clase se almacenarán en el almacenamiento dinámico de Java junto con el objeto Class, en este tiempo, "las variables de clase están en el área del método" es completamente una expresión de conceptos lógicos.Con respecto a esta parte, el autor la ha introducido y verificado en la Sección 4.3.1.

contador de programa

El contador de programa (Program Counter Register) es un pequeño espacio de memoria, que puede considerarse como el indicador de número de línea del código de bytes ejecutado por el hilo actual. En el modelo conceptual de la máquina virtual de Java, el intérprete de código de bytes selecciona la siguiente instrucción de código de bytes que se ejecutará cambiando el valor del contador cuando funciona.Es un indicador del flujo de control del programa, bifurcación, bucle, salto, manejo de excepciones , la recuperación de subprocesos y otras funciones básicas deben depender de este contador para completarse.

Dado que los subprocesos múltiples de la máquina virtual Java se realizan cambiando los subprocesos y asignando el tiempo de ejecución del procesador, en un momento dado, un procesador (un núcleo para un procesador multinúcleo) solo ejecutará una instrucción en el subproceso. Por lo tanto, para volver a la posición de ejecución correcta después del cambio de subproceso, cada subproceso debe tener un contador de programa independiente. Los contadores entre subprocesos no se afectan entre sí y se almacenan de forma independiente. Llamamos a este tipo de área de memoria "subproceso privado". .de la memoria.

pila de métodos nativos

Las pilas de métodos nativos (Native Method Stacks) son muy similares a las pilas de máquinas virtuales. La diferencia es que la pila de la máquina virtual sirve a la máquina virtual para ejecutar métodos Java (es decir, códigos de bytes), mientras que la pila de métodos nativos sirve a la máquina virtual. servicio de método local (nativo) utilizado por la máquina.

Explicación: este artículo tiene una extensión limitada, por lo que solo se muestra una parte del contenido de la entrevista. El documento completo de aprendizaje de la entrevista de Java se ha compilado para usted. Si necesita un amigo, envíeme un mensaje privado (necesita) para recibir el aprendizaje de la entrevista de Java y Dachang materiales gratis!

 

 

Supongo que te gusta

Origin blog.csdn.net/m0_67788957/article/details/123737648
Recomendado
Clasificación