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");
}
}
|
public class HelloWorld {/ ** * @param args * / public static void main (String[] args) {// TODO Método auxiliar generado automáticamente System.out.println («Hola mundo»); }}
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
}
|
Compilado a partir de la clase pública «HelloWorld.java» HelloWorld extiende java.lang.Object {public HelloWorld (); Código: 0: aload_0 1: invokespecial # 1; // Método java / lang / Object. «» :() V 4: return public static void main (java.lang.String[]); Código: 0: getstatic # 2; // Campo java / lang / System.out: Ljava / io / PrintStream; 3: ldc # 3; // Cadena Hola mundo 5: invokevirtual # 4; // Método 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
}
|
Compilado a partir de la clase pública «HelloWorld.java» HelloWorld extiende java.lang.Object SourceFile: «HelloWorld.java» versión secundaria: 0 versión principal: 50 Pool de constantes: const # 1 = Método # 6. # 15; // java / lang / Object. «» :() V const # 2 = Field # 16. # 17; // java / lang / System.out: Ljava / io / PrintStream; const # 3 = Cadena # 18; // Hola mundo const # 4 = Método # 19. # 20; // java / io / PrintStream.println: (Ljava / lang / String;) V const # 5 = class # 21; // HolaMundo const # 6 = clase # 22; // java / lang / Object const # 7 = Asciz ; constante # 8 = Asciz () V; const # 9 = Código Asciz; const # 10 = Asciz LineNumberTable; const # 11 = Asciz principal; constante # 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[]); Código: Pila = 2, Locales = 1, Args_size = 1 0: getstatic # 2; // Campo java / lang / System.out: Ljava / io / PrintStream; 3: ldc # 3; // Cadena Hola mundo 5: invokevirtual # 4; // Método java / io / PrintStream.println: (Ljava / lang / String;) V 8: return LineNumberTable: línea 9: 0 línea 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.

Este trabajo de carga lo realizan Java Classloaders. Cuando se inicia la JVM, se utilizan tres cargadores de clases:
- 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.
- Cargador de clases de extensiones: carga el código en los directorios de extensiones (por ejemplo, / jar / lib / ext).
- 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