Categorías
JVM/Compiler

¿Cuándo y cómo se carga e inicializa una clase Java?

En Java, primero escribe un archivo .java que luego se compila en un archivo .class durante el tiempo de compilación. Java es capaz de cargar clases en tiempo de ejecución. La confusión es cuál es la diferencia entre «cargar» e «inicializar». ¿Cuándo y cómo se carga e inicializa una clase Java? Puede ilustrarse claramente utilizando un ejemplo sencillo a continuación.

¿Qué significa decir «cargar una clase»?

C / C ++ se compila primero en código de máquina nativo y luego requiere un paso de vinculación después de la compilación. Lo que hace el enlace es combinar archivos fuente de diferentes lugares y formar un programa ejecutable. Java no hace eso. El paso similar a la vinculación para Java se realiza cuando se cargan en JVM.

Diferentes JVM cargan clases de diferentes maneras, pero la regla básica es cargar clases solo cuando son necesarias. Si hay otras clases requeridas por la clase cargada, también se cargarán. El proceso de carga es recursivo.

¿Cuándo y cómo se carga una clase Java?

En Java, las políticas de carga las gestiona un ClassLoader. El siguiente ejemplo muestra cómo y cuándo se carga una clase para un programa simple.

TestLoader.java

package compiler;
public class TestLoader {
	public static void main(String[] args) {
		System.out.println("test");
	}
}

A.java

package compiler;
public class A {
	public void method(){
		System.out.println("inside of A");
	}
}

Aquí está la jerarquía de directorios en eclipse:

Al ejecutar el siguiente comando, podemos obtener información sobre cada clase cargada. La opción «-verbose: class» muestra información sobre cada clase cargada.

java -verbose:class -classpath /home/ron/workspace/UltimateTest/bin/ compiler.TestLoader

Parte de la salida:

 
[Loaded sun.misc.JavaSecurityProtectionDomainAccess from /usr/local/java/jdk1.6.0_34/jre/lib/rt.jar]
[Loaded java.security.ProtectionDomain$2 from /usr/local/java/jdk1.6.0_34/jre/lib/rt.jar]
[Loaded java.security.ProtectionDomain$Key from /usr/local/java/jdk1.6.0_34/jre/lib/rt.jar]
[Loaded java.security.Principal from /usr/local/java/jdk1.6.0_34/jre/lib/rt.jar]
[Loaded compiler.TestLoader from file:/home/xiwang/workspace/UltimateTest/bin/]
test
[Loaded java.lang.Shutdown from /usr/local/java/jdk1.6.0_34/jre/lib/rt.jar]
[Loaded java.lang.Shutdown$Lock from /usr/local/java/jdk1.6.0_34/jre/lib/rt.jar]

Ahora si cambiamos TestLoader.java a:

package compiler;
public class TestLoader {
	public static void main(String[] args) {
		System.out.println("test");
		A a = new A();
		a.method();
	}
}

Y ejecute el mismo comando nuevamente, la salida sería:

[Loaded sun.misc.JavaSecurityProtectionDomainAccess from /usr/local/java/jdk1.6.0_34/jre/lib/rt.jar]
[Loaded java.security.ProtectionDomain$2 from /usr/local/java/jdk1.6.0_34/jre/lib/rt.jar]
[Loaded java.security.ProtectionDomain$Key from /usr/local/java/jdk1.6.0_34/jre/lib/rt.jar]
[Loaded java.security.Principal from /usr/local/java/jdk1.6.0_34/jre/lib/rt.jar]
[Loaded compiler.TestLoader from file:/home/xiwang/workspace/UltimateTest/bin/]
test
[Loaded compiler.A from file:/home/xiwang/workspace/UltimateTest/bin/]
inside of A
[Loaded java.lang.Shutdown from /usr/local/java/jdk1.6.0_34/jre/lib/rt.jar]
[Loaded java.lang.Shutdown$Lock from /usr/local/java/jdk1.6.0_34/jre/lib/rt.jar]

Podemos ver la diferencia resaltada en rojo. Una clase se carga solo cuando se usa. En resumen, se carga una clase:

  • cuando se ejecuta el nuevo bytecode. Por ejemplo, SomeClass f = new SomeClass ();
  • cuando los códigos de bytes hacen una referencia estática a una clase. Por ejemplo, System.out.

¿Cuándo y cómo se inicializa una clase Java?

Una clase se inicializa cuando se usa por primera vez un símbolo de la clase. Cuando se carga una clase, no se inicializa.

JVM inicializará la superclase y los campos en orden textual, inicializará los campos finales estáticos primero y dará a cada campo un valor predeterminado antes de la inicialización.

La inicialización de instancia de clase Java es un ejemplo que muestra el orden de ejecución de campo, campo estático y constructor.

Referencias:
1. Cargador de clases de Java
2. Carga de clases de Java
3. Inicialización de clases y objetos

Categorías
JVM/Compiler

¿Qué podemos aprender de Java HelloWorld?

Este es el programa que todo programador Java conoce. Es simple, pero un comienzo simple puede conducir a una comprensión profunda de conceptos más complejos. En esta publicación exploraré lo que se puede aprender de este sencillo programa. Por favor, deje sus comentarios si hola mundo significa más para usted.

HelloWorld.java

 
public class HelloWorld {
	/**
	 * @param args
	 */
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		System.out.println("Hello World");
	}
}

1. ¿Por qué todo comienza con una clase?

Los programas Java se crean a partir de clases, cada método y campo debe estar en una clase. Esto se debe a su característica orientada a objetos: todo es un objeto que es una instancia de una clase. Los lenguajes de programación orientados a objetos tienen muchas ventajas sobre los lenguajes de programación funcionales, como una mejor modularidad, extensibilidad, etc.

2. ¿Por qué siempre hay un método «principal»?

El método «principal» es la entrada del programa y es estático. «estático» significa que el método es parte de su clase, no parte de objetos.

¿Porqué es eso? ¿Por qué no ponemos un método no estático como entrada al programa?

Si un método no es estático, primero se debe crear un objeto para usar el método. Porque el método debe invocarse en un objeto. Para el propósito de la entrada, esto no es realista. No podemos conseguir un huevo sin una gallina. Por tanto, el método de entrada al programa es estático.

El parámetro «String[] args «indica que se puede enviar una matriz de cadenas al programa para ayudar con la inicialización del programa.

3. Código de bytes de HelloWorld

Para ejecutar el programa, el archivo Java se compila primero en un código de bytes Java almacenado en el archivo .class. ¿Qué aspecto tiene el código de bytes? El código de bytes en sí no es legible. Si usamos un editor hexadecimal, se verá así:

Podemos ver una gran cantidad de código de operación (por ejemplo, CA, 4C, etc.) en el código de bytes de arriba, cada uno de ellos tiene un código mnemónico correspondiente (por ejemplo, aload_0 en el ejemplo de abajo). El código de operación no se puede leer, pero podemos usar javap para ver la forma nemotécnica de un archivo .class.

«javap -c» imprime el código desensamblado para cada método de la clase. El código desensamblado hace referencia a las instrucciones que componen los códigos de bytes de Java.

javap -classpath .  -c HelloWorld
Compiled from "HelloWorld.java"
public class HelloWorld extends java.lang.Object{
public HelloWorld();
  Code:
   0:	aload_0
   1:	invokespecial	#1; //Method java/lang/Object."<init>":()V
   4:	return
 
public static void main(java.lang.String[]);
  Code:
   0:	getstatic	#2; //Field java/lang/System.out:Ljava/io/PrintStream;
   3:	ldc	#3; //String Hello World
   5:	invokevirtual	#4; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
   8:	return
}

El código anterior contiene dos métodos: uno es el constructor predeterminado, que es inferido por el compilador; el otro es el método principal.

Debajo de cada método, hay una secuencia de instrucciones, como aload_0, invokespecial # 1, etc. Lo que hace cada instrucción se puede buscar en Listados de instrucciones de código de bytes de Java. Por ejemplo, aload_0 carga una referencia en la pila desde la variable local 0, getstatic obtiene un valor de campo estático de una clase. Observe que el «# 2» después de que la instrucción getstatic señale al grupo de constantes en tiempo de ejecución. La agrupación constante es una de las áreas de datos en tiempo de ejecución de JVM. Esto nos lleva a echar un vistazo al grupo de constantes, que se puede hacer usando el comando «javap -verbose».

Además, cada instrucción comienza con un número, como 0, 1, 4, etc. En el archivo .class, cada método tiene una matriz de código de bytes correspondiente. Estos números corresponden al índice de la matriz donde se almacenan cada código de operación y sus parámetros. Cada código de operación tiene una longitud de 1 byte y las instrucciones pueden tener 0 o varios parámetros. Por eso estos números no son consecutivos.

Ahora podemos usar «javap -verbose» para echar un vistazo más de cerca a la clase.

javap -classpath . -verbose HelloWorld
Compiled from "HelloWorld.java"
public class HelloWorld extends java.lang.Object
  SourceFile: "HelloWorld.java"
  minor version: 0
  major version: 50
  Constant pool:
const #1 = Method	#6.#15;	//  java/lang/Object."<init>":()V
const #2 = Field	#16.#17;	//  java/lang/System.out:Ljava/io/PrintStream;
const #3 = String	#18;	//  Hello World
const #4 = Method	#19.#20;	//  java/io/PrintStream.println:(Ljava/lang/String;)V
const #5 = class	#21;	//  HelloWorld
const #6 = class	#22;	//  java/lang/Object
const #7 = Asciz	<init>;
const #8 = Asciz	()V;
const #9 = Asciz	Code;
const #10 = Asciz	LineNumberTable;
const #11 = Asciz	main;
const #12 = Asciz	([Ljava/lang/String;)V;
const #13 = Asciz	SourceFile;
const #14 = Asciz	HelloWorld.java;
const #15 = NameAndType	#7:#8;//  "<init>":()V
const #16 = class	#23;	//  java/lang/System
const #17 = NameAndType	#24:#25;//  out:Ljava/io/PrintStream;
const #18 = Asciz	Hello World;
const #19 = class	#26;	//  java/io/PrintStream
const #20 = NameAndType	#27:#28;//  println:(Ljava/lang/String;)V
const #21 = Asciz	HelloWorld;
const #22 = Asciz	java/lang/Object;
const #23 = Asciz	java/lang/System;
const #24 = Asciz	out;
const #25 = Asciz	Ljava/io/PrintStream;;
const #26 = Asciz	java/io/PrintStream;
const #27 = Asciz	println;
const #28 = Asciz	(Ljava/lang/String;)V;
 
{
public HelloWorld();
  Code:
   Stack=1, Locals=1, Args_size=1
   0:	aload_0
   1:	invokespecial	#1; //Method java/lang/Object."<init>":()V
   4:	return
  LineNumberTable: 
   line 2: 0
 
 
public static void main(java.lang.String[]);
  Code:
   Stack=2, Locals=1, Args_size=1
   0:	getstatic	#2; //Field java/lang/System.out:Ljava/io/PrintStream;
   3:	ldc	#3; //String Hello World
   5:	invokevirtual	#4; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
   8:	return
  LineNumberTable: 
   line 9: 0
   line 10: 8
}

De Especificación de JVM: El grupo de constantes de tiempo de ejecución tiene una función similar a la de una tabla de símbolos para un lenguaje de programación convencional, aunque contiene una gama más amplia de datos que una tabla de símbolos típica.

El «n. ° 1» en la instrucción «invocar especial n. ° 1» apunta a la constante n. ° 1 del grupo de constantes. La constante es «Método # 6. # 15;». A partir del número, podemos obtener la constante final de forma recursiva.

LineNumberTable proporciona información a un depurador para indicar qué línea de código fuente Java corresponde a qué instrucción de código de bytes. Por ejemplo, la línea 9 en el código fuente de Java corresponde al código de bytes 0 en el método principal y la línea 10 corresponde al código de bytes 8.

Si desea saber más sobre el código de bytes, puede crear y compilar una clase más complicada para echar un vistazo. HelloWorld es realmente un punto de partida para hacer esto.

4. ¿Cómo se ejecuta en JVM?

Ahora la pregunta es ¿cómo JVM carga la clase e invoca el método principal?

Antes de que se ejecute el método principal, JVM necesita 1) cargar, 2) vincular y 3) inicializar la clase. 1) La carga trae la forma binaria para una clase / interfaz a JVM. 2) La vinculación incorpora los datos de tipo binario en el estado de tiempo de ejecución de JVM. La vinculación consta de 3 pasos: verificación, preparación y resolución opcional. La verificación asegura que la clase / interfaz sea estructuralmente correcta; la preparación implica la asignación de la memoria que necesita la clase / interfaz; resolución resuelve referencias simbólicas. Y finalmente 3) la inicialización asigna a las variables de clase los valores iniciales adecuados.

jvm-load-link-initialize

Este trabajo de carga lo realizan Java Classloaders. Cuando se inicia la JVM, se utilizan tres cargadores de clases:

  1. Cargador de clases Bootstrap: carga las bibliotecas centrales de Java ubicadas en el Directorio / jre / lib. Es parte del núcleo de JVM y está escrito en código nativo.
  2. Cargador de clases de extensiones: carga el código en los directorios de extensiones (por ejemplo, / jar / lib / ext).
  3. Cargador de clases del sistema: carga el código que se encuentra en CLASSPATH.

Entonces, la clase HelloWorld es cargada por el cargador de clases del sistema. Cuando se ejecuta el método principal, activará la carga, vinculación e inicialización de otras clases dependientes si existen.

Finalmente, el marco principal () se inserta en la pila de JVM y el contador de programa (PC) se configura en consecuencia. Luego, la PC indica que se envíe el marco println () a la pila de JVM. Cuando se complete el método main (), aparecerá de la pila y la ejecución estará lista.

Referencias:
1. Carga
2. Mecanismo de carga de clases
3. Cargador de clases

Categorías
Diagram JVM/Compiler Memory

Áreas de datos en tiempo de ejecución de JVM

Las siguientes son mis notas sobre la lectura de las especificaciones de JVM.

1. Áreas de datos para cada hilo individual (no compartido)

Las áreas de datos para cada subproceso individual incluyen registro de contador de programa, pila de JVM y pila de método nativo. Todos se crean cuando se crea un nuevo hilo.

Program Counter Register se utiliza para controlar cada ejecución de cada hilo.
JVM Stack contiene marcos que se muestran en el diagrama a continuación.
Native Method Stack se utiliza para admitir métodos nativos, es decir, métodos que no son de lenguaje Java.

2. Áreas de datos compartidas por todos los subprocesos

Todos los subprocesos comparten el área de método y montón.

Montón es el área con la que nos ocupamos con más frecuencia. Almacena matrices y objetos, creados cuando se inicia JVM. Garbage Collection trabaja en esta área.

El área de métodos almacena el grupo de constantes en tiempo de ejecución, los datos de campos y métodos, y el código de los métodos y constructores.

Runtime Constant Pool es una representación en tiempo de ejecución por clase o por interfaz de la tabla constant_pool en un archivo de clase. Contiene varios tipos de constantes, que van desde literales numéricos conocidos en tiempo de compilación hasta referencias a métodos y campos que deben resolverse en tiempo de ejecución.

Pila de JVM

La pila contiene marcos y se envía un marco a la pila cuando se invoca un método. Un marco contiene una matriz de variables locales, Operand Stack, Reference to Constant Pool.

Para obtener más información, visite el sitio oficial de especificaciones de JVM.

Referencias:
1. Especificación de JVM: áreas de datos en tiempo de ejecución
2. Fundamentos de código de bytes de Java

Categorías
Basics Diagram JVM/Compiler

¿Qué es exactamente nulo en Java?


Comencemos por la siguiente declaración:

String x = null;

1. ¿Qué hace exactamente esta declaración?

Recuerde qué es una variable y qué es un valor. Una metáfora común es que una variable es similar a una caja. Así como puede usar un cuadro para almacenar algo, puede usar una variable para almacenar un valor. Al declarar una variable, necesitamos establecer su tipo.

Hay dos categorías principales de tipos en Java: primitivo y de referencia. Variables declaradas de tipo primitivo almacenan valores; variables declaradas de un tipo de referencia almacenar referencias. En este caso, la instrucción de inicialización declara una variable «x». «X» almacena la referencia de cadena. Aquí es nulo.

La siguiente visualización da una mejor idea de este concepto.

Si x = «abc», se parece a lo siguiente:
variable-referencia

2. ¿Qué es exactamente nulo en la memoria?

¿Qué es exactamente nulo en la memoria? O ¿Cuál es el valor nulo en Java?

En primer lugar, null no es una instancia de objeto válida, por lo que no hay memoria asignada para él. Es simplemente un valor que indica que la referencia del objeto no se refiere actualmente a un objeto.

De Especificaciones de JVM:

La especificación de la máquina virtual Java no exige un valor concreto que codifique nulo.

Asumiría que son todos ceros de algo similar como en otros lenguajes similares a C.

3. ¿Qué es exactamente x en la memoria?

Ahora sabemos qué es nulo. Y sabemos que una variable es una ubicación de almacenamiento y un nombre simbólico asociado (un identificador) que contiene algún valor. ¿Dónde está exactamente x en la memoria?

A partir del diagrama de las áreas de datos en tiempo de ejecución de JVM, sabemos que dado que cada método tiene un marco de pila privado dentro de la pila del subproceso, las variables locales se encuentran en ese marco.

Referencias:

1. Variables, operadores y expresiones
2. Variable
3. Especificaciones de JVM