public class HelloWorld {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
System.out.println("Hello World");
}
}
Compiled from "HelloWorld.java"
public class HelloWorld extends java.lang.Object{
public HelloWorld();
Code:
0: aload_0
1: invokespecial #1; //Method java/lang/Object."
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
}
上面的代码包含两个方法:一个是默认构造器,这个是编译器自动生成的;另外一个是主函数。
在每一个方法的下面都包含一系列的指令,例如aload_0,invokespecial #1,等等。每条指令都有什么功能可以查看Java bytecode instruction listings。举个例子,aload_0将位置为0的引用压入栈,getstatic读取类中一个静态域的值。注意在getstatic指令后的“#2”指向运行时的常量池(run-time constant pool)。常量池是JVM运行时数据区域(JVM run-time data areas)中的一个。让我们看看常量池吧,使用命令“javap -verbose”就可以。
总的来说,每一条指令都是从一个数字开始,就像0,1,4,等等。在.class文件中,每一个方法对应一个字节码数组,数组中存储着操作码和它的参数,而这些数字对应到这个数组的索引。每一条操作码都是1byte长,而这些指令可以有0到多个参数。这也是这些数字不连贯的原因。
我们可以用“javap -verbose”进一步查看类。
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."
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;// "
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."
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
}
从JVM规范我们可以知道:尽管运行时常量池比典型的符号表包含更加广泛的数据,它和传统编程语言中的符号表一样提供一个服务函数。
指令“invokespecial #1”中的“#1”指向在常量池#1这个常量。这个常量的值为“Method #6.#15;”。从这些标号中,我们能够递归地知道最终的常量值。
行号表(LineNumberTable)可以向调试器提供java源代码对应字节码指令的行号信息。例如,在示例程序中主方法的第9行对应到字节码中的0行,第10行对应字节码的第8行。
引用:
1. Load
2. Class Loading Mechanism
3. Classloader