(Comprensión en profundidad de la máquina virtual Java) Un artículo lo lleva a conocer las diversas áreas de la memoria de la máquina virtual Java

I. Introducción

Para los desarrolladores que se dedican al desarrollo de programas C y C ++, en el campo de la gestión de la memoria, son tanto el "emperador" con la máxima autoridad como la gente trabajadora que se dedica al trabajo más básico: ambos tienen la "propiedad" de cada objeto y Responsable del mantenimiento de la vida de cada sujeto de principio a fin.

Para los programadores de Java,Con la ayuda del mecanismo automático de administración de memoria de la máquina virtual, ya no es necesario escribir códigos emparejados de eliminación / liberación para cada nueva operación, y las pérdidas de memoria y los problemas de desbordamiento de memoria no son propensos a ocurrir.Parece que todo está bien cuando la máquina virtual administra la memoria.

Sin embargo, es precisamente porque los programadores de Java dan el poder de controlar la memoria a la máquina virtual de Java. Una vez que ocurren las pérdidas de memoria y los problemas de desbordamiento, si no comprende cómo la máquina virtual usa la memoria, la solución de problemas y la corrección del problema serán Conviértete en un trabajo extremadamente difícil.

Dos, área de datos en tiempo de ejecución

La máquina virtual Java divide la memoria que administra en varias áreas de datos diferentes durante la ejecución del programa Java. Estas áreas tienen sus propios propósitos, así como el momento de creación y destrucción. Algunas áreas siempre existen cuando se inicia el proceso de la máquina virtual, y algunas áreas se crean y destruyen según el inicio y el final del hilo del usuario.
De acuerdo con la "Especificación de la máquina virtual Java", la memoria administrada por la máquina virtual Java incluirá las siguientes áreas de datos en tiempo de ejecución:

Inserte la descripción de la imagen aquí

Tres, el contador del programa

El registro de contador de programa es un pequeño espacio de memoria, que puede considerarse como un 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 Java, cuando el intérprete de código de bytes funciona, selecciona la siguiente instrucción de código de bytes a ejecutar cambiando el valor de este contador, es un indicador del flujo de control del programa, bifurcaciones, bucles y saltos. Las funciones básicas como el manejo de excepciones, la recuperación de subprocesos, etc., deben depender de este contador para completarse.

Dado que el subproceso múltiple de la máquina virtual Java se realiza mediante la conmutación de subprocesos y la asignación del tiempo de ejecución del procesador a su vez, en cualquier momento determinado, un procesador (para un procesador de múltiples núcleos es un núcleo) solo ejecutará Instrucciones en el hilo
Por lo tanto, para restaurar a la posición de ejecución correcta después del cambio de hilo, cada hilo necesita tener un contador de programa independiente. Los contadores entre los hilos no se afectan entre sí y se almacenan de forma independiente.Este tipo de área de memoria es la memoria "privada de subprocesos"

Si el hilo está ejecutando un método Java, este contador registra la dirección de la instrucción de código de bytes de la máquina virtual en ejecución; si el hilo está ejecutando un método nativo, el valor del contador debe estar indefinido. Esta área de memoria es la única sin ninguna disposición en la "Especificación de la máquina virtual Java" en el OutOfMemoryErrorcontexto regional.

Cuarto, la pila de máquinas virtuales Java

Al igual que el contador de programa, Java Virtual Machine Stack (Java Virtual Machine Stack) también es un subproceso privado y su ciclo de vida es el mismo que el de un subproceso.
La pila de la máquina virtual describe el modelo de memoria de subprocesos de ejecución del método Java: Cuando se ejecuta cada método, la máquina virtual Java creará sincrónicamente un marco de pila (marco de pila) para almacenar información como tablas de variables locales, pilas de operandos, conexiones dinámicas y salidas de métodos.El proceso de cada método que se llama hasta que se completa la ejecución corresponde al proceso de un marco de pila en la pila de la máquina virtual de la pila a la pila

La gente suele dividir el área de memoria de Java en memoria de pila (Heap) y memoria de pila (Stack) en general. Este método de división hereda directamente la estructura de diseño de memoria de los programas tradicionales C y C ++, y parece un poco tosco en el lenguaje Java. La división del área de memoria real es más complicada que esto.

Sin embargo, la popularidad de esta división muestra indirectamente que las áreas que más preocupan a los programadores y que están más estrechamente relacionadas con la asignación de memoria de objetos son el "montón" y la "pila". entre ellos,La "pila" generalmente se refiere a la pila de la máquina virtual mencionada aquí, o más a menudo solo a la parte de la tabla de variables locales de la pila de la máquina virtual.

La tabla de variables locales almacena los tipos de datos básicos (booleano, byte, char, short, int, float, long, double) y referencias de objeto (tipo de referencia, que no es equivalente al objeto en sí, que se puede conocer en tiempo de compilación). Es un puntero de referencia a la dirección inicial del objeto, o puede apuntar a un identificador que representa el objeto u otras ubicaciones relacionadas con este objeto) y returnAddresstipo (apuntando 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 una ranura de variable local (ranura), de la cual los datos dobles y de 64 bits de longitud ocuparán dos ranuras de variables, y los tipos de datos restantes solo ocuparán una.
El espacio de memoria requerido por la tabla de variables locales se asigna durante la compilación. Al ingresar un método, la cantidad de espacio de variables locales que este método necesita asignar en el marco de la pila se determina por completo y el tamaño de la tabla de variables locales no se cambiará durante la ejecución 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 utiliza realmente la máquina virtual (por ejemplo, según una ranura variable, ocupa 32 bits, 64 bits o más) para realizar una ranura variable , Esto está completamente determinado por la implementación de la máquina virtual específica.

En la "Especificación de la máquina virtual Java", se especifican dos tipos de condiciones anormales para esta área de memoria: si la profundidad de la pila solicitada por el subproceso es mayor que la profundidad permitida por la máquina virtual, se lanzará StackOverflowErroruna excepción; si la capacidad de la pila de la máquina virtual Java se puede expandir dinámicamente, cuando Cuando se expande la pila, se lanzará OutOfMemoryErroruna excepción si no hay suficiente memoria disponible .

Cinco, la pila de métodos locales

Las pilas de métodos nativos (Native Method Stacks) y las pilas de máquinas virtuales desempeñan funciones muy similares. La diferencia es que la pila de máquinas virtuales sirve a la máquina virtual para ejecutar métodos Java (es decir, código de bytes), mientras que la pila de métodos nativos sirve a la máquina virtual. Servicio de método nativo utilizado por la máquina

La "Especificación de máquina virtual Java" no tiene disposiciones obligatorias sobre el idioma, el uso y la estructura de datos de los métodos en la pila de métodos local. Por lo tanto, la máquina virtual específica puede implementarla libremente según sea necesario, e incluso algunas máquinas virtuales Java (como Hot-Spot Máquina virtual) combina directamente la pila de métodos locales y la pila de máquinas virtuales en una. Al igual que la pila de la máquina virtual, la pila del método local también arrojará excepciones StackOverflowError y OutOfMemoryError respectivamente cuando la profundidad de la pila se desborde o la expansión de la pila falle.

Seis, montón de Java

Para las aplicaciones Java, Java Heap es la mayor parte de la memoria administrada por la máquina virtual.
El montón de Java es un área de memoria compartida por todos los subprocesos y se crea 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 de objetos en el mundo Java asignan memoria aquí.

La descripción del montón de Java en la "Especificación de la máquina virtual de Java" es: "Todas las instancias y matrices de objetos deben asignarse en el montón", y el" casi "escrito por el autor aquí significa que desde una perspectiva de implementación, con el desarrollo del lenguaje Java, ahora podemos ver algunos signos de que el soporte para tipos de valor puede aparecer en el futuro, incluso si solo lo consideramos ahora, debido a la tecnología de compilación justo a tiempo. El progreso de, especialmente la tecnología de análisis de escape cada vez más poderosa, la asignación de pila, los métodos de optimización de reemplazo escalar [ilustración] han provocado que se produzcan algunos cambios sutiles de manera silenciosa, de modo que las instancias de objetos Java se asignan en el montón y gradualmente se vuelven menos absolutas .

El montón de Java es un área de memoria administrada por el recolector de basura, por lo que también se denomina "montón de GC" en algunos materiales (Garbage Collected Heap, afortunadamente, no se traduce como "montón de basura" en China). Desde la perspectiva de la recuperación de la memoria, dado que la mayoría de los recolectores de basura modernos están diseñados sobre la base de la teoría de la recolección generacional, a menudo hay "nueva generación", "vieja generación", "generación permanente", "espacio del Edén" y "De Survivor" en el montón de Java. Sustantivos como “espacio” y “Al espacio de sobreviviente”. Aquí, el autor quisiera explicar que estas divisiones son solo las características comunes o estilos de diseño de algunos recolectores de basura, no el diseño de memoria inherente de una máquina virtual Java. No es una división más detallada del montón de Java en la "Especificación de la máquina virtual de Java".
Muchos materiales suelen decir algo como "La memoria de pila de la máquina virtual Java se divide en generación joven, generación anterior, generación permanente, Eden, Survivor ...". Hace diez años (con la aparición del recolector G1 como demarcación), como la máquina virtual HotSpot principal absoluta de la industria, todos sus recolectores de basura internos se diseñaron en base a la "generación clásica", lo que requería la combinación de recolectores de nueva y vieja generación. En este contexto, la declaración anterior no es demasiado ambigua. Pero hoy la tecnología de recolectores de basura no es la misma que hace diez años. También hay nuevos recolectores de basura que no adoptan el diseño generacional en HotSpot. Hay muchos puntos a discutir de acuerdo con la formulación anterior.

Desde la perspectiva de la asignación de memoria, el montón de Java compartido por todos los subprocesos se puede dividir en varios búferes de asignación de subprocesos privados (búfer de asignación local de subprocesos, TLAB) para mejorar la eficiencia de la asignación de objetos. Sin embargo, no importa desde cualquier ángulo, no importa qué tan dividido esté, no cambiará la similitud de los contenidos del 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 un mejor reciclaje. Memoria o asignar memoria más rápido. En este capítulo, solo discutimos el rol del área de memoria.Los detalles de la asignación y recuperación de las áreas mencionadas en el montón de Java serán el tema del próximo capítulo.

De acuerdo con la "Especificación de máquina virtual Java",El montón de Java puede estar en un espacio de memoria físicamente no contiguo, pero lógicamente debe considerarse contiguo. Es como si usamos espacio en disco para almacenar archivos, y no requiere que todos los archivos se almacenen continuamente.. Pero para objetos grandes (normalmente objetos de matriz), es probable que la mayoría de las implementaciones de máquinas virtuales requieran espacio de memoria contiguo por motivos de simplicidad y eficiencia de almacenamiento.

El montón de Java se puede implementar como un tamaño fijo o expandible, pero las máquinas virtuales Java principales actuales se implementan todas de acuerdo con la escalabilidad (establecida por los parámetros -Xmx y -Xms). Si no hay memoria en el montón de Java para completar la asignación de la instancia y el montón ya no se puede expandir, la máquina virtual Java generará una excepción OutOfMemoryError.

Siete, área de método

El área de métodos, como el montón de Java, es un área de memoria compartida por cada hilo. Se utiliza para almacenar datos como información de tipo, constantes, variables estáticas y caché de código compilado por el compilador JIT que ha sido cargado por la máquina virtual.. Aunque la "Especificación de máquina virtual de Java" describe el área de método como una parte lógica del montón, tiene un alias llamado "No montón" para distinguirlo del montón de Java.

Hablando del área de métodos, debo mencionarEl concepto de generación permanente, especialmente antes de JDK 8, muchos programadores de Java están acostumbrados a desarrollar e implementar programas en la máquina virtual HotSpot. Mucha gente prefiere llamar al área de métodos "Generación permanente", o las dos Confundir

En esencia, los dos no son equivalentes, porque el equipo de diseño de la máquina virtual HotSpot en ese momento eligió extender el diseño generacional del recolector al área de métodos, o usar la generación permanente para implementar el área de métodos, lo que hace que HotSpot sea basura. El recolector puede administrar esta parte de la memoria como el montón de Java, ahorrando el trabajo de escribir código de administración de memoria específicamente para el área de métodos.

Pero para otras implementaciones de máquinas virtuales, como BEA JRockit, IBM J9, etc., no existe el concepto de generación permanente. En principio, la forma de implementar el área de método pertenece a los detalles de implementación de la máquina virtual, que no se rige por la "Especificación de máquina virtual de Java" y no requiere uniformidad. Pero mirando hacia atrás ahora, la decisión de usar la generación permanente para implementar el área de método no fue una buena idea. Este diseño hizo que las aplicaciones Java fueran más propensas a encontrar problemas de desbordamiento de memoria (la generación permanente tiene el límite superior de -XX: MaxPermSize, incluso si no La configuración también tiene un tamaño predeterminado, y siempre que J9 y JRockit no toquen el límite superior de la memoria disponible del proceso, como el límite de 4GB en un sistema de 32 bits, no habrá problemas), y hay muy pocos métodos (por ejemplo String::intern()) debido a la generación permanente. Esto conduce a diferentes rendimientos en diferentes máquinas virtuales. Después de que Oracle adquirió BEA y obtuvo la propiedad de JRockit, estaba listo para migrar las excelentes características de JRockit, como la herramienta de administración Java Mission Control, a la máquina virtual HotSpot, pero enfrentó muchas dificultades debido a las diferencias en el área de métodos entre los dos. Teniendo en cuenta el desarrollo futuro de HotSpot, en JDK6, el equipo de desarrollo de HotSpot abandonó la generación permanente y gradualmente cambió al plan de usar memoria nativa para implementar el área de métodos.Por el HotSpot de JDK 7, ya colocó el original en la generación permanente. El grupo de constantes de cadena, las variables estáticas, etc. se eliminaron, y en JDK 8 finalmente se abandonó por completo el concepto de generación permanente, en su lugar se utilizó el metaespacio implementado en la memoria local como JRockit y J9. El contenido restante (principalmente información de tipo) de la generación permanente en 7 se mueve todo al metaespacio.

La "Especificación de máquina virtual de Java" tiene restricciones muy flexibles en el área de métodos. Además de las mismas que el montón de Java, no requiere memoria contigua y puede elegir un tamaño fijo o expandible. Incluso puede optar por no implementar la recolección de basura.. En términos relativos, la recolección de basura es relativamente rara en esta área.Sin embargo, no es que los datos ingresen al área del método como "permanentes" como el nombre de la generación permanente. El objetivo de la recuperación de la memoria en esta área es principalmente para la recuperación del pool constante y la descarga de tipos. En general, el efecto de recuperación de esta área es relativamente difícil y satisfactorio. Especialmente para la descarga de tipos, las condiciones son bastante duras, pero la recuperación de esta parte del área es a veces De hecho es necesario. En la lista de errores de la compañía Sun anterior, varios errores graves que han aparecido se deben a que la versión baja de la máquina virtual HotSpot no recupera completamente esta área, lo que provoca pérdidas de memoria. De acuerdo con la "Especificación de máquina virtual de Java", si el área de método no puede cumplir con los nuevos requisitos de asignación de memoria, se lanzará OutOfMemoryErroruna excepción.

Ocho, grupo constante de tiempo de ejecución

El grupo de constantes de tiempo de ejecución es parte del área de métodos. Además de la información de descripción de la versión de la clase, los campos, los métodos y las interfaces en el archivo de la clase, hay otra pieza de información que es la tabla de pool constante, que se utiliza para almacenar varias referencias literales y simbólicas generadas durante la compilación. El contenido se almacenará en el grupo de constantes de tiempo de ejecución en el área de método después de que se cargue la clase.

La máquina virtual Java tiene regulaciones estrictas sobre el formato de cada parte del archivo de clase (incluido naturalmente el grupo constante). Por ejemplo, el tipo de datos que se usa para almacenar cada byte debe cumplir con los requisitos de la especificación antes de que la máquina virtual pueda reconocerlos, cargarlos y ejecutarlos. , Pero para el grupo de constantes de tiempo de ejecución, la "Especificación de máquina virtual de Java" no establece ningún requisito detallado. Las máquinas virtuales implementadas por diferentes proveedores pueden implementar esta área de memoria de acuerdo con sus propias necesidades, pero en términos generales, excepto Classpor la descripción en el archivo guardado Además de las referencias simbólicas, las referencias directas traducidas de las referencias simbólicas también se almacenan en el grupo de constantes de tiempo de ejecución

Otra característica importante del grupo de constantes en tiempo de ejecución en comparación con el grupo de constantes de archivo de clase es que es dinámico. El lenguaje Java no requiere que se generen constantes solo durante la compilación, es decir, se puede ingresar el contenido del grupo de constantes que no está preestablecido en el archivo de clase. Grupo de constantes de tiempo de ejecución del área de método, también puede poner nuevas constantes en el grupo durante el tiempo de ejecución.Esta característica es utilizada por los desarrolladores con más frecuencia en el intern()método de la clase String . Dado que el grupo de constantes de tiempo de ejecución es parte del área de método, naturalmente está limitado por la memoria del área de método. Cuando el grupo de constantes ya no pueda solicitar memoria, se lanzará OutOfMemoryErroruna excepción.

Nueve, memoria directa

La memoria directa no es parte del área de datos del tiempo de ejecución de la máquina virtual, ni es el área de memoria definida en la "Especificación de la máquina virtual Java". Pero esta parte de la memoria también se usa con frecuencia, y también puede causar OutOfMemoryErrorexcepciones, por lo que lo explicaremos aquí.

En JDK 1.4, se agregó recientemente la clase NIO (Nueva entrada / salida) y se introdujo un método de E / S basado en el canal y el búfer. Puede usar la biblioteca de funciones nativas para asignar directamente la memoria fuera del montón y luego Opere utilizando un objeto DirectByteBuffer almacenado en el montón de Java como referencia a esta memoria. Esto puede mejorar significativamente el rendimiento en algunos escenarios porque evita copiar datos entre el montón de Java y el montón nativo.

Obviamente, la asignación de memoria nativa directa no estará limitada por el tamaño del montón de Java, pero dado que es memoria, definitivamente se verá afectada por el tamaño de la memoria nativa total (incluida la memoria física, la partición SWAP o el archivo de paginación) y el espacio de direccionamiento del procesador. Al configurar los parámetros de la máquina virtual, los administradores generales del servidor establecerán -Xmx y otra información de parámetros de acuerdo con la memoria real, pero a menudo ignoran la memoria directa, lo que hace que la suma de cada área de memoria sea mayor que el límite de memoria física (incluido el nivel físico y del sistema operativo Límite), lo que provoca una OutOfMemoryErrorexcepción durante la expansión dinámica .

Supongo que te gusta

Origin blog.csdn.net/nanhuaibeian/article/details/109070608
Recomendado
Clasificación