如同讲汇编必先讲计算机组成原理,在开始字节码之前,我们先了解一下JVM的主要构成。 在JVM的内部,主要由如下几个部分构成:
1.数据区
- 方法区:存放类定义信息、字节码、常量等数据,在Sun HotSpot JVM中,这块也称为Perm Gen。
- 堆:创建的对象信息将放入堆中,堆内部如何实现各虚拟机各不相同,对于Sun HotSpot JVM来说又分为Young Gen和Tenured Gen,更详细描述参见《[Java性能剖析]Sun JVM内存管理和垃圾回收 》
- Java栈:对于每个执行线程,会分配一个Java栈,JVM在执行过程当中,每执行一个方法,都会为方法在当前栈中增加一个栈帧,每个栈帧的信息与具体实现相关,但一般会由3部分组成:变量区,方法参数和本地变量会放入这个位置,大小是固定的,在进行方法时会先分配好,在类定义中,会由max local来指定这块区的大小;方法信息区,会包括当前类常量池的入口地址等信息,这块大小也是固定的;操作栈,与Intel体系架构中的运算使用寄存器来进行不一样,JVM的字节码的方法调用、运算等需要的参数,都是通过操作栈来传递的,在后面详细介绍中我们会进一步了解到,在类定义中,会由max stack指定最大的操作栈。关于Java栈的更详细描述参见《Java 栈内存介绍 》
- 本地方法栈:对本地方法的调用,并不会使用Java栈而是使用本地方法栈,本地方法栈的组成取决于所使用的平台和操作系统
- PC寄存器:对于每个执行线程会分配一个PC寄存器,寄存器中存放当前字节码的执行位置
2.类加载器子系统
类加载器完成类的加载工作,包括查找和装载类定义(.class)信息、连接(包括字节码验证、类变量内存分配和初始化、符号解析)和类初始化的过程
- 查找和装载类定义(.class)信息:关于这块的内容可以google到无数的描述文档,JVM内部也提供了多种查找和装载类定义的途径,譬如从本地加载类定义、从远程加载类定义,甚至需要的话,我们可以对类定义进行加密在装载的时候做处理等等,更详细的描述也可以参见《[Tomcat源码系列] Tomcat 类加载器结构 》
- 连接—字节码验证:对于加载的类定义,JVM必须确保其是合法的,包括定义的结构是合法的、声明的类信息(包括属性、方法等等)是合法的、字节码的正确性(包括确保操作码是合法的、有合法操作栈、goto语句能够到达一个合法的地址上等等)等等
- 连接—类变量内存分配和初始化:在这个阶段,类变量将分配内存,并设置一个合法的初始化值,譬如对象是null、数值型是0、布尔值是false等等
- 连接—符号解析:符号定义了类提供给外部可访问的服务以及类需要访问到的外部的服务,譬如类提供了方法给外部调用,或者类调用外部的方法,这些就是符号,符号解析过程就是将符号型的描述(譬如字符串)转换成实际的引用地址(譬如方法入口字节码指针地址),符号解析可能是在加载时进行,也有可能推迟到实际被使用到才去解析,但不管怎么样,JVM应该对外提供迟解析的印象,即不管何时解析符号,总是在符号被第一次访问的时候才会抛出异常,关于符号解析,后面会有专门的篇章更详细地介绍
- 类初始化:类的初始化是在类第一次被引用到的时候进行,类初始化包括给类属性(static)设置初始化值、调用类中的static块代码,在字节码中,会有一个专门的<clinit>方法,类初始化的时候会调用这个方法,譬如如下
public class Test2
{
public static int cout = 0;
static
{
for (int i=0; i<100; i++)
{
cout += i;
}
}
}
3.执行引擎:可以理解为CPU,是JVM最核心的部分。在Java虚拟机规范中,执行引擎的行为使用指令集来定义。对于每条指令,规范都详细规定了当实现执行到该指令时应该处理什么,但却没有定义如何处理,具体策略交给JVM的具体实现
在后面的篇章,将继续介绍符号解析和字节码
参考: <<深入Java虚拟机>>