()类装载子系统 装载 连接 初始化 ()方法区被所有线程共享垃圾收集也会清理方法区中的无用类型对象 a 类型信息类加载器加载类时从类文件中提取出来 类的完整有效名 父类的完整有效名(interface and javalangObject 除外因为无父类) 类型的修饰符 类型直接接口列表 b 常量池存储了一个类型所使用的常量所有类型域和方法的符号引用 c 域信息jvm必须在方法区中保存类型的所有域的相关信息以及域的声明顺序 域的相关信息包括 域名 域类型 域修饰符(public private protected static final volatile transient…) d方法信息 方法名 方法返回类型 方法参数 方法的修饰符 方法的字节码(abstract and native 除外)(被PC寄存器指向) 操作数栈和方法栈帧的局部变量区的大小 异常表 e 类的静态变量(所有对象共享一分拷贝) f 类的被声明为final的类变量(所有对象共享一分拷贝) g 加载一个类的类加载器的引用 h Class类的引用 i 方法表 j 一个例子 Class Lava { private int speed = ; void flow(); } Class Volcano { public static void main(String[] args) { Lava lava = new Lava(); lavaflow(); } } 下面我们描述一下main()方法的第一条指令的字节码是如何被执行的不同的jvm实现的差别很大这里只是其中之一 为了运行这个程序你以某种方式把Volcano传给了jvm有了这个名字jvm找到了这个类文件(Volcanoclass)并读入它从类文件提取了类型信息并放在了方法区中通过解析存在方法区中的字节码jvm激活了main()方法在执行时jvm保持了一个指向当前类(Volcano)常量池的指针 注意jvm在还没有加载Lava类的时候就已经开始执行了正像大多数的jvm一样不会等所有类都加载了以后才开始执行它只会在需要的时候才加载 main()的第一条指令告知jvm为列在常量池第一项的类分配足够的内存 jvm使用指向Volcano常量池的指针找到第一项发现是一个对Lava类的符号引用然后它就检查方法区看lava是否已经被加载了 这个符号引用仅仅是类lava的完整有效名lava这里我们看到为了jvm能尽快从一个名称找到一个类一个良好的数据结构是多么重要这里jvm的实现者可以采用各种方法如hash表查找树等等同样的算法可以用于Class类的forName()的实现 当jvm发现还没有加载过一个称为Lava的类它就开始查找并加载类文件Lavaclass它从类文件中抽取类型信息并放在了方法区中 jvm于是以一个直接指向方法区lava类的指针替换了常量池第一项的符号引用以后就可以用这个指针快速的找到lava类了而这个替换过程称为常量池解析(constant pool resolution)在这里我们替换的是一个native指针 jvm终于开始为新的lava对象分配空间了这次jvm仍然需要方法区中的信息它使用指向lava数据的指针(刚才指向volcano常量池第一项的指针)找到一个lava对象究竟需要多少空间 一旦jvm知道了一个Lava对象所要的空间它就在堆上分配这个空间并把这个实例的变量speed初始化为缺省值假如lava的父对象也有实例变量则也会初始化 当把新生成的lava对象的引用压到栈中第一条指令也结束了下面的指令利用这个引用激活java代码把speed变量设为初始值另外一条指令会用这个引用激活Lava对象的flow()方法 ()堆存放运行时所有 对象 和 数组 ()栈每次启动一个新的线程就会被分配一个栈 ()PC 寄存器 ( 程序计数器 ) 总是指向该线程下一步要执行的指令指令的位置放在方法区的方法字节码中内容是相 对于第一个指令的偏移量 ()本地方法栈 |