Momento y proceso de carga de clases.

Desde el momento en que una clase se carga en la memoria de la máquina virtual hasta que se descarga de la memoria, su ciclo de vida completo incluye: Carga , Verificación , Preparación , Resolución , Inicialización y uso (Using) y descarga (Descarga) 7 etapas. Entre ellos, las tres partes de verificación, preparación y análisis se denominan colectivamente Vinculación .
inserte la descripción de la imagen aquí
Se determina el orden de las cinco fases de carga, verificación, preparación, inicialización y descarga, pero no necesariamente para la fase de "análisis". En algunos casos, puede comenzar después de la inicialización. Esto se hace para admitir la función de enlace Runtime de Java (también conocido como enlace dinámico o enlace tardío).


1. Cargando

Durante la fase de carga, la máquina virtual debe completar las tres cosas siguientes:

  1. Obtenga el flujo 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 del método.
  3. Genere un objeto java.lang.Class que represente esta clase en la memoria, como entrada de acceso a varios datos de esta clase en el área de método.

2. Verificación (solo comprenda)

Es el primer paso de la fase de conexión. El propósito de esta fase es garantizar que la información contenida en el flujo de bytes del archivo Class cumpla con los requisitos de la máquina virtual actual y no ponga en peligro la seguridad de la máquina virtual en sí.

Pero en general, la fase de verificación completará aproximadamente las siguientes cuatro etapas de acciones de verificación: verificación de formato de archivo , verificación de metadatos , verificación de código de bytes y verificación de referencia de símbolo .

2.1, verificación del formato de archivo

La primera etapa es verificar si el flujo de bytes se ajusta a la especificación del formato de archivo de clase y puede ser procesado por la versión actual de la máquina virtual. Esta fase puede incluir los siguientes puntos de verificación:

  • Ya sea para comenzar con el número mágico 0xCAFEBABE.
  • Si los números de versión principal y secundaria están dentro del rango aceptable de la máquina virtual Java actual.
  • Si hay un tipo de constante no admitido en la constante del grupo de constantes (verifique la etiqueta de constante).
  • Si alguno de los diversos valores de índice que apuntan a constantes apunta a constantes inexistentes o constantes que no se ajustan al tipo.

2.2 Verificación de metadatos

La segunda etapa consiste en realizar un análisis semántico de la información descrita por el código de bytes para garantizar que la información que describe cumpla con los requisitos de la "Especificación del lenguaje Java", los puntos de verificación que se pueden incluir en esta etapa son los siguientes:

  • Si esta clase tiene un padre (todas las clases excepto java.lang.Object deben tener un padre).
  • Si la clase principal de esta clase hereda una clase que no se permite heredar (una clase modificada por final).
  • Si esta clase no es una clase abstracta, si ha implementado todos los métodos necesarios para implementarse en su clase o interfaz principal.
  • Si los campos y métodos de la clase entran en conflicto con la clase principal (por ejemplo, el campo final de la clase principal está cubierto o hay sobrecargas de métodos que no se ajustan a las reglas, por ejemplo, los parámetros del método son todos iguales) , pero el tipo de valor de retorno es diferente, etc.).

2.3 Verificación del código de bytes

La tercera etapa de la verificación del código de bytes es la etapa más complicada de todo el proceso de verificación y su objetivo principal es determinar si la semántica del programa es legal y lógica mediante el análisis del flujo de datos y el análisis del flujo de control. Después de verificar el tipo de datos en la información de metadatos en la segunda etapa, es necesario verificar y analizar el cuerpo del método de la clase (atributo de Código en el archivo de clase) en esta etapa para garantizar que el método de la clase verificada no se ejecute. Comportamientos que ponen en peligro la seguridad de las máquinas virtuales, como:

  • Asegúrese de que el tipo de datos de la pila de operandos y la secuencia del código de instrucción puedan funcionar juntos en cualquier momento; por ejemplo, no existirá algo así como "un dato de tipo int se coloca en la pila de operaciones, pero se carga en el tabla de variables locales siempre que se utilice" en tal situación.
  • Se garantiza que cualquier instrucción de salto no saltará a instrucciones de código de bytes fuera del cuerpo del método.

2.4 Verificación de referencia de símbolo

La etapa final de verificación ocurre cuando la máquina virtual convierte las referencias simbólicas en referencias directas, esta conversión ocurrirá en la tercera etapa de la conexión, la etapa de resolución. La verificación de referencia de símbolo puede verse como una verificación coincidente de diversa información además de la clase en sí (varias referencias de símbolo en el grupo constante). En términos generales, si la clase carece o tiene prohibido acceder a algunos recursos externos depende de recursos como las clases. , métodos, campos, etc. Esta etapa generalmente necesita verificar lo siguiente:

  • Si el nombre completo descrito por la cadena en la referencia del símbolo puede encontrar la clase correspondiente.
  • Si el método y el campo descritos por el descriptor de campo y el nombre simple del método coincidente existen en la clase especificada.
  • Accesibilidad (privada, protegida, pública) de clases, campos, métodos en referencias simbólicas.
  • Si la clase actual puede acceder a él.

3. Prepárate

La etapa de preparación es la etapa de asignación formal de memoria para las variables de clase y establecimiento del valor inicial de las variables de clase. La memoria utilizada por estas variables se asignará en el área de método.


En esta etapa, hay dos conceptos confusos que deben enfatizarse. Primero, la asignación de memoria en este momento solo incluye variables de clase (variables modificadas por estática), no variables de instancia. Las variables de instancia se instanciarán cuando se instancia el objeto. el objeto asignado en el montón de Java.


En segundo lugar, el valor inicial mencionado aquí es "normalmente" el valor cero del tipo de datos, suponiendo que una variable de clase se define como:
public static int value = 123;

Entonces, el valor inicial del valor de la 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 putstatic que asigna el valor a 123 se almacena en el constructor de clase <clinit> (). método, por lo que la acción de asignar el valor a 123 solo se ejecutará durante la fase de inicialización.

tipo de datos valor cero tipo de datos valor cero
En t 0 booleano FALSO
largo 0L flotar 0.0f
corto (corto) 0 doble 0.0d
carbonizarse '\u0000' referencia nulo
byte (byte) 0

Pero también hay algunos casos especiales, como que el atributo ConstantValue existe en la tabla de atributos de campo del campo de clase, luego el valor de la variable se inicializará al valor especificado por el atributo ConstantValue durante la fase de preparación. Supongamos que el ejemplo anterior es el siguiente. sigue:
public static final int value = 123;

Al compilar, javac generará el atributo ConstantValue para el valor y, en la etapa de preparación, la máquina virtual asignará el valor a 123 de acuerdo con la configuración de ConstantValue.


4. Análisis

La fase de análisis es el proceso en el que la máquina virtual reemplaza las referencias simbólicas en el grupo constante con referencias directas.

Referencias simbólicas (Referencias simbólicas)
Las referencias simbólicas utilizan un conjunto de símbolos para describir el objetivo al que se hace referencia. El símbolo puede ser cualquier forma de valor literal, siempre que se pueda utilizar para localizar el objetivo sin ambigüedad.

Referencias directas Una referencia directa
puede ser un puntero directamente al objetivo, un desplazamiento relativo o un identificador que se puede ubicar indirectamente en el objetivo.


El análisis se puede dividir a grandes rasgos en:

  • Resolución de clase o interfaz
  • análisis de campo
  • análisis del método de clase
  • Análisis del método de interfaz.

Las siguientes excepciones comunes están relacionadas con esta etapa:

  • java.lang.NoSuchFieldErrorSegún la relación de herencia de abajo hacia arriba, se informa un error cuando no se puede encontrar el campo relevante. (excepción de análisis de campo)
  • java.lang.IllegalAccessErrorSe produce un error cuando el campo o método no tiene derechos de acceso. (excepción de análisis para clase o interfaz)
  • java.lang.NoSuchMethodErrorError cuando no se puede encontrar un método relacionado. (Se produjeron excepciones durante el análisis del método de clase y el análisis del método de interfaz)

5. Inicialización

La inicialización es principalmente para operar declaraciones estáticas en una clase (el código de bytes correspondiente es el método clinit). El método <clinit>() no es necesario para una clase o interfaz. Si no hay un bloque de declaración estática en una clase y no hay operación de asignación a variables, entonces el compilador no necesita generar el método <clinit>() para esto. clase.


La máquina virtual garantiza que el método <clinit>() de una clase esté correctamente bloqueado y sincronizado en un entorno multiproceso. Si varios subprocesos inicializan una clase al mismo tiempo, solo un subproceso ejecutará el método <clinit>() , otros subprocesos deben bloquearse y esperar hasta que el subproceso activo termine de ejecutar el método <clinit>(). Si hay una operación que requiere mucho tiempo en el método <clinit>() de una clase, puede provocar el bloqueo de varios procesos.



Para la fase de inicialización, la especificación de la máquina virtual estipula estrictamentetener y solo5 casos en los que la clase debe "inicializarse" inmediatamente (aunque la carga, la validación y la preparación naturalmente deben comenzar antes de eso, la fase de análisis no necesariamente):

  • Al encontrar new, getstatico putstaticestas invokestaticinstrucciones de código de 4 bytes, si la clase no se ha inicializado, primero se debe activar su inicialización. Los escenarios de código Java más comunes que generan estas 4 instrucciones son:

    1. Al crear una instancia de un objeto usando la nueva palabra clave
    2. Al leer o configurar un campo estático de una clase (excepto los campos estáticos que se modifican finalmente y han puesto el resultado en el grupo constante en el momento de la compilación)
    3. Al llamar a un método estático de una clase
  • Cuando se utilizan los métodos del paquete java.lang.reflect para realizar llamadas reflexivas a una clase, si la clase no se ha inicializado, primero se debe activar su inicialización.

  • Al inicializar una clase, si descubre que su clase principal no se ha inicializado, primero debe activar la inicialización de su clase principal.

  • Cuando se inicia la máquina virtual, el usuario debe especificar una clase principal que se ejecutará (la clase que contiene el método main()), y la máquina virtual primero inicializa la clase principal.

  • Cuando se utiliza el soporte de lenguaje dinámico de JDK 1.7, si el resultado final del análisis de una instancia java.lang.invoke.MethodHandle es el identificador de método de REF_getStatic, REF_putStatic, REF_invokeStatic y la clase correspondiente a este identificador de método no se ha inicializado, debe activarse primero su inicialización.

  • Cuando se define en una interfaz un método predeterminado recién agregado por JDK1.8 (método de interfaz modificado por la palabra clave predeterminada), si se inicializa la clase de implementación de la interfaz, la interfaz debe inicializarse antes que ella.


5.1 Casos comunes

  • Hacer referencia a los campos estáticos de la clase principal a través de la subclase solo activará la inicialización de la clase principal y no provocará la inicialización de la subclase (la subclase solo se cargará)
    public class SuperClass {
          
          
    	static {
          
          
        	System.out.println("SuperClass init...");
    	}
    
    	public static int value = 123;
    }
    
    public class SubClass extends SuperClass {
          
          
    	static {
          
          
        	System.out.println("SubClass init...");
    	}
    }
    
    public class App {
          
          
    	public static void main(String[] args) {
          
          
        	System.out.println(SubClass.value);
    	}
    }
    
    inserte la descripción de la imagen aquí
    Aquí encontramos que la consola solo genera "SuperClass init...", pero no "SubClass init...". Para campos estáticos, solo se inicializará la clase que define directamente este campo, por lo que se hace referencia a la clase principal a través de su subclase Los campos estáticos definidos en solo activarán la inicialización de la clase principal y no la inicialización de la subclase.

  • La introducción de una clase a través de una definición de matriz no activa la inicialización de esta clase
    public class ArrayClass {
          
          
    	static {
          
          
        	System.out.println("ArrayClass init...");
    	}
    }
    
    public class App {
          
          
    	public static void main(String[] args) {
          
          
        	SuperClass[] classArr = new SuperClass[10];
    	}
    }
    
    inserte la descripción de la imagen aquí
    Después de ejecutar, se descubrió que "ArrayClass init ..." no aparecía, lo que indica que la fase de inicialización de la clase ArrayClass no se activó.

  • Las constantes se almacenarán en el grupo de constantes de la clase que llama durante la fase de compilación. En esencia, no hay una referencia directa a la clase que define la constante, por lo que no se activará la inicialización de la clase que define la constante.
    public class ConstClass {
          
          
    	static {
          
          
        	System.out.println("ConstClass init...");
    	}
    
    	public static final String HELLO_WORLD = "Hello World";
    }
    
    public class App {
          
          
    	public static void main(String[] args) {
          
          
        	System.out.println(ConstClass.HELLO_WORLD);
    	}
    }
    
    inserte la descripción de la imagen aquí
    Después de ejecutar el código, "ConstClass init..." no se genera. Esto se debe a que, aunque en el código fuente de Java se hace referencia a la constante HELLO_WORLD en la clase ConstClass, el valor de esta constante "Hello World" se almacena en la constante grupo de la clase Cliente, y las referencias del Cliente a la constante ConstClass.HELLO_WORLD en realidad se convierten en referencias de la clase Cliente a su propio grupo constante.

Supongo que te gusta

Origin blog.csdn.net/rockvine/article/details/124821837
Recomendado
Clasificación