Optimización del rendimiento | Análisis completo del mecanismo de carga de clases

Proceso de ejecución de código local:

  • Primero compile el archivo java en un archivo de clase a través del comando javac
  • Luego, la máquina virtual carga el archivo de clase en la memoria (puede provenir de la red)
  • La máquina virtual verifica si el archivo de clase es legal
  • Asignar memoria para variables estáticas de la clase y realizar procesamiento de valor cero
    • Tipo básico (tipo entero: 0, tipo booleano: falso ...)
    • Tipo de referencia: establecido en vacío
  • Resolver referencias de símbolos
    • Resolver referencias simbólicas en referencias directas. Las referencias directas se refieren a direcciones o identificadores de memoria específicos. Este es un proceso de vinculación estática. Si las referencias de símbolos se resuelven en referencias directas durante el tiempo de ejecución, se denominan referencias dinámicas.
  • inicialización
    • Empiece a ejecutar declaraciones de inicialización en el código, incluidos bloques de código estático y operaciones de asignación a variables estáticas.
      Inserte la descripción de la imagen aquí

El siguiente es el proceso de uso y desinstalación.

¿Qué tipo de cargadores de clases hay en JVM?

El cargador de clases es para cargar el archivo de clases en el jvm.

  • Bootstrap Classloader: escrito en lenguaje C, responsable de cargar todos los archivos de clase en el directorio lib en el entorno jre
  • Extensión Classloader: cargue jre \ lib \ ext * y escriba todos los archivos de clase
  • Cargador de clases de aplicación: cargue todos los archivos de clase en la ruta de clase, que es el código que escribimos.
  • Cargador de clases personalizado: cargue el archivo de código de bytes que necesita cargar a pedido

Verifique los archivos de clase cargados por los tres cargadores:

    public static void main(String[] args) {
    
    
        System.out.println("bootstrapLoader加载以下文件:");
        URL[] urls = Launcher.getBootstrapClassPath().getURLs();
        for (int i = 0; i < urls.length; i++) {
    
    
            System.out.println(urls[i]);
        }
        System.out.println();
        System.out.println("extClassloader加载以下文件:");
        System.out.println(System.getProperty("java.ext.dirs"));

        System.out.println();
        System.out.println("appClassLoader加载以下文件:");
        System.out.println(System.getProperty("java.class.path"));
    }

Inserte la descripción de la imagen aquí

Se puede ver en la figura que el par de carga de clases solo cargará la parte del archivo de clase de la que es responsable en la memoria.

Proceso de inicialización del cargador de clases

Observamos el contenido del método de construcción del lanzador (Launch) para averiguar cómo se inicializa el cargador de clases

    public Launcher() {
    
    
        Launcher.ExtClassLoader var1;
        try {
    
    
            var1 = Launcher.ExtClassLoader.getExtClassLoader();
        } catch (IOException var10) {
    
    
            throw new InternalError("Could not create extension class loader", var10);
        }

        try {
    
    
            this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
        } catch (IOException var9) {
    
    
            throw new InternalError("Could not create application class loader", var9);
        }}

En el método de construcción, puede ver que el sistema ha creado dos cargadores, a saber:
ExtClassLoader y AppClassLoader, el cargador de clases que usamos generalmente por defecto es usar

            this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);

Este cargador está cargado, y lo que generalmente llamamos
Class.class.getClassLoader()retornos del método es este cargador inicializado

¿Cuál es la relación entre estos tres cargadores de clases?

Ejecutamos el siguiente código

 public static void main(String[] args) {
    
    
        System.out.println(TestJVMClassLoader.class.getClassLoader().toString());
        System.out.println(TestJVMClassLoader.class.getClassLoader().getParent().toString());
        System.out.println(TestJVMClassLoader.class.getClassLoader().getParent().getParent());
    }

Inserte la descripción de la imagen aquí

Se puede encontrar que el cargador de clases que carga el código que creamos es AppClassLoader, la clase principal de AppClassLoader es ExtClassLoader y la clase principal de ExtClassLoader es nula. Aquí hay dos cargadores de clases y también hay un cargador de clases de arranque. Si no adivina mal, debería ser nulo, entonces, ¿por qué es nulo?

Recuerde que dijimos anteriormente que el cargador de clases de arranque está escrito en lenguaje C. Ya que está escrito en lenguaje C, ¿cómo se puede imprimir aquí? Entonces ahora hacemos un dibujo para resolver la relación entre estos tres:
Inserte la descripción de la imagen aquí

¿Qué diablos es el mecanismo de delegación de padres?

Muchos alumnos han oído hablar de este término, pero no hay ningún artículo que pueda explicar claramente qué es un mecanismo de delegación parental, hoy utilizo lo que he aprendido a lo largo de mi vida para que los alumnos comprendan plenamente qué es un mecanismo de delegación parental.

¿Qué es el mecanismo de delegación de padres?

Cuando miramos el código fuente directamente, es fácil entender qué es la delegación de los padres;

 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;
        }
    }

Déjame explicarte el proceso de ejecución de este código:

  1. Use el método findLoadedClass para encontrar el objeto de clase en la memoria, si no, continúe buscando
  2. Si padre no está vacío, se refiere a ExtClassLoader, el cargador de clases padre de AppClassLoader. Se llama a LoadClass a través de ExtClassLoader para cargar archivos de clase.
  3. Si el padre está vacío, significa que el cargador actual ya es ExtClassLoader. En este momento, BootstrapClass se llama directamente para cargar;
  4. Si el objeto de clase se ha encontrado en este momento, se puede devolver directamente. Si aún no se encuentra, se debe llamar al método findCLass implementado por el cargador de subclase. Para cargar nuestro archivo de clase personalizado.
  5. Si el cargador de subclases no implementa el método findClass, podemos ver que la implementación predeterminada de la clase padre es: lanza directamente la excepción classNotFound, generalmente si personalizamos la carga de la clase, solo necesitamos implementar el método findClass.
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        throw new ClassNotFoundException(name);
    }

Usemos una imagen para ilustrar el proceso de búsqueda de "clase"
Inserte la descripción de la imagen aquí

De hecho, el último paso es encontrar una forma por ti mismo, que consiste en implementar el método findclass de la clase principal.
Después de leer esta explicación, todos deberían tener una comprensión general de la delegación de padres. Si realmente lee este proceso con atención, creo que definitivamente tendrá preguntas:

Si necesita cargar este código de bytes, ¿por qué no? Si llama a su propio método findclass directamente, tienes que buscar nivel por nivel ¿Por qué la JVM configura un mecanismo de delegación padre?

¿Por qué diseñar un mecanismo de delegación de padres?

  • Mecanismo de seguridad de la zona de pruebas: evita que la biblioteca principal de API sea manipulada a voluntad
  • Evite la carga repetida de clases: cuando el padre ya ha cargado la clase, no es necesario volver a cargar el ClassLoader secundario para garantizar la unicidad de la clase cargada.
    Verifiquemos si el mecanismo de delegación principal de JVM es realmente efectivo:
    ejecutamos lo siguiente código, compañero de clase Vamos a adivinar cuál es el resultado de la ejecución.
package java.util;
public class Date {
    public static void main(String[] args) {
        System.out.println("我被执行");
    }
}

Resultados de:
Inserte la descripción de la imagen aquí

¿Por qué sucede esto? ¿Por qué no se puede encontrar el método principal?
De hecho, este es el mecanismo de delegación padre en funcionamiento, porque ya existe una clase Date con el mismo nombre de paquete en el sistema Java.Cuando ejecutamos nuestro método principal, primero debe cargar la clase Date. De acuerdo con el mecanismo de delegación principal, AppClassLoader primero pregunta si el cargador principal ha cargado esta fecha. Después de preguntar, se encuentra que la clase principal ya ha cargado esta clase, por lo que AppClass no debería volver a cargarla por sí misma y usar directamente el sistema. Fecha cargada por el cargador principal. Clase, pero la clase Fecha del sistema no tiene un método principal. Es por eso que ocurre el error anterior.

Implementar manualmente un cargador de clases

package com.lezai;

import java.io.FileInputStream;
import java.lang.reflect.Method;

public class CustomClassloaderTest {
    
    
    static class CustomClassloader extends ClassLoader{
    
    
        private String classPath = "/Users/yangle/Desktop";

        /**
         * 自己实现查找字节码文件的逻辑,可以来自本地磁盘,也可以是来自网络
         * @param name
         * @return
         * @throws ClassNotFoundException
         */
        @Override
        protected Class<?> findClass(String name) throws ClassNotFoundException {
    
    
            try {
    
    
                byte[] data = loadByte(name);
                return defineClass(name, data, 0, data.length);
            } catch (Exception e) {
    
    
                e.printStackTrace();
            }
            return null;
        }
        // 将文件读取到字节数组
        private byte[] loadByte(String name) throws Exception {
    
    
            name = name.replaceAll("\\.", "/");
            FileInputStream fis = new FileInputStream(classPath + "/" + name
                    + ".class");
            int len = fis.available();
            byte[] data = new byte[len];
            fis.read(data);
            fis.close();
            return data;

        }
    }
    public static void main(String[] args) {
    
    
        CustomClassloader classLoader = new CustomClassloader();
        try {
    
    
            Class clazz = classLoader.loadClass("com.lezai.Test");
            Object obj = clazz.newInstance();
            Method method= clazz.getDeclaredMethod("out", String.class);
            method.invoke(obj,"乐哉");
            System.out.println(clazz.getClassLoader());
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
    }
}

Pasos de implementación:

  • La clase personalizada hereda la clase ClassLoader
  • Vuelva a escribir el método findClass para darse cuenta de la lógica de encontrar archivos de código de bytes por sí mismo
  • Si no desea cumplir con el mecanismo de delegación principal, puede implementar el método loadClass y ya no preguntar si el archivo de código de bytes que necesitamos se ha cargado en la clase principal.

Cómo romper el mecanismo de delegación parental

Si necesitamos romper el mecanismo de delegación padre, solo necesitamos implementar el método loadClass por nosotros mismos, ya no preguntar si el archivo de código de bytes que necesitamos se ha cargado en la clase padre, y luego llamar directamente a findClass para cargar nuestra clase.

¿Por qué Tomcat rompe el mecanismo de delegación parental?

Tome la carga de clases de Tomcat como ejemplo. Si Tomcat usa el mecanismo de carga de clases de delegación principal predeterminado, ¿funcionará?

  • Pensemos en ello: Tomcat es un contenedor web, entonces, ¿qué problema resuelve?
  1. Un contenedor web puede necesitar implementar dos aplicaciones. Diferentes aplicaciones pueden depender de diferentes versiones de la misma biblioteca de clases de terceros. No es necesario que la misma biblioteca de clases tenga solo una copia en el mismo servidor. Por lo tanto, es necesario Asegúrese de que las bibliotecas de clases de cada aplicación sean independientes, lo que garantiza el aislamiento mutuo.

  2. Se puede compartir la misma versión de la misma biblioteca de clases implementada en el mismo contenedor web. De lo contrario, si el servidor tiene 10 aplicaciones, entonces se deben cargar 10 copias de la misma biblioteca de clases en la máquina virtual.

  3. El contenedor web también tiene su propia biblioteca de clases dependiente, que no debe confundirse con la biblioteca de clases de la aplicación. Según consideraciones de seguridad, la biblioteca de clases del contenedor debe aislarse de la biblioteca de clases del programa.

  4. El contenedor web debe admitir la modificación de jsp. Sabemos que el archivo jsp debe compilarse en un archivo de clase para ejecutarse en la máquina virtual. Sin embargo, es común modificar el jsp después de que se ejecuta el programa. El contenedor web debe Admite la modificación de jsp sin reiniciar.

  • Veamos nuestra pregunta nuevamente: ¿Puede Tomcat usar el mecanismo de carga de clases de delegación principal predeterminado?
    La respuesta es no. ¿por qué?
  1. La primera pregunta es que si usa el mecanismo de cargador de clases predeterminado, no puede cargar dos versiones diferentes de la misma biblioteca de clases. El sumador de clases predeterminado no importa en qué versión se encuentre, y solo se preocupa por su nombre de clase completamente calificado. es solo uno.

  2. El segundo problema es que el cargador de clases predeterminado se puede lograr porque su responsabilidad es garantizar la unicidad.

  3. La tercera pregunta es la misma que la primera pregunta.

  4. Veamos la cuarta pregunta de nuevo. Pensamos en cómo podemos lograr la carga en caliente de archivos jsp. Los archivos jsp son en realidad archivos de clase. Si los modifica, pero el nombre de la clase sigue siendo el mismo, el cargador de clases tomará directamente el archivo existente. área de método. Sí, el jsp modificado no se volverá a cargar. ¿Entonces lo que hay que hacer? Podemos descargar directamente el cargador de clases del archivo jsp, por lo que debería haber pensado que cada archivo jsp corresponde a un cargador de clases único.Cuando se modifica un archivo jsp, el cargador de clases jsp se descarga directamente. Vuelva a crear el cargador de clases y vuelva a cargar el archivo jsp.

  • Los principales cargadores de clase en tomcat

    • commonLoader: el cargador de clases más básico de Tomcat; el contenedor de Tomcat y cada aplicación web pueden acceder a las clases en la ruta de carga;
    • catalinaLoader: cargador de clases privadas del contenedor Tomcat, la clase en la ruta de carga no es visible para Webapp;
    • sharedLoader: el cargador de clases compartido por cada Webapp, la clase en la ruta de carga es visible para todas las Webapps, pero no para el contenedor Tomcat;
    • WebappClassLoader: cada cargador de clases privadas de Webapp, la clase en la ruta de carga solo es visible para la Webapp actual, como la carga de clases relacionadas en el paquete war, cada aplicación de paquete war tiene su propio WebappClassLoader para lograr un aislamiento mutuo, como un paquete war diferente aplicaciones Introdujo diferentes versiones de resorte, de modo que las implementaciones puedan cargar sus respectivas versiones de resorte;
  • Diagrama de la relación entre varios cargadores de clases
    Inserte la descripción de la imagen aquí

Wechat busca una búsqueda [Charla abierta de Le Zai] Sigue al guapo yo, responde [Recibir productos secos], habrá una gran cantidad de materiales para entrevistas y libros de lectura obligatoria para arquitectos esperando que elijas, incluidos conceptos básicos de Java, concurrencia de Java, microservicios, middleware, etc. Más información le está esperando.

Supongo que te gusta

Origin blog.csdn.net/weixin_34311210/article/details/109232056
Recomendado
Clasificación