1. Área de memoria de la JVM y diseño de la memoria de objetos

1. Área de datos de tiempo de ejecución

El diagrama de área de memoria de la JVM es el siguiente:

Mapa de área de memoria JVM

1.1, contador de programa

El registro de contador de programa es un pequeño espacio de memoria, que puede verse 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 [1], 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 y bucles. Las funciones básicas como salto, manejo de excepciones y recuperación de subprocesos 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 (porque un procesador de múltiples núcleos es un núcleo) solo ejecutará una instrucción en la amenaza. Por lo tanto, para restaurar a la posición de ejecución correcta después de que se cambia el subproceso, cada subproceso debe tener un contador de programa independiente. Los contadores entre los subprocesos no se afectan entre sí y se almacenan de forma independiente. A este tipo de área de memoria lo llamamos " hilo privado "Memoria. 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 que se está ejecutando; si el hilo está ejecutando un método nativo, el valor del contador debe estar indefinido. Esta área de memoria es la única área que no especifica ninguna condición de OutOfMemoryError en la "Especificación de máquina virtual de Java".

1.2, pila de máquinas virtuales

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 para almacenar tablas de variables locales, pilas de operandos, conexiones dinámicas y métodos. Exportación y otra información. El proceso desde el momento en que se llama a cada método hasta la finalización de la ejecución corresponde al proceso de un marco de pila desde que se empuja hasta que aparece en la pila de la máquina virtual.

El espacio de almacenamiento de estos tipos de datos en la tabla de variables locales está representado por una ranura de variable local (ranura), donde 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 completamente 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, la cantidad de espacio de memoria que realmente usa la máquina virtual (por ejemplo, de acuerdo con una ranura variable que 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á una excepción StackOverflowError; si el Java La capacidad de la pila de la máquina virtual se puede expandir dinámicamente. Cuando se expande la pila, arrojará OutOfMemoryError si no hay suficiente memoria disponible.

1.3, pila de métodos locales

Las pilas de métodos nativos (pilas de métodos nativos) 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 la máquina virtual Java" no tiene disposiciones obligatorias sobre el lenguaje, 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 The virtual machine) 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 arroja excepciones StackOverflowError y OutOfMemoryError respectivamente cuando la profundidad de la pila se desborda o falla la expansión de la pila.

1.4, 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 de objetos y las matrices deben asignarse en el montón", y el autor aquí "casi" significa que, desde el punto de vista de la implementación, con el 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 al 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, los métodos de optimización de asignación en la pila y el reemplazo escalar han llevado a que algunos cambios sutiles ocurran silenciosamente, por lo 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 le llama "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 superviviente" aparecerán repetidamente en los capítulos siguientes de este libro. Aquí quiero explicar primero que estas divisiones son solo algunas de las características comunes o estilos de diseño de los recolectores de basura. no el diseño de memoria inherente implementado por una determinada máquina virtual de Java, ni la 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 límite), 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 que los recolectores de nueva y vieja generación coincidieran en En este contexto, la declaración anterior no es demasiado ambigua. Pero hoy, la tecnología del recolector de basura no es la misma que hace diez años, y HotSpot también ha publicado Hay un nuevo recolector de basura que no adopta un diseño generacional, y luego 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 privados de subprocesos (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 cuál sea la división, no cambiará la similitud del contenido de almacenamiento en el montón de Java. No importa en qué área, el almacenamiento solo puede ser la instancia del objeto. El propósito de subdividir el El montón de Java es solo para un mejor reciclaje de memoria o para asignar memoria más rápido. En este capítulo, solo discutimos el papel 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 la máquina virtual de Java", el montón de Java puede estar en un espacio de memoria físicamente discontinuo, pero lógicamente debe considerarse contiguo. Esto es lo mismo que cuando usamos espacio en disco para almacenar archivos. Cada documento es necesario para almacenarse continuamente. Pero para objetos grandes (típicamente objetos de matriz), es probable que la mayoría de las implementaciones de máquinas virtuales requieran espacio de memoria contiguo en aras de una implementación simple y una alta 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 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 lanzará una excepción OutOfMemoryError. De acuerdo con la "Especificación de la máquina virtual de Java", el montón de Java puede estar en un espacio de memoria físicamente discontinuo, pero lógicamente debe considerarse contiguo. Esto es lo mismo que cuando usamos espacio en disco para almacenar archivos. Cada documento es necesario para almacenarse continuamente. Pero para objetos grandes (típicamente objetos de matriz), es probable que la mayoría de las implementaciones de máquinas virtuales requieran espacio de memoria contiguo en aras de una implementación simple y una alta 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 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 lanzará una excepción OutOfMemoryError. De acuerdo con la "Especificación de la máquina virtual de Java", el montón de Java puede estar en un espacio de memoria físicamente discontinuo, pero lógicamente debe considerarse contiguo. Esto es lo mismo que cuando usamos espacio en disco para almacenar archivos. Cada documento es necesario para almacenarse continuamente. Pero para objetos grandes (típicamente objetos de matriz), es probable que la mayoría de las implementaciones de máquinas virtuales requieran espacio de memoria contiguo en aras de una implementación simple y una alta 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 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 lanzará una excepción OutOfMemoryError.

1.5, á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 justo a tiempo que se ha 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, tengo que mencionar el 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, y muchas personas prefieren llamar al área de métodos "". Generación permanente "(Generación permanente), o confunda los dos. 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étodo, o usar la generación permanente para implementar el área de método, lo que hace que HotSpot sea basura. administre 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 está sujeta a 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 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 4 GB en un sistema de 32 bits, no habrá problemas), y hay muy pocos métodos (como String :: intern ()) causará diferentes rendimientos en diferentes máquinas virtuales debido a la generación permanente. 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 JDK 6, el equipo de desarrollo de HotSpot abandonó la generación permanente y cambió gradualmente al plan de usar memoria nativa para implementar el área de método. En HotSpot de JDK 7, el original La generación permanente de grupos de cadenas de cadenas constantes, variables estáticas, etc., se eliminaron, y en JDK 8, el concepto de generación permanente finalmente se abandonó por completo. En su lugar, se utilizó el metaespacio (Metaspace) implementado en la memoria local como JRockit y J9, y el JDK 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 relajadas 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, pero no es que los datos ingresen al área del método como "permanente" 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 más 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 necesaria. En la lista de errores de la compañía Sun anterior, varios errores graves que han aparecido se deben a la versión baja de la máquina virtual HotSpot que no recuperó por completo esta área, lo que provocó pérdidas de memoria. La "Especificación de máquina virtual de Java" tiene restricciones muy relajadas 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, pero no es que los datos ingresen al área del método como "permanente" 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 más 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 necesaria. En la lista de errores de la compañía Sun anterior, varios errores graves que han aparecido se deben a la versión baja de la máquina virtual HotSpot que no recuperó por completo esta área, lo que provocó pérdidas de memoria. La "Especificación de máquina virtual de Java" tiene restricciones muy relajadas 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, pero no es que los datos ingresen al área del método como "permanente" 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 más 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 necesaria. En la lista de errores de la compañía Sun anterior, varios errores graves que han aparecido se deben a la versión baja de la máquina virtual HotSpot que no recuperó por completo esta área, lo que provocó pérdidas de memoria.

1.6, grupo de constantes en 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, también hay una tabla de pool constante (Constant Pool Table), que se utiliza para almacenar varias referencias literales y simbólicas generadas durante el tiempo de compilación. Esta parte 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, qué tipo de datos se utiliza para almacenar cada byte debe cumplir con los requisitos de la especificación antes de que pueda ser reconocido , cargado y ejecutado por la máquina virtual, 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 , además de guardar el archivo de clase Además de las referencias simbólicas descritas, las referencias directas traducidas de las referencias simbólicas también se almacenan en el grupo de constantes de tiempo de ejecución [1]. Otra característica importante del grupo de constantes en tiempo de ejecución en comparación con el grupo de constantes de archivos de clase es que es dinámico. El lenguaje Java no requiere que se generen constantes solo en tiempo de compilación, es decir, el contenido del grupo de constantes que no está preestablecido en el archivo de clase se puede ingresar. El área de método es un grupo de constantes en tiempo de ejecución, y también se pueden 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 método intern () 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, lanzará una excepción OutOfMemoryError.

1.7, memoria directa

La memoria directa (memoria directa) no forma 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 hacer que aparezca OutOfMemoryError, por lo que lo ponemos aquí para explicarlo juntos. En JDK 1.4, se agregó recientemente la clase NIO (Nueva entrada / salida) y se introdujo un método de E / S basado en canales y búfer. Puede usar la biblioteca de funciones nativas para asignar directamente memoria fuera del montón y luego operar por 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 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 (incluidas las físicas y Límite de nivel de sistema operativo), lo que da como resultado una excepción OutOfMemoryError durante la expansión dinámica.

 

2. Explorando el objeto de la máquina virtual HotSpot

2.1 Creación de objetos

Cuando la máquina virtual Java encuentra una nueva instrucción de código de bytes, primero verificará si el parámetro de esta instrucción puede ubicar una referencia de símbolo de una clase en el grupo constante, y verificará si la clase representada por la referencia de símbolo se ha cargado, resuelto, e inicializado. Si no es así, primero debe realizar el proceso de carga de clases correspondiente. Después de que pase la verificación de carga de clases, la máquina virtual asignará memoria para el nuevo objeto. El tamaño de la memoria requerida por el objeto se puede determinar completamente después de que se cargue la clase.

Los métodos de asignación de memoria incluyen métodos de "colisión de puntero" y "lista libre":

Cuando la memoria Java es absolutamente normal, se puede asignar mediante "colisión de puntero". Toda la memoria utilizada se coloca en un lado, la memoria libre se coloca en el otro lado y un puntero se coloca en el medio como indicador del punto de demarcación. ., La memoria asignada es solo para mover el puntero al espacio libre una distancia igual al tamaño del objeto Este método de asignación se llama "Bump The Pointer". Este método de asignación se puede utilizar cuando se utiliza un algoritmo GC basado en marcar y clasificar.

Cuando la memoria en el montón de Java no es regular, y la memoria usada y la memoria libre están entrelazadas, no hay forma de simplemente colisionar punteros. La máquina virtual debe mantener una lista de los bloques de memoria que se registran. Si está disponible , busque un espacio lo suficientemente grande en la lista para asignarlo a la instancia del objeto durante la asignación y actualice los registros de la lista. Este método de asignación se denomina "Lista libre". Este método de asignación se puede utilizar cuando se utiliza un algoritmo GC basado en marcado y barrido.

Cuando se trata de la asignación concurrente, hay dos soluciones: una es sincronizar la acción de asignar espacio de memoria; de hecho, la máquina virtual usa CAS con reintento de falla para garantizar la atomicidad de la operación de actualización; la otra es La acción de la memoria La asignación se lleva a cabo en diferentes espacios de acuerdo con la división de subprocesos, es decir, cada subproceso asigna previamente una pequeña porción de memoria en el montón de Java, llamado búfer de asignación de subprocesos local (Thread Local Allocation Buffer, TLAB), que subproceso debe asignar la memoria, Se asigna en el búfer local de qué hilo, solo cuando el búfer local se agota, se requiere el bloqueo de sincronización cuando se asigna una nueva área de búfer. Si la máquina virtual usa TLAB se puede establecer mediante el parámetro -XX: +/- UseTLAB.

2.2 Disposición de la memoria de objetos

El diseño de la memoria de los objetos en la máquina virtual HotSpot se puede dividir en tres: encabezado del objeto (encabezado), datos de instancia (datos de instancia) y relleno de alineación (relleno).

2.2.1, encabezado del objeto

       Se sabe por la imagen que el encabezado del objeto se divide en dos partes: Marcar palabra y Puntero de clase.

Mark Word almacena el código hash del objeto, la información de GC y la información de bloqueo. Class Pointer almacena un puntero a la información del objeto de clase. En la JVM de 32 bits, el tamaño del encabezado del objeto es de 8 bytes y la JVM de 64 bits es de 16. Los dos tipos de Mark Word y Class Pointer cada uno ocupan la mitad del espacio. Hay una opción de puntero comprimido -XX: + UseCompressedOops en la JVM de 64 bits, que está habilitada de forma predeterminada. Después de abrir, la parte Class Pointer se comprimirá a 4 bytes y el tamaño del encabezado del objeto se reducirá a 12 bytes.

La siguiente es una imagen que muestra la distribución de memoria del encabezado del objeto en la JVM de 32 bits, que es fácil de entender.

2.2.2, datos de instancia y relleno de alineación

       La asignación de memoria variable definida en el programa de usuario se verá afectada por el parámetro de estrategia de asignación de la máquina virtual (-XX: parámetro FieldsAllocationStyle) y el orden en que los campos se definen en el código fuente de Java. El orden de asignación predeterminado de la máquina virtual HotSpot es longs / doubles, ints, short / chars, bytes / booleanos, oops (Ordinary Object Pointers, OOP). Como se puede ver en la estrategia de asignación predeterminada anterior, los campos con el mismo ancho son siempre asignadas Almacenadas juntas, en el caso de cumplir con este prerrequisito, las variables definidas en la clase padre aparecerán antes que la clase hija. Si el valor del parámetro + XX: CompactFields de la máquina virtual HotSpot es verdadero (el valor predeterminado es verdadero), las variables más estrechas de la subclase también se pueden insertar en el espacio de la variable de clase principal para ahorrar un poco de espacio. Dado que el sistema de administración automática de memoria de la máquina virtual HotSpot requiere que la dirección de inicio del objeto sea un múltiplo entero de 8 bytes, en otras palabras, el tamaño de cualquier objeto debe ser un múltiplo entero de 8 bytes, por lo que si el objeto La parte de datos de instancia no está alineada. Entonces necesita alinear.

2.3. Ubicación de acceso a objetos

Los métodos de acceso principales incluyen principalmente el uso de identificadores y punteros directos:

1. Si usa el acceso de identificador, una parte de la memoria se puede dividir en el montón de Java como un grupo de identificador. La dirección del identificador del objeto se almacena en la referencia y el identificador contiene la información de dirección específica de los datos de instancia del objeto y escriba los datos.

2. Si utiliza el acceso de puntero directo, el diseño de la memoria del objeto en el montón de Java debe considerar cómo colocar la información sobre los datos del tipo de acceso. La referencia almacenada en el objeto es la dirección del objeto directamente. Si solo accede al objeto en sí, no es necesario que vuelva a hacerlo. La sobrecarga del acceso indirecto.

 

3. Excepción OutOfMemoryError

3.1, desbordamiento del montón de Java

/**
* VM Args:-Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError
* @author zzm
*/
public class HeapOOM {
    static class OOMObject {
    }
    public static void main(String[] args) {
        List<OOMObject> list = new ArrayList<OOMObject>();
        while (true) {
            list.add(new OOMObject());
        }
    }
}

El código de prueba es el anterior, la excepción OutOfMemoryError de la memoria del montón de Java es la excepción de desbordamiento de memoria más común en aplicaciones prácticas. Cuando se produce un desbordamiento de la memoria del montón de Java, la información de la pila de excepciones "java.lang.OutOfMemoryError" seguirá un mensaje adicional "Espacio de montón de Java".

Para resolver la anomalía en esta área de memoria, el método de procesamiento convencional es analizar primero la instantánea de volcado de pila de Dump a través de una herramienta de análisis de imágenes de memoria (como Eclipse Memory Analyzer). El primer paso es confirmar si los objetos de la memoria que provocan OOM son necesarios, es decir, distinguir si hay una fuga de memoria (Memory Leak) o un desbordamiento de memoria (Memory Overflow).

Si se trata de una fuga de memoria, puede verificar más a fondo la cadena de referencia del objeto filtrado a GC Roots a través de la herramienta y averiguar qué ruta de referencia atraviesa el objeto filtrado y con qué GC Roots están asociados, de modo que el recolector de basura no puede recuperarlos, de acuerdo con el tipo de objeto filtrado La información y su información a la cadena de referencia GC Roots generalmente se puede ubicar con mayor precisión donde se crean estos objetos, y luego averiguar la ubicación específica del código que causó la pérdida de memoria.

Si no se trata de una pérdida de memoria, en otras palabras, todos los objetos en la memoria deben estar activos, entonces debe verificar la configuración del parámetro de pila (-Xmx y -Xms) de la máquina virtual Java y compararla con la memoria de la máquina para vea si hay algún espacio de ajuste hacia arriba. Luego, verifique en el código si hay algunos objetos con un ciclo de vida demasiado largo, un tiempo de estado de retención demasiado largo, un diseño de estructura de almacenamiento irrazonable, etc., para minimizar el consumo de memoria durante la operación del programa.

3.2 Desbordamiento de pila de máquina virtual y pila de método local

Con respecto a la pila de la máquina virtual y la pila del método local, se describen dos excepciones en la "Especificación de la máquina virtual de Java":

1) Si la profundidad de pila solicitada por el hilo es mayor que la profundidad máxima permitida por la máquina virtual, se lanzará una excepción StackOverflowError.

2) Si la memoria de la pila de la máquina virtual permite la expansión dinámica, se lanzará una excepción OutOfMemoryError cuando la capacidad de la pila de expansión no pueda solicitar suficiente memoria.

/**
* 不停的递归导致栈溢出
* VM Args:-Xss128k
* @author zzm
*/
public class JavaVMStackSOF {
    private int stackLength = 1;
    public void stackLeak() {
        stackLength++;
        stackLeak();
    }
    public static void main(String[] args) throws Throwable {
        JavaVMStackSOF oom = new JavaVMStackSOF();
        try {
            oom.stackLeak();
        } catch (Throwable e) {
            System.out.println("stack length:" + oom.stackLength);
            throw e;
        }
    }
}

/**
* 不停创建线程导致栈溢出
* VM Args:-Xss2M (这时候不妨设大些,请在32位系统下运行)
* @author zzm
*/
public class JavaVMStackOOM {
    private void dontStop() {
        while (true) {
        }
    }
    public void stackLeakByThread() {
        while (true) {
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    dontStop();
                }
            });
            thread.start();
        }
    }
    public static void main(String[] args) throws Throwable {
        JavaVMStackOOM oom = new JavaVMStackOOM();
        oom.stackLeakByThread();
    }
}

Cuando se produce una excepción StackOverflowError, habrá una pila de errores clara para el análisis, que es relativamente fácil de localizar el problema. Si usa los parámetros predeterminados de la máquina virtual HotSpot, la profundidad de la pila es en la mayoría de los casos (debido a que el tamaño del marco introducido en la pila por cada método no es el mismo, por lo que solo puedo decir que en la mayoría de los casos) no hay problema para llegar a 1000 ~ 2000. Para llamadas a métodos normales (incluidas las llamadas recursivas que no se pueden optimizar para la recursividad de cola), esta profundidad debería ser suficiente. Sin embargo, si el desbordamiento de la memoria se debe al establecimiento de demasiados subprocesos, si no se puede reducir el número de subprocesos o no se puede reemplazar la máquina virtual de 64 bits, la única forma de intercambiar por más subprocesos es reducir el montón máximo y reducir la capacidad de la pila. Este método de "reducir la memoria" para resolver el desbordamiento de la memoria es generalmente difícil de pensar sin experiencia en esta área. Los lectores deben prestar atención a este punto cuando desarrollen aplicaciones multiproceso para sistemas de 32 bits. También se debe a que este problema está relativamente oculto. A partir de JDK 7, después de "no se pudo crear el hilo nativo" en el mensaje de aviso anterior, la máquina virtual indica específicamente que la razón puede ser "posiblemente sin memoria o límites de proceso / recursos alcanzados ".

 

3.3 Área de método y desbordamiento constante de la piscina en tiempo de ejecución

/**
* 使用GLIBC不断生成动态类型导致方法区溢出
* VM Args:-XX:PermSize=10M -XX:MaxPermSize=10M
* @author zzm
*/
public class JavaMethodAreaOOM {
	public static void main(String[] args) {
		while (true) {
			Enhancer enhancer = new Enhancer();
			enhancer.setSuperclass(OOMObject.class);
			enhancer.setUseCache(false);
			enhancer.setCallback(new MethodInterceptor() {
				public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
					return proxy.invokeSuper(obj, args);
				}
			});
			enhancer.create();
		}
	}
	static class OOMObject {
	}
}

El desbordamiento del área de método también es una excepción común de desbordamiento de memoria Si el recolector de basura debe reciclar una clase, las condiciones que se deben lograr son relativamente duras. En escenarios de aplicación donde se genera una gran cantidad de clases dinámicas durante el funcionamiento frecuente, se debe prestar especial atención al estado de reciclaje de estas clases. Después de JDK 8, la generación permanente se ha retirado por completo del escenario de la historia, y Metaspace aparece como su reemplazo.

HotSpot todavía proporciona algunos parámetros como medidas de defensa para el metaespacio, que incluyen:

-XX: MaxMetaspaceSize: establece el tamaño máximo de metaespacio, el valor predeterminado es -1, es decir, sin límite o solo limitado por el tamaño de la memoria local. ·

-XX: MetaspaceSize: Especifique el tamaño del espacio inicial del metaespacio, en bytes. Cuando se alcanza este valor, se activará la recolección de basura para la descarga de tipos y el recolector ajustará el valor: si se libera una gran cantidad de espacio, es apropiado Disminuya este valor; si se libera muy poco espacio, aumente el valor apropiadamente si no excede -XX: MaxMetaspaceSize (si está configurado).

-XX: MinMetaspaceFreeRatio: La función es controlar el porcentaje de la capacidad restante del metaespacio más pequeño después de la recolección de basura, lo que puede reducir la frecuencia de recolección de basura debido a un metaespacio insuficiente. De manera similar, existe -XX: Max-MetaspaceFreeRatio, que se usa para controlar el porcentaje de la capacidad restante del metaespacio más grande.

3.4 Desbordamiento de memoria directa nativa

/**
* 通过Unsafe不断分配堆外内存最后导致本机内存溢出
* VM Args:-Xmx20M -XX:MaxDirectMemorySize=10M
* @author zzm
*/
public class DirectMemoryOOM {
	private static final int _1MB = 1024 * 1024;
	public static void main(String[] args) throws Exception {
		Field unsafeField = Unsafe.class.getDeclaredFields()[0];
		unsafeField.setAccessible(true);
		Unsafe unsafe = (Unsafe) unsafeField.get(null);
		while (true) {
			unsafe.allocateMemory(_1MB);
		}
	}
}

El tamaño de la memoria directa (Direct Memory) se puede especificar mediante el parámetro -XX: MaxDirectMemorySize. Si no se especifica, el valor predeterminado es el mismo que el montón máximo de Java (especificado por -Xmx). Una característica obvia del desbordamiento de memoria causado por la memoria directa es que no se verán anomalías obvias en el archivo de volcado de pila. Si el lector encuentra que el archivo de volcado generado después del desbordamiento de memoria es muy pequeño, se usa directa o indirectamente en el programa. DirectMemory (el uso indirecto típico es NIO), entonces puede considerar enfocarse en verificar las razones de la memoria directa.

 

Referencia: <Comprensión profunda de la máquina virtual Java: características avanzadas y mejores prácticas de JVM tercera edición>

 

Supongo que te gusta

Origin blog.csdn.net/qq_32323239/article/details/108742375
Recomendado
Clasificación