下面的内容全部来自网络,基本无原创。只是学习笔记而已。
为了显示jvm如何使用方法区中的信息,我们据一个例子,我们看下面这个类:
class Lava { private int speed = 5; // 5 kilometers per hour void flow() { } } class Volcano { public static void main(String[] args) { Lava lava = new Lava(); lava.flow(); } }
下面我们描述一下main()方法的第一条指令的字节码是如何被执行的。不同的jvm实现的差别很大,这里只是其中之一。
一下是以上代码的字节码。
public class Lava { // Field descriptor #6 I private int speed; // Method descriptor #8 ()V // Stack: 2, Locals: 1 public Lava(); 0 aload_0 [this] 1 invokespecial java.lang.Object() [10] 4 aload_0 [this] 5 iconst_5 6 putfield Lava.speed : int [12] 9 return Line numbers: [pc: 0, line: 1] [pc: 4, line: 3] [pc: 9, line: 1] Local variable table: [pc: 0, pc: 10] local: this index: 0 type: Lava // Method descriptor #8 ()V // Stack: 0, Locals: 1 void flow(); 0 return Line numbers: [pc: 0, line: 6] Local variable table: [pc: 0, pc: 1] local: this index: 0 type: Lava }
class Volcano {
// Method descriptor #6 ()V // Stack: 1, Locals: 1 Volcano(); 0 aload_0 [this] 1 invokespecial java.lang.Object() [8] 4 return Line numbers: [pc: 0, line: 9] Local variable table: [pc: 0, pc: 5] local: this index: 0 type: Volcano // Method descriptor #15 ([Ljava/lang/String;)V // Stack: 2, Locals: 2 public static void main(java.lang.String[] args); 0 new Lava [16] 3 dup 4 invokespecial Lava() [18] 7 astore_1 [lava] 8 aload_1 [lava] 9 invokevirtual Lava.flow() : void [19] 12 return Line numbers: [pc: 0, line: 11] [pc: 8, line: 12] [pc: 12, line: 13] Local variable table: [pc: 0, pc: 13] local: args index: 0 type: java.lang.String[] [pc: 8, pc: 13] local: lava index: 1 type: Lava }
1 为了运行这个程序,你以某种方式把“Volcano"传给了jvm。有了这个名字,jvm找到了这个类文件(Volcano.class)并读入,它从类文件提取了类型信息并放在了方法区中
2 通过解析存在方法区中的字节码,jvm激活了main()方法,在执行时,jvm保持了一个指向当前类(Volcano)常量池的指针。注意jvm在还没有加载Lava类的时候就已经开始执行了。正像大多数的jvm一样,不会等所有类都加载了以后才开始执行,它只会在需要的时候才加载。其实就是按需加载)
这个符号引用仅仅是类lava的完整有效名”lava“。这里我们看到为了jvm能尽快从一个名称找到一个类,一个良好的数据结构是多么重要。这里jvm的实现者可以采用各种方法,如hash表,查找树等等。同样的算法可以用于Class类的forName()的实现
3 当jvm发现还没有加载过一个称为"Lava"的类,它就开始查找并加载类文件"Lava.class"。它从类文件中抽取类型信息并放在了方法区中。
4 jvm于是以一个直接指向方法区lava类的指针替换了常量池第一项的符号引用。以后就可以用这个指针快速的找到lava类了。而这个替换过程称为常量池解析(constant pool resolution)。在这里我们替换的是一个native指针。
5 jvm终于开始为新的lava对象分配空间了。这次,jvm仍然需要方法区中的信息。它使用指向lava数据的指针(刚才指向volcano常量池第一项的指针)找到一个lava对象究竟需要多少空间。
jvm总能够从存储在方法区中的类型信息知道某类型对象需要的空间。但一个对象在不同的jvm中可能需要不同的空间,而且它的空间分布也是不同的。
6 一旦jvm知道了一个Lava对象所要的空间,它就在堆上分配这个空间并把这个实例的变量speed初始化为缺省值0。假如lava的父对象也有实例变量,则也会初始化。(晕 这个需要后面heap的知识)
7 当把新生成的lava对象的引用压到栈中,第一条指令也结束了。下面的指令利用这个引用激活java代码把speed变量设为初始值
8 另外一条指令会用这个引用激活