5 consejos para comprender a fondo el modelo de memoria JVM [para el desarrollo de Java durante 3 años]

Prefacio

Este artículo se centrará en el análisis de jvm, y el contenido involucrado incluye el modelo de memoria de jvm, el cargador de clases, el algoritmo de recuperación de GC, el dispositivo de recuperación de GC y el sesgo general es teórico. Este artículo no es adecuado para principiantes. Debido al espacio limitado, el editor clasificará más de 400 páginas de notas de estudio sobre el ajuste del rendimiento de JVM y prestará atención a la especie pública Hao: Kylin cambia errores, compártelo con todos, adecuado para más de 3 años de experiencia en desarrollo El personal técnico de, da la bienvenida a todos para intercambiar y compartir, si hay alguna deficiencia en el artículo, da la bienvenida a los lectores y amigos para señalar, gracias de antemano.

Una relación clara entre jdk, jre y jvm

La siguiente imagen es el sitio web oficial sobre el diagrama de arquitectura jdk, jre y jvm, del diagrama de arquitectura, es fácil ver la relación entre los tres:

(1) jdk contiene jre y jre contiene jvm

(2) JDK se usa principalmente en el entorno de desarrollo y jre se usa principalmente en el entorno de lanzamiento. Por supuesto, está bien usar JDK en el entorno de lanzamiento, pero el rendimiento puede verse afectado un poco. La relación entre jdk y jre es algo similar a la relación entre la versión de depuración y la versión de lanzamiento del programa

(3) En términos de tamaño de archivo, jdk es más grande que jre. Como se puede ver en la figura, jdk tiene un conjunto de herramientas más que jre, como los comandos javac, java de uso común, etc.

5 consejos para comprender a fondo el modelo de memoria JVM [para el desarrollo de Java durante 3 años]

Cargador de segunda clase

Acerca del cargador de clases jvm, se puede resumir en la siguiente figura:

5 consejos para comprender a fondo el modelo de memoria JVM [para el desarrollo de Java durante 3 años]

1. ¿Por qué hay un cargador de clases?

(1) Cargue el archivo de código de bytes en el área de datos de tiempo de ejecución. El archivo de código de bytes (.class) formado al compilar el código fuente .java a través del comando Javac se carga en el jvm a través del cargador de clases.

(2) Determine la exclusividad del área de datos del archivo de código de bytes en tiempo de ejecución. El mismo archivo de código de bytes puede formar diferentes archivos a través de diferentes cargadores de clases, por lo que la unicidad del área de datos del archivo de código de bytes en tiempo de ejecución está determinada por el archivo de código de bytes y el cargador de clases que lo carga.

2. Tipos de cargadores de clases

Divididos de la categoría, los cargadores de clases se dividen principalmente en cuatro categorías

(1) Inicie el cargador de clases (el cargador de clases raíz Bootstrap ClassLoader): este cargador de clases se encuentra en el nivel superior del cargador de clases y carga principalmente los paquetes jar relacionados con el núcleo de JRE, como /jre/lib/rt.jar

(2) Cargador de clases de extensión: este cargador de clases se encuentra en el segundo nivel de la jerarquía del cargador de clases y carga principalmente paquetes jar relacionados con la extensión JRE, como /jre/lib/ext/*.jar

(3) Aplicación ClassLoader App: este cargador de clases está ubicado en la tercera capa del cargador de clases, y carga principalmente paquetes jar relacionados bajo classpath (classpaht)

(4) Cargador de clases definido por el usuario (Cargador de clases de usuario): este cargador de clases es un cargador de clases definido por el usuario, que carga principalmente paquetes jar relacionados en la ruta especificada por el usuario

3. El mecanismo del cargador de clases (delegación de los padres)

Para la carga de código de bytes, el mecanismo de carga de clases es la delegación principal ¿Qué es la delegación principal?

Una vez que el cargador de clases obtiene el archivo de código de bytes, no lo carga directamente, sino que pasa el archivo de código de bytes a su cargador de clases principal directo, y su cargador principal directo continúa pasando a la carga principal directa de su cargador principal directo. Loader, y así sucesivamente al cargador principal raíz, si el cargador principal raíz

Si puede cargarse, cárguelo; de lo contrario, lo cargará su cargador secundario directo. Si el cargador secundario directo puede cargar, se cargará. Si no puede, el cargador de clases secundario directo se seguirá por analogía. Si no se puede cargar ninguno, lo cargará la clase definida por el usuario Cargador.

4. ¿Cómo implementar el cargador de clases en jdk 1.8?

La siguiente es la implementación del cargador de clases jdk 1.8, usando el método recursivo

protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }

                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    long t1 = System.nanoTime();
                    c = findClass(name);

                    // this is the defining class loader; record the stats
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

5. Destruye el modelo de delegación principal

En algunos casos, debido a la limitación del rango de carga, el cargador de clases padre no puede cargar el archivo requerido, por lo que el cargador de clases padre necesita delegar su cargador de subclases para cargar el archivo de código de bytes correspondiente.

Por ejemplo, la interfaz del controlador de la base de datos Driver definida en jdk, pero la implementación de esta interfaz es implementada por diferentes proveedores de bases de datos, lo que causa este problema: por la clase de inicio (Bootstrap ClassLoader)

El DriverManager ejecutado debe cargar las clases de implementación relevantes que implementan la interfaz del controlador para lograr una administración unificada, pero Bootstrap ClassLoader solo puede cargar los archivos correspondientes en jre / lib, no

Clases de implementación relacionadas con la interfaz de Dirver implementadas por varios proveedores (las clases de implementación de Dirver son cargadas por Application ClassLoader). En este momento, Bootstrap ClassLoader debe confiar su cargador de subclase para cargar Driver

Conseguir, destruyendo así el modelo de delegación parental.

Tres tipos de ciclos de vida

El ciclo de vida de las clases en java y jvm se divide aproximadamente en cinco etapas:

1. Etapa de carga: obtenga el flujo binario del código de bytes, convierta la estructura de almacenamiento estático en la estructura de datos en tiempo de ejecución del área de método y genere el objeto de clase correspondiente (objeto java.lang.Class) en el área de método como los datos de la clase Accede a la entrada.

2. Fase de conexión: esta fase incluye tres fases pequeñas, a saber, verificación, preparación y análisis.

(1) Verificación: asegúrese de que el archivo de código de bytes cumpla con los requisitos de la especificación de la máquina virtual, como verificación de metadatos, verificación de formato de archivo, verificación de código de bytes y verificación de símbolos, etc.

(2) Preparación: Asigne memoria para la tabla estática interna y establezca el valor predeterminado de jvm. Para las variables no estáticas, no es necesario asignar memoria en esta etapa.

(3) Análisis: convierta referencias de símbolos en el grupo constante en referencias directas

3. Etapa de inicialización: algunos trabajos de inicialización necesarios antes del uso del objeto de clase

Lo siguiente citado desde el punto de vista de un blogger, personalmente creo que la explicación es muy buena.

En código Java, si queremos inicializar un campo estático, podemos asignarlo directamente durante la declaración, o asignarlo en un bloque de código estático.

Excepto por las constantes modificadas estáticas finales, las operaciones de asignación directa y todos los códigos en bloques de código estático serán colocados en el mismo método por el compilador de Java y nombrados como <clinit>. El propósito de la inicialización es marcar como

Asignación de campo de valor constante y proceso de ejecución del método <clinit>. La máquina virtual Java asegura que el método <clinit> de la clase se ejecute solo una vez mediante el bloqueo.

¿En qué condiciones se producirá la inicialización de la clase?

(1) Cuando se inicia la máquina virtual, inicialice la clase principal (función principal) especificada por el usuario;

(2) Cuando encuentre la nueva instrucción para crear una nueva instancia de la clase de destino, inicialice la clase de destino de la nueva instrucción;

(3) Cuando se encuentra una instrucción para llamar a un método estático, inicialice la clase donde se encuentra el método estático;

(4) La inicialización de la subclase activará la inicialización de la clase principal;

(5) Si una interfaz define un método predeterminado, la inicialización de la clase que implementa directamente o implementa indirectamente la interfaz activará la inicialización de la interfaz;

(6) Cuando utilice la API de reflexión para realizar una llamada de reflexión a una clase, inicialice esta clase;

(7) Cuando se llama a la instancia de MethodHandle por primera vez, inicialice la clase del método al que apunta MethodHandle.

4. Use stage: use objetos en jvm

5. Etapa de descarga: descargando el objeto del jvm (unload), ¿qué condiciones harán que la clase jvm se descargue?

(1) El cargador de clases que cargó la clase se recicla

(2) Todas las instancias de esta clase se han reciclado.

(3) El objeto java.lang.Class correspondiente a esta clase no se hace referencia en ninguna parte

5 consejos para comprender a fondo el modelo de memoria JVM [para el desarrollo de Java durante 3 años]

Modelo de memoria de cuatro jvm

1. ¿Qué es el modelo de memoria JVM?

El siguiente es un diagrama de la arquitectura del modelo de memoria JVM. Como se discutió en el artículo anterior, no los discutiré uno por uno aquí, y principalmente explicaré el área del montón.

5 consejos para comprender a fondo el modelo de memoria JVM [para el desarrollo de Java durante 3 años]

Antes de jdk 1.8, el área del montón se dividía principalmente en generación joven, generación anterior y generación permanente. Después de jdk 1.8, se eliminó la generación permanente y se agregó el área MetaSpace. Aquí, comparta principalmente jdk 1.8.

Según jdk1.8, la lógica del área del montón se abstrae en tres partes:

(1) Nueva generación: incluida el área de Eden, el área S0 (también llamada de área), S21 (también llamada área de TO)

(2) Vejez

(3) Área de metaespacio

2. ¿Cuál es el tamaño de la memoria de la nueva generación y la generación anterior?

De acuerdo con las recomendaciones oficiales, la nueva generación representa un tercio (Eden: S0: S1 = 8: 1: 1) y la generación anterior representa dos tercios. Por lo tanto, el diagrama de asignación de memoria es el siguiente:

5 consejos para comprender a fondo el modelo de memoria JVM [para el desarrollo de Java durante 3 años]

3. ¿Cómo funciona la recuperación de GC?

El objeto corre primero en el área del Edén. Cuando la memoria del Edén está llena, Edén realizará dos operaciones: recuperar los objetos no utilizados y colocar los objetos no recuperados en el área s0. En este momento, el área s0 y el área s1 intercambian nombres, es decir, s0- > s1, s1-> s0, el área del Edén se recuperó una vez y el espacio se libera. Cuando el Edén se llena de nuevo la próxima vez, se ejecutan y ejecutan los mismos pasos a su vez. Cuando se recupera el área del Edén, los objetos restantes exceden la capacidad s0. Se activará un GC menor. En este momento, los objetos no recuperados se colocarán en el área antigua y se ejecutarán en un bucle. Cuando el área Edén activa el GC menor y la capacidad del objeto restante es mayor que la capacidad restante del área anterior, el área anterior activará un GC mayor , En este momento se activará un GC completo. Cabe señalar que, en general, Major GC irá acompañado de una recuperación de GC completa. La GC completa consume mucho rendimiento. Preste atención al ajuste de JVM.

La siguiente imagen es una imagen de GC tomada por mí en el entorno de producción, la herramienta de monitoreo VisualVM

5 consejos para comprender a fondo el modelo de memoria JVM [para el desarrollo de Java durante 3 años]

4. ¿Cuáles son los algoritmos de recolección de basura?

(1) algoritmo Mark-clear

El algoritmo se divide en dos etapas, a saber, la etapa de marcado y la etapa de limpieza: en primer lugar, se marcan todos los objetos a reciclar y luego se reciclan los objetos marcados. Este algoritmo es ineficaz y propenso a la fragmentación de la memoria.

a. Baja eficiencia: es necesario recorrer la memoria dos veces, marcar la primera vez y reciclar el objeto marcado la segunda vez

b. Debido a que es un segmento de memoria no contiguo, es propenso a la fragmentación. Cuando el objeto es demasiado grande, la GC completa es propensa a ocurrir

La figura siguiente es un diagrama esquemático de la comparación del algoritmo de barrido de marcas antes y después de la recuperación.

5 consejos para comprender a fondo el modelo de memoria JVM [para el desarrollo de Java durante 3 años]

(2) Algoritmo de copia de marca

Este algoritmo resuelve el problema de la baja eficiencia del algoritmo "marcar y barrer" y la mayor parte de la fragmentación de la memoria. Divide la memoria en dos bloques de igual tamaño y solo usa un bloque a la vez. Cuando se necesita reciclar un bloque, solo el área del bloque Los objetos supervivientes se copian en otro bloque, y luego el bloque de memoria se limpia de una vez y el ciclo se repite.

La siguiente figura es un diagrama esquemático del algoritmo de copia de marca antes y después del reciclaje.

5 consejos para comprender a fondo el modelo de memoria JVM [para el desarrollo de Java durante 3 años]

Sin embargo, debido a que la mayoría de los objetos de la generación joven tienen un tiempo de residencia muy corto, el 98% de los objetos se reciclan rápidamente y hay muy pocos objetos supervivientes. No es necesario dividir la memoria de acuerdo con 1: 1, sino de acuerdo con 8: 1: 1. Dividir,

Ponga el 2% de los objetos supervivientes en s0 (del área).

El siguiente es un diagrama esquemático de dividir según el Edén: s0: s1 = 8: 1: 1

5 consejos para comprender a fondo el modelo de memoria JVM [para el desarrollo de Java durante 3 años]

(3) Algoritmo de clasificación y marcado

El algoritmo se divide en dos etapas, marcado y clasificación: primero, todos los objetos supervivientes se marcan, estos objetos se mueven a un extremo y luego la memoria fuera del límite final se limpia directamente. Dado que los objetos en la vejez viven más tiempo, este algoritmo es adecuado.

El proceso de marcado sigue siendo el mismo que el proceso de "marcado-borrado", pero los pasos siguientes no son limpiar directamente los objetos reciclables, sino mover todos los objetos supervivientes a un extremo y luego limpiar directamente la memoria fuera del límite final.

A continuación, se muestra un diagrama esquemático del período de recuperación y la recuperación posterior del "algoritmo de clasificación de marcas".

5 consejos para comprender a fondo el modelo de memoria JVM [para el desarrollo de Java durante 3 años]

(4) Algoritmo de recopilación generacional

El algoritmo es el algoritmo JVM actual, utilizando el pensamiento generacional, el modelo es el siguiente:

5 consejos para comprender a fondo el modelo de memoria JVM [para el desarrollo de Java durante 3 años]

5. ¿Cuáles son los colectores GC comunes?

(1)SerialGC

SerialGC también se llama colector en serie, y también es el colector GC más básico. Es principalmente adecuado para CPU de un solo núcleo. La nueva generación adopta el algoritmo de replicación y la generación anterior adopta el algoritmo de compresión de marcas. La aplicación debe suspenderse durante el funcionamiento.

Por lo tanto, causará problemas de STW. Marque el parámetro en JVM como: -XX: + UseSerialGC.

(2) Paralelo GC

ParallelGC se basa en SerialGC. Resuelve principalmente el problema serial de SerialGC. Se cambia a un problema paralelo para resolver el problema de múltiples subprocesos, pero también causará problemas STW. Parámetros clave de Jvm:

a.-XX: + UseParNewGC, que significa la nueva generación en paralelo (algoritmo de replicación) la antigua serie (marca-compresión)

b.XX: + UseParallelOldGC, la vejez también es paralela

(3) CMS GC

CMSGC pertenece al recopilador de la vejez. Adopta el "algoritmo de barrido de marcas" y no causará problemas de STW. La configuración de los parámetros en jvm:

-XX: + UseConcMarkSweepGC, lo que significa que la vejez usa el recopilador CMS

(4) Primero la basura

Garbage First está orientado al recolector de basura jvm. Satisface una pausa corta mientras logra un alto rendimiento. Es adecuado para CPUs multi-core y servidores de gran memoria. También es el recolector de basura predeterminado de jdk9.

Cinco resumen

Análisis en profundidad del modelo de memoria JVM, que se centra en el análisis de la relación entre jdk, jre y jvm, cargador de clases jvm, división de memoria de pila jvm, recolector de GC y algoritmo de reciclaje de GC, etc. El sesgo general es teórico, debido al espacio limitado, el editor lo organizará en consecuencia Más de 400 páginas de notas de aprendizaje sobre el ajuste del rendimiento de JVM, centrándose en GongZhouhao: Kylin cambia los errores y lo comparte con usted. Este artículo no analiza cómo se utilizan estas tecnologías en el ajuste real de JVM. Comparte contigo en el artículo.

Supongo que te gusta

Origin blog.51cto.com/14994509/2596518
Recomendado
Clasificación