Conocimiento profundo del proceso de carga de clase jvm

Este artículo es para leer notas. Personalmente creo que esta parte es muy importante en comparación con la recolección de basura, así que quiero mantener una nota

1. El ciclo de vida de una clase.

Cargar -> Autenticación -> Preparar -> análisis sintáctico -> Inicialización -> Uso -> Desinstalar
Inserte la descripción de la imagen aquí
puntos:

  • Se determina la secuencia de las cinco etapas de carga, verificación, preparación, inicialización y descarga.
  • La fase de análisis puede comenzar después de la fase de inicialización en algunos casos
  • La "carga" real de la primera etapa del proceso de carga de clase no está claramente definida
  • Pero en seis casos, la clase debe "inicializarse" inmediatamente:
    1. Cuando encuentre las cuatro instrucciones de bytecode new, getstatic, putstatic o invokestatic, si el tipo no se ha inicializado, primero debe activar su fase de inicialización.

Las condiciones de activación de estas cuatro instrucciones son:

  • Cuando se usa la nueva palabra clave para crear una instancia de un objeto.
  • Leer o establecer un tipo de campo estático
  • Al llamar a un método estático de una clase

2. Reflejar llamada, inicializar si no está inicializado
3. Ejecución del método principal clase principal
4. El manejador del método de reflexión se resuelve en REF_getStatic, REF_putStatic, REF_invokeStatic, REF_newInvokeSpecial. La clase correspondiente no se ha inicializado
5. El método de interfaz modificado por la palabra clave predeterminada es La clase implementada se inicializa primero
6. Cuando se inicializa la clase, si la clase primaria no se inicializa, se activa la inicialización de la clase primaria.
Solo estos seis escenarios representan referencias activas a un tipo

Caso de implementación:

  • Hacer referencia a campos estáticos definidos en la clase primaria a través de sus subclases solo activará la inicialización de la clase primaria y no las subclases.
  • Las constantes se agregarán al grupo de constantes de la clase en tiempo de compilación. En esencia, no existe una referencia directa a la clase que define la constante, por lo que no activará la inicialización
  • La matriz no activará la inicialización de la clase. La razón es que este tipo de máquina virtual genera automáticamente una subclase que hereda directamente de java.lang.Object. La acción de creación se activa mediante la instrucción byarcode newarray.
    Cuando se considera si una clase se inicializa, considere si se hace referencia pasiva, es decir, la inicialización de la clase es diferida.
    Hay materiales para probar aquí. Lo enumero y no causará la inicialización.
  • Objeto de clase
  • Método LoadClass de carga de clases
  • Cuando el segundo parámetro de Class.forname () es falso
  • La interfaz también tiene un proceso de inicialización, que es casi lo mismo que la clase

Comprensión personal de la inicialización de la clase: de
hecho, se ha mencionado anteriormente que la inicialización de la clase es lenta, lo que puede ahorrar espacio en la memoria. Por supuesto, algunos programas enfatizan un proceso de precalentamiento. Si lo precalentamos todo al principio, será No es necesario inicializar

Ejemplo:
Inserte la descripción de la imagen aquí
Prueba 1:
Inserte la descripción de la imagen aquí
Inserte la descripción de la imagen aquí
Prueba 2:
Inserte la descripción de la imagen aquí
Inserte la descripción de la imagen aquí
¿Cómo puedo activar la inicialización de MM?
Inserte la descripción de la imagen aquí
Luego ejecute el código anterior:
Inserte la descripción de la imagen aquí
Esto lleva a una conclusión: solo los tipos básicos y String pueden usarse como referencias directas a su propio grupo constante.
Otros tipos de referencia pueden usarse como clases finales. Se inicia la inicialización de esta clase, es decir, <cinit>, y el trabajo principal de <cinit> es inicializar las variables miembro definidas en la clase.

2. El proceso de carga de clases.

2.1 Fase de "carga"

Por favor, comprenda que la etapa de "Carga" es una etapa en todo el proceso de "Carga de clase"

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

Nota:

  • El cargador de clases no crea la clase de matriz en sí, pero el cargador de clase todavía carga el tipo de elemento de la clase de matriz.
  • La fase de carga no se ha completado, la fase de conexión puede haber comenzado

2.2 Fase de verificación

El propósito es asegurar que la información contenida en el flujo de bytes del archivo de Clase cumpla con todas las restricciones de la "Especificación de máquina virtual Java", la etapa de garantía de la seguridad del lenguaje Java

1. Verificación del formato de archivo

  • Si comienza con el número mágico 0xCAFEBABE.
  • Si los números de versión mayor y menor están dentro del rango aceptado por la máquina virtual Java actual.
  • ¿Hay tipos constantes no compatibles en las constantes del grupo de constantes (verifique el indicador de etiqueta constante).
  • ¿Hay una constante que apunta a una constante inexistente o no coincide con el tipo en varios valores de índice que apuntan a una constante?
  • ¿Hay algún dato en la constante CONSTANT_Utf8_info que no se ajuste a la codificación UTF-8?
  • Si hay información adicional eliminada o adicional en cada parte del archivo de clase y el archivo en sí.
  • ... espera

2. Verificación de metadatos (verificación semántica de la información de metadatos de la clase)

  • ¿Esta clase tiene una clase padre (excepto java.lang.Object, todas las clases deberían tener una clase padre)?
  • Si la clase principal de esta clase hereda una clase que no se permite heredar (una clase modificada por final).
    Si la clase no es una clase abstracta, si se implementan todos los métodos requeridos por su clase o interfaz principal.
  • Si los campos y métodos en la clase entran en conflicto con la clase principal (como sobrescribir el campo final de la clase principal, o si hay una sobrecarga del método que no cumple con las reglas, como que los parámetros del método son consistentes, pero el tipo de valor de retorno es diferente, etc.).
  • ... espera

3. Verificación de código de bytes

  • Asegúrese de que el tipo de datos y la secuencia del código de instrucciones de la pila de operandos puedan funcionar juntos en cualquier momento
  • Asegúrese de que ninguna instrucción de salto salte a la instrucción de bytecode fuera del cuerpo del método.
  • · Asegúrese de que la conversión de tipo en el cuerpo del método sea siempre válida, por ejemplo, puede asignar un objeto de subclase al tipo de datos de la clase primaria, que es seguro, pero asignar el objeto de clase primaria al tipo de datos de la subclase e incluso asignar el objeto a Un tipo de datos que no tiene relación de herencia y es completamente irrelevante es peligroso e ilegal.
  • ... espera

4. Verificación de referencia de símbolo

  • Si el nombre completo calificado descrito por la cadena de caracteres en la referencia del símbolo puede encontrar la clase correspondiente.
  • Si hay métodos y campos descritos por el descriptor de campo del método y el nombre simple en la clase especificada.
  • Si la clase actual puede acceder a la accesibilidad (privada, protegida, pública) de la clase, campo, método en la referencia del símbolo.

2.3 Etapa de preparación

La etapa de preparación es la etapa de asignación oficial de memoria para las variables definidas en la clase (es decir, variables estáticas, variables modificadas por estática) y establecer el valor inicial de las variables de clase

Nota:
La asignación de memoria solo incluye variables de clase, no variables de instancia. Las variables de instancia se asignarán en el montón de Java junto con el objeto cuando se instancia el objeto.
Ejemplos:

public static int value = 123;

El valor inicial del valor variable después de la fase de preparación es 0 en lugar de 123, porque no se ha ejecutado ningún método Java en este momento, y la instrucción puttática con el valor 123 es que el programa se compila y almacena en el método constructor de clase () Por lo tanto, la acción de asignar el valor a 123 no se ejecutará hasta la etapa de inicialización de la clase.
Pero por:

public static final int value = 123;

Si el atributo ConstantValue existe en la tabla de atributos de campo del campo de clase, el valor de la variable se inicializará al valor inicial especificado por el atributo ConstantValue durante la etapa de preparación

2.4 Etapa de resolución

La fase de resolución es el proceso de reemplazar las referencias de símbolos en el grupo constante con referencias directas por la máquina virtual Java

Para aclarar el concepto:
referencia de símbolo : la referencia de símbolo describe el objetivo al que se hace referencia con un conjunto de símbolos. El símbolo puede ser cualquier forma de literal, siempre que se pueda utilizar para localizar el objetivo sin ambigüedad.

Referencia directa: una referencia directa es un puntero que puede apuntar directamente al objetivo, un desplazamiento relativo o un controlador que se puede ubicar indirectamente en el objetivo.
1. Análisis de clase o interfaz
2. Análisis de campo
3. Análisis de método

2.5 Fase de inicialización

No fue sino hasta la fase de inicialización que la máquina virtual Java realmente comenzó a ejecutar el código del programa Java escrito en la clase y entregó el control a la aplicación.
La fase de inicialización es el proceso de ejecución del método constructor de clase ().

El método <clinit> () es generado por el compilador que recopila automáticamente las acciones de asignación de todas las variables de clase en la clase y las declaraciones en el bloque de declaración estática (bloque static {}). El orden en que el compilador recoge las declaraciones está en el archivo fuente El orden de aparición determina que solo se puede acceder a las variables definidas antes del bloque de declaración estática en el bloque de declaración estática, y las variables definidas después de que se puede asignar pero no se puede acceder en el bloque de declaración estática anterior

La Oportunidad virtual de Java garantiza que el método <clinit> () de la clase primaria se haya ejecutado antes de que se ejecute el método <clinit> () de la clase secundaria.

El método <clinit> () no es necesario para una clase o interfaz. Si no hay un bloque de declaración estática o asignación de variables a una clase, el compilador no puede generar el método <clinit> () para esta clase.
El método <clinit> () de la interfaz de ejecución no necesita ejecutar primero el método <clinit> () de la interfaz principal, porque la interfaz principal se inicializará solo cuando se utilicen las variables definidas en la interfaz principal.

La máquina virtual Java debe garantizar que el método <clinit> () de una clase esté correctamente bloqueado y sincronizado en un entorno multiproceso

3. Cargador de clases

El equipo de diseño de la máquina virtual Java tiene la intención de poner la acción de "obtener una secuencia de bytes binarios que describa una clase por su nombre completo" durante la fase de carga de clases fuera de la máquina virtual Java para permitir que la aplicación decida cómo ir. Obtenga la clase requerida. El código que implementa esta acción se llama "Cargador de clases".

Para cualquier clase, el cargador de clases que lo carga y la clase misma deben establecer conjuntamente su singularidad en la máquina virtual Java . Cada cargador de clases tiene un espacio de nombres de clase independiente.

Es decir, si dos clases son "iguales" solo tiene sentido si las dos clases son cargadas por el mismo cargador de clases , de lo contrario, incluso si las dos clases se derivan del mismo archivo de Clase, el mismo Java las usa Carga de máquina virtual, siempre que el cargador de clases que las carga sea diferente, esas dos clases deben ser desiguales.

Si hay un objeto de clase cargado por dos cargadores de clase, y se genera el objeto correspondiente, cuando la verificación de tipo del objeto pertenece, el resultado es falso

3.1 Modelo de delegación de padres

Categoría:
Bootstrap Class Loader (BootstrapClassLoader), este cargador de clases se implementa en lenguaje C ++ y es parte de la máquina virtual en sí;
todos los demás cargadores de clases, estos cargadores de clases son implementados por el lenguaje Java y existen independientemente fuera de la máquina virtual Y todo heredado de la clase abstracta java.lang.ClassLoader.

Iniciar cargador de clases

Responsable de cargar y almacenar en el directorio <JAVA_HOME> \ lib, o almacenado en la ruta especificada por el parámetro -Xbootclasspath, y puede ser reconocido por la máquina virtual Java (identificada por el nombre del archivo, como rt.jar, tools.jar, el nombre no coincide La biblioteca de clases no se cargará incluso si se coloca en el directorio lib) La biblioteca de clases se carga en la memoria de la máquina virtual.
El programa Java no puede hacer referencia directa al cargador de clases de inicio. Al escribir un cargador de clases personalizado, si necesita delegar la solicitud de carga al cargador de clases de arranque para su procesamiento, puede usar directamente nulo.
Inserte la descripción de la imagen aquí

Cargador de clase de extensión

Este cargador de clases se implementa en forma de código Java en la clase sun.misc.Launcher $ ExtClassLoader. Es responsable de cargar todas las bibliotecas en el directorio <JAVA_HOME> \ lib \ ext o la ruta especificada por la variable de sistema java.ext.dirs.

Cargador de clase de aplicación

Este cargador de clases está implementado por sun.misc.Launcher $ AppClassLoader. Debido a que el cargador de clases de la aplicación es el valor de retorno del método getSystem-ClassLoader () en la clase ClassLoader, en algunos casos también se denomina "cargador de clases del sistema". Es responsable de cargar todas las bibliotecas de clases en el classpath del usuario (ClassPath), y los desarrolladores también pueden usar este cargador de clases directamente en el código. Si no ha personalizado su propio cargador de clases en la aplicación, en general este es el cargador de clases predeterminado en el programa.

Modelo de delegación principal

Inserte la descripción de la imagen aquí
El modelo de delegación principal requiere que, además del cargador de clases de inicio de nivel superior, el resto del cargador de clases debe tener su propio cargador de clases principal.
Sin embargo, la relación padre-hijo entre los cargadores de clases generalmente no se implementa en una relación de herencia (Herencia), sino que generalmente usa una relación de composición (Composición) para reutilizar el código del cargador principal.

Proceso de trabajo:
Si se carga un cargador de clases de la clase recibe una solicitud, que primero no lo hace propio para tratar de cargar esta clase , y que esta solicitud se delega al cargador de clases padre a completa , cada nivel del cargador de clases es cierto Por lo tanto, todas las solicitudes de carga deberían transferirse eventualmente al cargador de clases de inicio de nivel superior. Solo cuando el cargador principal informa que no puede completar la solicitud de carga (la clase requerida no se encuentra en su ámbito de búsqueda), el cargador secundario Intentará completar la carga por sí mismo.

Beneficios:
Un beneficio obvio es que las clases en Java tienen una relación jerárquica con prioridad junto con sus cargadores de clases.

Por ejemplo, la clase java.lang.Object, que se almacena en rt.jar, no importa qué cargador de clases quiera cargar esta clase, finalmente se delega en el cargador de clases de inicio en la parte superior del modelo para cargar, por lo que la clase Object está en el programa En todo tipo de entornos de cargadores de clases, se puede garantizar la misma clase.

Si deja que otros cargadores de clase lo carguen, la determinación final del tipo definitivamente será confusa

Implementación de código:
Inserte la descripción de la imagen aquí

Destruye la delegación parental

loadclass-> findclass
JNDI service- > El tipo básico necesita ser devuelto al código del usuario
Despliegue en caliente-> Cuando se recibe una solicitud de carga de clase, escanee la clase y cárguela

Adicional: diferentes cargadores de clases cargan la misma clase, y finalmente getclass es igual para juzgar el caso falso

 public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        ClassLoader myClassLoader = new ClassLoader() {
            @Override
            public Class<?> loadClass(String name) throws ClassNotFoundException {
                try {
                    String filename = name.substring(name.lastIndexOf(".") + 1) + ".class";
                    InputStream is = getClass().getResourceAsStream(filename);
                    if (is == null) {
                        return super.loadClass(name);
                    }
                    byte[] b = new byte[is.available()];
                    is.read(b);
                    return defineClass(name, b, 0, b.length);
                } catch (IOException e) {
                    e.printStackTrace();
                }
                return null;
            }
        };

        Class<?> aClass = myClassLoader.loadClass("com.lyq.boot.lexicalanalysis.A");
        Object o =  aClass.newInstance();
        A a = new A();
        System.out.println(o.getClass().equals(a.getClass()));
    }

Cargador de clases de contexto

Es decir, la clase cargada por el cargador de clases padre necesita ser cargada por la
clase hija . El cargador de clases hijo retiene la referencia del cargador de clases padre. Pero, ¿qué sucede si la clase cargada por el cargador de clases padre necesita acceder a la clase cargada por el cargador de clases hijo? El escenario más clásico es la carga JDBC.

JDBC es una interfaz estándar establecida por Java para acceder a la base de datos, incluida en la biblioteca de clases básicas de Java y cargada por el cargador de clases raíz. Las bibliotecas de implementación de varios proveedores de bases de datos se introducen como dependencias de terceros. El cargador de clases de la aplicación carga esta parte de las bibliotecas de implementación.

Código para obtener la conexión Mysql:

// Cargar el controlador

Class.forName("com.mysql.jdbc.Driver");

// Conéctate a la base de datos

Connection conn = DriverManager.getConnection(url, user, password);

DriverManager es cargado por el cargador de clases de inicio. El controlador de base de datos (com.mysql.jdbc.Driver) utilizado por él es cargado por el cargador de clases de la aplicación. Esta es la clase típica cargada por el cargador de clases padre. La clase cargada por el dispositivo.

Para la realización de este proceso, vea el código fuente de la clase DriverManager:

//建立数据库连接底层方法
private static Connection getConnection(
        String url, java.util.Properties info, Class<?> caller) throws SQLException {
    //获取调用者的类加载器
    ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;
    synchronized(DriverManager.class) {
        //由启动类加载器加载的类,该值为null,使用上下文类加载器
        if (callerCL == null) {
            callerCL = Thread.currentThread().getContextClassLoader();
        }
    }
```java
    //...

    for(DriverInfo aDriver : registeredDrivers) {
        //使用上下文类加载器去加载驱动
        if(isDriverAllowed(aDriver.driver, callerCL)) {
            try {
                //加载成功,则进行连接
                Connection con = aDriver.driver.connect(url, info);
                //...
            } catch (SQLException ex) {
                if (reason == null) {
                    reason = ex;
                }
            }
        } 
        //...
    }
}
在上面的代码中留意改行代码:

```java
callerCL = Thread.currentThread().getContextClassLoader();

Esta línea de código obtiene el ContextClassLoader del hilo actual, y ¿dónde está configurado el ContextClassLoader? Se establece en el código fuente del Iniciador anterior:

// Establecer el cargador de clases de contexto
Thread.currentThread (). SetContextClassLoader (this.loader); De
esta manera, el denominado cargador de clases de contexto es esencialmente un cargador de clases de aplicaciones. Por lo tanto, el cargador de clases de contexto es solo un concepto propuesto para resolver el acceso inverso de la clase. No es un cargador de clases completamente nuevo. Es esencialmente un cargador de clases de aplicaciones.

Publicado 37 artículos originales · elogiado 6 · visitas 4641

Supongo que te gusta

Origin blog.csdn.net/littlewhitevg/article/details/105521641
Recomendado
Clasificación