Aprendizaje de JVM (cuatro) carga de clase

El problema

  • Mecanismo de carga de clases, hay varios pasos para cargar una clase en una máquina virtual. Qué orden de estos pasos es fijo y cuál no, ¿por qué no?

Respuesta: 1. Cargar 2. Verificar 3. Preparar 4. Análisis estático (no fijo) 5. Inicializar 6. Usar 7. Análisis dinámico (no fijo) 8. Descargar la conexión de referencia: https://www.jianshu.com/ p / 2a3cdc027c2c

Resumen

  • La carga de clases Java da principalmente tres pasos
  1. Cargando
  2. Enlace (también dividido en tres pequeños pasos de verificación, preparación y análisis)
  3. Inicializar
  • Hay un principio importante en la carga: el modelo de delegación principal.

Cargando

El primer paso en el proceso de carga de clases es completar las siguientes tres cosas:

  1. Obtenga la secuencia de bytes binarios que define esta clase por el nombre completo de la clase
  2. Convierta la estructura de almacenamiento estática representada por la secuencia de bytes en la estructura de datos de tiempo de ejecución del área de método
  3. Genere un objeto de clase que represente esta clase en la memoria como acceso a estos datos en el área de método

identificador de clase java

En tiempo de ejecución, la identidad de una clase está determinada no solo por su nombre, sino también por su nombre binario y el cargador que lo define.Cada clase o interfaz pertenece a un paquete de tiempo de ejecución separado. La definición de clases e interfaces en este paquete de tiempo de ejecución está determinada por el nombre del paquete y el cargador que lo define.

Sabemos que hay dos tipos de tipos de lenguaje Java: tipos de referencia y tipos primitivos. Los tipos básicos están predefinidos por la máquina virtual Java. Los tipos de referencia están divididos en cuatro tipos por Java: clases, interfaces, Clase de matriz y parámetros genéricos. Dado que los parámetros genéricos se borrarán durante la compilación, en realidad solo hay tres tipos de máquinas virtuales java: interfaces, clases y matrices.

El cargador de clases carga las interfaces y las clases, mientras que el cargador de clases no carga los tipos de matriz, sino el jvm.

Resumen del proceso de carga

La siguiente descripción proviene de: https://www.ibm.com/developerworks/cn/java/j-lo-classloader/index.html

Al presentar el modo proxy del cargador de clases anteriormente, se mencionó que el cargador de clases primero se proxy al otro cargador de clases para intentar cargar una determinada clase. Esto significa que el cargador de clases que realmente completa el trabajo de carga de clases y el cargador de clases que inició el proceso de carga pueden no ser los mismos. La carga de la clase se realiza llamando a defineClass; el proceso de carga de la clase de inicio se logra llamando a loadClass. El primero se llama un cargador de definición de clase, y el segundo se llama el cargador inicial. Cuando la máquina virtual Java determina si dos clases son iguales, se usa el cargador de definición de clase. En otras palabras, no importa qué cargador de clases inicie el proceso de carga de clases, lo que importa es que el cargador de clases finalmente esté definido. La relación entre los dos cargadores de clases es que el cargador de definiciones de una clase es el cargador inicial de otras clases a las que hace referencia. Si la clase com.example.Outer se refiere a la clase com.example.Inner, el cargador de definiciones de la clase com.example.Outer es responsable de iniciar el proceso de carga de la clase com.example.Inner.

Carga comisionada por el padre

1297993-20200416102038427-116171272.jpg

Cabe señalar que los cargadores de clases anteriores no son relaciones heredadas (extendidas) sino relaciones de retención combinadas. Cuando una subclase carga una clase, primero se entregará a la clase primaria para cargarla. Si la clase primaria se carga Después de eso, la clase primaria devuelve la clase cargada. Si la clase primaria no se puede cargar, entonces el cargador de clases secundaria la cargará.

public abstract class ClassLoader { 

    private static nativo void registerNatives (); 
    static { 
        registerNatives (); 
    } 

    // El cargador de clases padre para la delegación 
    // Nota: VM codificó el desplazamiento de este campo, por lo tanto, todos los campos nuevos 
    // deben agregarse * después * de él. 
    // 持有 的 父 类 类 加载 器
    privado final ClassLoader parent; 


    Clase protegida <?> loadClass (nombre de cadena, resolución booleana) 
        arroja ClassNotFoundException 
    { 
        sincronizado (getClassLoadingLock (nombre)) { 
            // Primero, verifique si la clase ya se ha cargado 
            Clase <?> c = findLoadedClass (nombre);
            if (c == null) {
                t0 largo = System.nanoTime (); 
                intente { 
                    // 先到 父 类 那里 进行 加载
                    if (parent! = null) { 
                        c = parent.loadClass (name, false); 
                    } else { 
                        c = findBootstrapClassOrNull (nombre); 
                    } 
                } catch (ClassNotFoundException e) { 
                    // ClassNotFoundException lanzada si no se encuentra la clase 
                    // del cargador de clases padre no nulo 
                } 

                if (c == null) { 
                    // Si aún no se encuentra, invoque findClass en orden 
                    // para Encuentra la clase.
                    t1 largo = System.nanoTime (); 
                    c = findClass (nombre); 

                    // este es el cargador de clases definitorio; registrar las estadísticas 
                    sun.misc.PerfCounter.getParentDelegationTime (). addTime (t1 - t0); 
                    sun.misc.PerfCounter.getFindClassTime (). addElapsedTimeFrom (t1); 
                    sun.misc.PerfCounter.getFindClasses (). increment (); 
                } 
            } 
            if (resolver) { 
                resolveClass (c); 
            } 
            retorno c; 
        } 
    } 

}


Anotación del método loadClass.

Carga la clase con el nombre binario especificado. La implementación predeterminada de este método busca clases en el siguiente orden: 
1. Invoque findLoadedClass (String) para verificar si la clase ya se ha cargado. 
2. Invoque el método loadClass en el cargador de clases padre. Si el padre es nulo, se usa el cargador de clases integrado en la máquina virtual. 
3. Invoque el método findClass (String) para encontrar la clase. 

Si se encontró la clase utilizando los pasos anteriores, y el indicador de resolución es verdadero, este método invocará el método resolveClass (Clase) en el objeto Clase resultante. 

Se alienta a las subclases de ClassLoader a anular findClass (String), en lugar de este método.
A menos que se anule, este método se sincroniza con el resultado del método getClassLoadingLock durante todo el proceso de carga de clases.

Donde

  • Bootstrap ClassLoader: se usa para cargar la biblioteca central de Java. Es principalmente el directorio jre / lib (de la variable de entorno sun.boot.class.path). Está escrito en C ++ y es parte de la máquina virtual en sí, y no se puede hacer referencia a él en código Java.
  • Extensiones ClassLoader: se utiliza para cargar la biblioteca de extensiones de Java.
  • Application ClassLoader: utilizado para cargar la clase de biblioteca en la aplicación Java en ejecución, puede obtenerla a través de ClassLoader.getSystemClassLoader (). Por ejemplo, la clase de biblioteca en el archivo pom.xml.

El proceso de carga del código anterior es el modo proxy

El modo proxy es para garantizar la seguridad del tipo de la biblioteca central de Java. Todas las aplicaciones Java deben al menos referirse a la clase java.lang.Object, lo que significa que, en tiempo de ejecución, la clase java.lang.Object debe cargarse en la máquina virtual Java. Si este proceso de carga se completa con el cargador de clases propio de la aplicación Java, puede haber varias versiones de la clase java.lang.Object, y estas clases son incompatibles. A través del modo proxy, el cargador de clases de arranque completa la carga de las clases de la biblioteca central de Java, lo que garantiza que las aplicaciones Java utilicen la misma versión de la clase de la biblioteca central de Java y sean compatibles entre sí.

El siguiente es un diagrama esquemático de la carga encargada principal, la imagen es de materiales de referencia:

1297993-20200416135145267-264158634.jpg

¿Por qué usar el modelo de delegación principal?

Una cosa es para la seguridad: si la clase básica java.lang.Object se carga en la clase principal y un desarrollo carga el mismo objeto desde otro lugar, fallará porque la clase principal ya se ha cargado, lo que también garantiza La seguridad de la carga de clases principales en Java.

Destrucción del modelo de delegación padre

En primer lugar, necesitamos saber qué se considera roto. Antes de que se cargue el modo de delegación principal, debe entregarse al principal para cargarlo. El significado de la destrucción es: el cargador de subclase ya no se carga desde el cargador de clases principal, sino que se carga solo.

Ejemplos de destrucción de la delegación parental.

Fuente de ejemplo: https://juejin.im/post/5a59f2296fb9a01ca871eb8c

¿Qué pasa si la clase base llama al código del usuario? Un ejemplo típico es el servicio JNDI. JNDI ahora es un servicio estándar de Java. Su código es cargado por el cargador de clases de inicio (rt.jar que se puso en JDK1.3), pero debe ser llamado por un proveedor independiente. E implemente el código del proveedor de interfaz JNDI (SPI, Service Provider Interface) en ClassPath de la aplicación, pero es imposible que el cargador de clases de inicio "conozca" estos códigos. Debido a que estas clases no están en rt.jar, pero el cargador de clases de inicio debe cargarse. Que debo hacer

El equipo de Java introdujo un cargador de clases de contexto de hilo.

Cargador de clases de contexto de subprocesos

El cargador de clase de contexto de subproceso (cargador de clase de contexto) se introdujo desde JDK 1.2. Los métodos getContextClassLoader () y setContextClassLoader (ClassLoader cl) en la clase java.lang.Thread se usan para obtener y establecer el cargador de clases de contexto del hilo. Si no se establece mediante el método setContextClassLoader (ClassLoader cl), el subproceso heredará el cargador de clases de contexto de su subproceso principal. El cargador de clases de contexto del subproceso inicial que ejecuta la aplicación Java es el cargador de clases del sistema (Application ClassLoader). El código que se ejecuta en un hilo puede cargar clases y recursos a través de este cargador de clases.

Obtener el cargador de contexto de hilo actual

Thread.currentThread (). GetContextClassLoader (); 
ClassLoader.getSystemClassLoader ();

Con el cargador de contexto de subprocesos, el servicio JNDI utiliza este cargador de contexto de subprocesos para cargar el código SPI requerido, es decir, el cargador de clases primario solicita al cargador de clases secundario que complete la acción de carga de clases. La jerarquía del modelo de delegación para revertir el uso de cargadores de clases en realidad viola los principios generales del modelo de delegación principal

Use un cargador de clases personalizado

Cree un programa java bajo una ruta de la siguiente manera:

Prueba de clase pública { 
    int int privado; 
    nombre de cadena privado; 
    Prueba pública () { 
        this.age = 15; 
        this.name = "Aaaa"; 
    } 
}

A continuación, javac Test.javagenerar el archivo de clase.

clase pública MyClassLoader extiende ClassLoader { 


    @Override 
    Clase protegida <?> findClass (String name) lanza ClassNotFoundException { 
        File file = getClassFile (name); 
        pruebe { 
            byte [] bytes = getClassBytes (archivo); 
            return defineClass (nombre, bytes, 0, bytes.length); 
        } catch (Excepción e) { 
            e.printStackTrace (); 
        } 
        return super.findClass (nombre); 
    } 

    archivo privado getClassFile (nombre de cadena) { 

        devolver un nuevo archivo ("C: \\ Users \\ Administrator \\ Desktop \\ Test.class"); 
    } 

    byte estático privado [] getClassBytes (archivo de archivo) arroja la excepción {
        FileInputStream fis = nuevo FileInputStream (archivo); 
        ByteArrayOutputStream aos = new ByteArrayOutputStream (fis.available ()); 
        byte [] bytes = nuevo byte [fis.available ()]; // 使用 fis.avaliable () 方法 确保 整个 字节 数组 没有 多余 数据
        fis.read (bytes); 
        aos.write (bytes); 
        fis.close (); 
        devuelve aos.toByteArray (); 
    } 

    public static void main (String [] args) lanza Exception { 
        MyClassLoader ct = new MyClassLoader (); 
        Clase c = Class.forName ("Prueba", verdadero, ct); 
        System.out.println (c.getClassLoader ()); 
    } 
}

Corre para ver el resultado.

Suplemento

  • El modelo de delegación principal también se destruye en tomcat
  • Para obtener recomendaciones de implementación en caliente, consulte "Comprensión exhaustiva de la máquina virtual Java". Los blogs en línea están escritos en un lío.

Referencias

  • https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-5.html#jvms-5.3.2 (documento oficial)
  • https://www.cnblogs.com/aspirant/p/8991830.html (ver)
  • https://www.cnblogs.com/huxuhong/p/11856786.html (ver)
  • https://www.jianshu.com/p/9df9d318e838

Supongo que te gusta

Origin www.cnblogs.com/Benjious/p/12712816.html
Recomendado
Clasificación