Una comprensión profunda de la JVM puede ayudarnos a mejorar nuestra capacidad para resolver problemas desde la perspectiva de la plataforma, como prevenir eficazmente pérdidas de memoria, optimizar el uso de bloqueos de subprocesos, recolectar basura de manera más eficiente y mejorar el sistema. rendimiento y reducción de la latencia. Mejorar su rendimiento, etc.
¿Cómo entiendes JVM?
JVM es la abreviatura de máquina virtual JAVA. Como sugiere el nombre, es una computadora virtual, una implementación abstracta (ficticia) de una computadora de hardware y una parte de la plataforma Java, como se muestra en la figura (ver parte inferior de la figura):
JVM es la base para la implementación multiplataforma de programas Java (la multiplataforma de Java se implementa esencialmente a través de JVM de diferentes plataformas). Su función es cargar el programa Java, traducir el código de bytes a código de máquina y luego entregarlo a la CPU para su ejecución. Como se muestra en la figura:
El código Java (.java) debe convertirse en código de bytes (.class) antes de ejecutar el programa. JVM carga el código de bytes en la memoria a través de ClassLoader. Siga Silicon Valley y aprenda fácilmente. Sin embargo, el archivo de código de bytes es un conjunto de especificaciones de conjunto de instrucciones de la JVM y no se puede entregar directamente al sistema operativo subyacente para su ejecución. Por lo tanto, se necesita un motor de ejecución de analizador de comandos especializado para traducir el código de bytes en código de máquina subyacente y luego entregárselo a la CPU.
¿Cuáles son las JVM más populares del mercado?
JVM es una especificación. A partir de esta especificación, diferentes empresas han realizado implementaciones específicas. ¿BEA desarrolla la máquina virtual JRockit? Posteriormente fue adquirida por Oracle en 2008; ¿IBM desarrolló J9 VM? , utilizado sólo dentro de IBM. ¿Hotspot VM desarrollado por Sun? Posteriormente fue adquirida por Oracle en 2010. Actualmente es la máquina virtual JVM más utilizada en Oracle y la más utilizada.
¿Cuál es la arquitectura de JVM?
La arquitectura de JVM, como se muestra en la figura:
El sistema ClassLoader es responsable de cargar las clases en la memoria; el área de datos en tiempo de ejecución es responsable de almacenar la información de datos del objeto durante la ejecución; El motor se encarga de llamar a los objetos para ejecutar servicios; La interfaz nativa se encarga de integrar diferentes lenguajes de programación para Java.
¿Cuáles son los modos de funcionamiento de JVM?
JVM tiene dos modos de funcionamiento: servidor y cliente. La diferencia entre los dos modos es que el modo cliente se inicia más rápido y el modo servidor se inicia más lentamente, pero después de que el inicio entra en un período estable, el programa en el modo servidor se ejecuta mucho más rápido que el cliente. Esto se debe a que la JVM iniciada en modo servidor utiliza una máquina virtual pesada, que optimiza el programa en mayor medida. Una JVM iniciada en modo invitado utiliza una máquina virtual liviana. Por lo tanto, el servidor se inicia lentamente, pero después de la estabilización, es mucho más rápido que el cliente.
Actualmente, el jdk de 64 bits está predeterminado en modo servidor (se puede ver a través de la versión java). Cuando la máquina virtual se ejecuta en modo cliente, utiliza un compilador liviano con el nombre en código C1, mientras que la máquina virtual iniciada en modo servidor usa un compilador relativamente pesado con el nombre en código c2. Tanto c1 como C2 son compiladores JIT. C2 compila más exhaustivamente que el compilador C1 y tiene un mayor rendimiento después del servicio.
¿Cuál es la estructura de memoria de tiempo de ejecución de la JVM?
Las diferentes implementaciones de máquinas virtuales pueden ser ligeramente diferentes, pero todas siguen la especificación de la máquina virtual Java, Java 8 virtual.
Según la especificación de simulación, la memoria administrada por la máquina virtual Java incluirá las siguientes áreas, como se muestra en la figura:
Montón de Java
El El montón de Java es el más grande en la JVM. Un bloque de memoria compartido por todos los subprocesos. Se crea cuando se inicia la máquina virtual y se utiliza principalmente para almacenar instancias de objetos. La mayoría de las instancias de objetos también se asignan aquí. Con el desarrollo de los compiladores JIT y la madurez gradual de la tecnología de análisis de escape, la tecnología de asignación de pila y optimización de reemplazo escalar conducirá a algunos cambios sutiles, y todos los objetos asignados en el montón se volverán gradualmente menos absolutos. Los objetos pequeños sin escape también se pueden asignar directamente en la pila. Si no hay memoria en el montón para completar la asignación de instancias y el montón ya no se puede expandir, el tiempo de ejecución subyacente del sistema generará un OutOfMemoryError. Según las especificaciones de la máquina virtual Java, el montón de Java puede ser un espacio de memoria físicamente discontinuo, siempre que sea lógicamente continuo, al igual que nuestro espacio en disco.
En la implementación, puede tener un tamaño fijo o escalable, pero actualmente las máquinas virtuales convencionales son escalables y el tamaño de la memoria del montón está definido por los parámetros -Xmx y -Xms.
Área de métodos
El área de métodos es una especificación que se utiliza para almacenar datos cargados por la máquina virtual, como información de clases, constantes, variables estáticas, código compilado justo a tiempo, etc. . Diferente jdk, la implementación del área de método es diferente. La máquina virtual del punto de acceso utiliza memoria nativa para implementar el área de método en JDK 8. Cuando un método no puede cumplir con los requisitos de asignación de memoria, se generará una excepción OutOfMemoryError.
Pila de máquina virtual Java (pila VM)
La pila de máquina virtual Java describe el modelo de memoria de los métodos Java durante la ejecución. Cuando un subproceso llama a cada método, se crea un marco de pila para almacenar información como tablas de variables locales, pilas de operandos, enlaces dinámicos, salidas de métodos, etc. Cada método está orientado a Silicon Valley desde la invocación hasta la finalización de la ejecución. Es fácil entender que corresponde al proceso de entrada y salida de un marco de pila de la pila de la máquina virtual. Si la profundidad de la pila solicitada por el subproceso es mayor que la profundidad de la pila permitida por la máquina virtual, se generará una excepción StackOverflowError. Si la máquina virtual se puede expandir dinámicamente y no puede solicitar suficiente memoria durante el proceso de expansión, se generará una excepción OutOfMemoryError.
Pila de métodos nativos JVM
La función de la pila de métodos nativos es similar a la de la pila de máquinas virtuales, excepto que la pila de máquinas virtuales sirve métodos Java, mientras que la pila de métodos nativos Sirve a la máquina virtual para llamar a métodos nativos. No existen requisitos especiales para la pila de métodos locales en la especificación de la máquina virtual Java, y la máquina virtual se puede implementar libremente, por lo que la pila de métodos locales y la pila de máquinas virtuales se integran directamente en la máquina virtual Sun HotSpot.
Registro del contador del programa JVM
El registro del contador del programa es un pequeño espacio de memoria que puede considerarse como la indicación del número de línea del código de bytes ejecutado por el hilo actual. En el modelo conceptual de la máquina virtual, el trabajo del analizador de código de bytes es seleccionar la siguiente instrucción de código de bytes que se ejecutará cambiando el valor de este contador. Las funciones básicas como bifurcación, bucle, salto, manejo de excepciones y recuperación de subprocesos son. all Este contador es necesario para completar.
Porque el subproceso múltiple de JVM se logra cambiando los subprocesos en secuencia y asignando el tiempo de ejecución del procesador, es decir, en cualquier momento, un procesador (o núcleo) solo ejecutará instrucciones en un subproceso. Entonces, para restaurar la posición de ejecución correcta después del cambio de hilo, cada hilo tiene un contador de programa independiente.
Si el hilo está ejecutando un método en Java, el contador del programa registra la dirección de la instrucción de código de bytes de la máquina virtual que se está ejecutando; si es un método nativo, este contador no está definido, por lo que esta área de memoria; es Java El único en la especificación de la máquina virtual que no especifica OutOfMemoryError.
¿Cómo entender el sistema GC en JVM?
Rastree todos los objetos que todavía están en uso, marque el resto como basura y luego recíclelos. Este proceso se llama GC (recolección de basura). Todos los sistemas de GC se pueden derivar de GC (como recuento de referencias, análisis de accesibilidad de objetos), algoritmos de recopilación de GC (marcar-barrer, marcar-barrer-ordenar, marcar-copiar-barrer, generar), recopiladores de GC (como serie, paralelo ) para determinar la estrategia.
¿Cuál puede ser la raíz en la cadena de referencia JVM?
Objetos de referencia en la pila de la máquina virtual Java;
Objetos referenciados por JNI (comúnmente llamados métodos nativos) en la pila de métodos local;
Área de método La referencia objeto de la constante estática en la clase;
El objeto de referencia de la constante en el área del método.
¿Cuáles son los algoritmos comunes de recolección de basura en JVM?
Algoritmo de contador de referencia
Este algoritmo establece un contador de referencia para cada objeto. Siempre que hay una referencia a este objeto, el contador se incrementa en 1. Por el contrario, siempre que la referencia no es válida, el contador se reduce en 1. Es decir, el conteo se utiliza para determinar si un objeto es basura. Por ejemplo:
Un gran defecto del método de recuento de referencias son las referencias circulares, como:
El algoritmo de análisis de accesibilidad
La idea central de este El algoritmo es una serie de objetos "raíz de GC" que se utilizan como punto de partida para la búsqueda, y la ruta de búsqueda se denomina "cadena de referencia". Demuestra que un objeto se puede reciclar sin una cadena de referencia conectada a la raíz del GC.
Por ejemplo:
Algoritmo de copia
Este algoritmo divide la memoria en dos bloques del mismo tamaño. Cuando este bloque se agote, copie los objetos activos actualmente en otro bloque y luego borre el bloque actual de una vez. La desventaja de este algoritmo es que sólo utiliza la mitad del espacio de memoria. Por ejemplo:
Algoritmo de limpieza de marcas
La implementación de este algoritmo se divide en dos etapas. En la primera fase, todos los objetos a los que se hace referencia se marcan a partir del nodo raíz de referencia y, en la segunda fase, se recorre todo el montón para borrar los objetos no marcados. Este algoritmo requiere suspender toda la aplicación y provoca fragmentación de la memoria. Por ejemplo:
Algoritmo de clasificación de marcas
Este algoritmo combina las ventajas de los algoritmos de "marca clara" y "copia". La primera fase marca todos los objetos referenciados a partir del nodo raíz. La segunda fase atraviesa todo el montón, "comprime" y copia los objetos supervivientes en un espacio en el montón y luego los descarga en orden. Este algoritmo evita el problema de fragmentación de "marcar-borrar" y el problema de espacio del algoritmo de "copia", como:
¿Cuáles son los tipos de referencias de objetos JVM?
Los algoritmos de recuento de referencias y análisis de accesibilidad están relacionados con la "referencia" de objetos [Hablemos de los cuatro tipos de referencias en Java. 】, se puede ver que el objeto de referencia del objeto determina la vida o muerte del objeto. La relación de referencia de los objetos es la siguiente.
Referencias sólidas
Mientras existan referencias sólidas, el recolector de basura nunca reciclará las referencias ubicuas como Object obj = new Object() en el código.
Referencia suave
Es una referencia relativamente fuerte con una referencia más débil que puede salvar el objeto de alguna recolección de basura. Solo cuando la JVM crea que no hay suficiente memoria intentará recuperar el objeto señalado por la referencia suave. La JVM se asegurará de que el objeto al que apunta la referencia suave se borre antes de generar un OutOfMemoryError.
Referencia débil
Un objeto no esencial, pero su fuerza es más débil que la de una referencia suave. El objeto asociado con una referencia débil solo puede sobrevivir hasta la siguiente recolección de basura.
Referencia virtual
También conocida como referencia fantasma o referencia fantasma, es la relación de referencia más débil. Las instancias de objetos no se pueden obtener a través de referencias virtuales. El propósito de establecer una referencia virtual a un objeto es solo recibir una notificación del sistema cuando el recolector reclama el objeto.
¿Cuáles son las clasificaciones de los recolectores de basura JVM?
Recuperador de nueva generación
Recuperador en serie, nuevo y paralelo
Recicladores antiguos
Antiguo en serie, antiguo en paralelo, CMS
Recuperador de pila completa
Recolector de basura G1
¿Cuáles son los componentes de un recolector de basura generacional?
Una generación de recolectores de basura está formada por una generación joven y una generación mayor. De forma predeterminada, la proporción de memoria entre la nueva generación y la antigua generación es 1:2.
¿Cuáles son los componentes de nueva generación? :
La nueva generación consta de Eden, Form Survivor y To Survivor. Su proporción de memoria predeterminada es 8:1:1, como se muestra en la figura:
¿Cómo es la nueva generación? de recolección de basura?
El primer paso es copiar el cuerpo vivo de Eden del superviviente al área de superviviente; el segundo paso es limpiar las particiones de Eden y de superviviente y el tercer paso es cambiar la partición de superviviente a superviviente (Cambiar); de superviviente a superviviente, de superviviente a superviviente). Cuando la partición superviviente de nueva generación es 2, tanto la utilización del espacio como la eficiencia de ejecución del programa son óptimas.
¿Hablar sobre el recolector de basura CMS en la JVM?
CMS (Concurrent Mark and Sweep) es un recolector de marcado y eliminación de basura simultáneos. Utilizará listas gratuitas para gestionar la recuperación de espacio de memoria sin tener que clasificar los viejos tiempos. Tiene la ventaja de que la mayor parte del trabajo de la fase de marca y limpieza se realiza simultáneamente con el hilo de aplicación. Puede reducir retrasos, acortar los tiempos de pausa y mejorar el tiempo de respuesta del servicio www.atguigu.com. Por supuesto, también hay fallas, principalmente porque es sensible a los requisitos de recursos de la CPU y no puede eliminar la basura flotante (la basura flotante se refiere a la nueva basura generada por los subprocesos del usuario cuando el CMS limpia la basura. Esta parte de la basura no marcada se llama "basura flotante"). y solo se puede eliminar la próxima vez. Se borra durante la GC), y también generará una gran cantidad de fragmentación del espacio.
¿Hablar sobre el recolector de basura G1 en JVM?
G1 (Garbage-First GC) es un recolector en tiempo real cuyo objetivo de diseño es hacer que el tiempo de pausa y la distribución de STW sean predecibles y configurables. Se puede decir que es una implementación de GC que tiene en cuenta tanto el rendimiento como el tiempo de pausa. G1 hace que sea intuitivo establecer objetivos de tiempo de pausa. En comparación con CMS, es posible que G1 no pueda retrasar las pausas de CMS en el mejor de los casos, pero es mucho mejor en el peor de los casos.
Cuando se utiliza el recopilador G1, el diseño de la memoria del montón de Java es muy diferente al de otros recopiladores. Divide todo el montón de Java en varias regiones independientes de igual tamaño. Aunque los conceptos de nueva generación y vejez aún se conservan, la nueva generación y la vejez ya no son barreras físicas. Son una colección de áreas (discontinuas), como por ejemplo:
Esta división. make El GC no necesita recopilar todo el espacio del montón cada vez, sino que lo procesa de forma incremental, procesando solo una parte de un área pequeña del montón a la vez, denominada conjunto de recopilación. Cada pausa recoge todos los pequeños montones de la generación más joven y puede contener solo una parte de los pequeños montones de la generación anterior.
Otra innovación de G1 es estimar el número total de objetos supervivientes en cada pequeño montón durante la fase concurrente. El principio de construir un grupo de recolección es recolectar primero el montón pequeño con mayor cantidad de basura. De aquí proviene el nombre G1: basura primero.
G1 resuelve varios desafíos en CMS, incluido hacer que los tiempos de pausa sean más predecibles y terminar con la fragmentación de la memoria del montón. Para sistemas que son muy sensibles a la latencia de los servicios individuales, G1 es posiblemente la mejor opción en HotSpot, especialmente en la última versión de la máquina virtual Java, si los recursos de la CPU no son limitados. Por supuesto, esta optimización para reducir la latencia tiene un costo: G1 tendrá más sobrecarga debido a barreras de escritura adicionales y subprocesos de demonio más activos. Por lo tanto, CMS es una mejor opción si el sistema prioriza el rendimiento o si la CPU está ocupada al 100% todo el tiempo y no le importan los tiempos de pausa de GC individuales.
¿Cuáles son los parámetros de ajuste para la recolección de basura JVM?
-Xmx:512 establece la memoria de montón máxima en 512 m.
-Xms:256 la memoria de montón inicial (montón mínimo) es 256 m
-XX:MaxNewSize; Establece la memoria máxima de la generación joven;
-XX:MaxTenuringThreshold=6 establece los objetos de nueva generación que serán promovidos a la vejez después de 6 GC;
-xx: pretnuresizethreshold establece la valor de objeto grande, los objetos grandes que exceden este valor ingresan directamente a la generación anterior;
-XX:NewRatio establece la relación de memoria entre la nueva generación y la generación anterior del recolector de basura generacional;
-XX:SurvivorRatio Establece la proporción de Eden, Form Survivor y To Survivor en la nueva generación.
¿Cuáles son los principios de ajuste para el GC concurrente moderno de JVM?
En primer lugar, el espacio se intercambia por tiempo y eficiencia para g 1 y aumentar la configuración de la memoria del montón de ZGC (más espacio libre) suele ser más propicio para que el GC alcance el tiempo de pausa objetivo. En segundo lugar, tenga en cuenta que las pausas bajas no necesariamente significan un alto rendimiento. La GC concurrente garantiza que los subprocesos comerciales aún puedan obtener intervalos de tiempo de CPU al mismo tiempo, pero también significa que GC aprovechará los recursos informáticos con los subprocesos comerciales. A menudo, más etapas concurrentes ocuparán más recursos informáticos para manejar más problemas de sincronización. El tercero es que el ajuste de GC siempre debe considerar los recursos de la máquina, los escenarios de aplicación del sistema correspondientes, etc. No existe una solución mágica, al menos por ahora.
Artículo de Jason.