一个Java虚拟机实例的职责就是负责运行一个Java程序,当通过main启动一个Java程序时,一个虚拟机实例就诞生了。如果同时运行三个Java程序,将得到三个Java虚拟机实例。例子:tomcat启动之后就只有一个Java虚拟机实例。
当Java虚拟机运行一个程序时需要内存来存储很多东西,例如,字节码,从已装载的class文件中得到的信息,对象实例,方法参数,返回值,局部变量以及中间结果等等,Java虚拟机把这些东东组织到几个“运行时数据区”中,便于管理。如下图所示:
[img]http://dl.iteye.com/upload/attachment/568957/5910842d-1b57-3c83-b154-721f8622ddc1.png[/img]
方法区:
在方法区中主要保存如下信息:
1)、类型的全名
2)、类型的父类型的全名
3)、给类型是一个类还是接口
4)、类型的修饰符
5)、所有父接口全名的列表
类型全名保存的数据结构由虚拟机实现者定义。除此之外,Java虚拟机还要为每个类型保存如下信息:
1)、类型的常量池
2)、类型字段的信息
3)、类型方法的信息
4)、所有的静态类变量(非常量)信息
5)、一个指向类加载器的引用
6)、一个指向Class类的引用
重点说下常量池,常量池中除了存储直接常量(final常量、字符串)外,还存储类,字段和方法的符号引用。常量池解析指的就是把这些符号引用替换为相应的指针。
堆:
Java程序在运行时创建的所有类实例或数组都放在同一个堆中。而一个Java虚拟机实例中只存在一个堆空间,因此所有线程都共享这个堆。因此对于多个线程访问这个堆中的同一实例(单例)时需要考虑线程同步问题,多例不用考虑线程同步。
举个例子:在web开发中,通过main方法启动tomcat后就启动了一个Java虚拟机实例,且此后一直仅有这个虚拟机实例,因此也就只存在一个堆空间,所有线程都共享这个堆。而每个用户通过浏览器发送请求访问服务器上的Action时就启动了一个线程,对于struts1.x而言,Action为单例,即每个线程访问的Action都是堆中的同一个类实例,需要考虑线程同步。而对于struts2.x而言,Action为多例,即每个线程访问的Action都是不同的类实例,不需要考虑线程同步。
堆中都存了些什么东东?堆中存储的Java类实例由它所属的类及其超类的实例变量组成。另外这个类实例必须能够找到自己相应的类数据(存储在方法区中),因此在类实例中还应该存储一个指向方法区类数据的指针(否则你光有数据,没有方法信息,怎么执行?)。下面的两张可能的堆空间设计图,其中都包含了方法区的指针(ptr to class data)。
[img]http://dl.iteye.com/upload/attachment/568959/646b90c4-5ef9-33e7-8311-14d0162c4127.png[/img]
[img]http://dl.iteye.com/upload/attachment/568962/ca1417f8-09a4-302b-b70b-3de820a0ac58.png[/img]
PC寄存器:
每一个线程都有它自己的PC寄存器,也是该线程启动时创建的。PC寄存器的内容总是指向下一条将被执行指令的地址,这里的地址可以是一个本地指针,也可以是在方法区中相对应于该方法起始指令的偏移量。
Java栈:
每一个线程都有它自己的Java栈,栈中可以存储方法参数,返回值,局部变量以及中间结果等等。
每个线程都有自己的pc寄存器(程序计数器)和java栈,如果线程正在执行一个方法,pc寄存器的值总是指向下一条将被执行的指令。
Java栈是由许多栈帧组成的,一个栈帧包含一个Java方法调用的状态。当线程调用一个Java方法时,虚拟机压入一个新的栈帧到该线程的Java栈中,当该方法返回时,这个栈帧被从Java栈中弹出并抛弃。
[img]http://dl.iteye.com/upload/attachment/584673/d3399477-83f3-3e5d-a98c-114e6c66a58e.png[/img]
栈帧由3部分组成:局部变量区、操作数栈和帧数据区。
局部变量区:用于存储方法参数,返回值,局部变量以及中间结果。
操作数栈:用于存储操作数。它执行标准的栈操作:进栈、出栈。如果某个指令把一个值压入到操作数栈中,稍后另一个指令就可以弹出这个值来使用。
看一个例子:
上面的这个方法对应的指令操作如下(注释是我添加的):
本地方法栈:
本地方法栈用于支持native方法的执行,存储了native方法的调用状态。
当Java虚拟机运行一个程序时需要内存来存储很多东西,例如,字节码,从已装载的class文件中得到的信息,对象实例,方法参数,返回值,局部变量以及中间结果等等,Java虚拟机把这些东东组织到几个“运行时数据区”中,便于管理。如下图所示:
[img]http://dl.iteye.com/upload/attachment/568957/5910842d-1b57-3c83-b154-721f8622ddc1.png[/img]
方法区:
在方法区中主要保存如下信息:
1)、类型的全名
2)、类型的父类型的全名
3)、给类型是一个类还是接口
4)、类型的修饰符
5)、所有父接口全名的列表
类型全名保存的数据结构由虚拟机实现者定义。除此之外,Java虚拟机还要为每个类型保存如下信息:
1)、类型的常量池
2)、类型字段的信息
3)、类型方法的信息
4)、所有的静态类变量(非常量)信息
5)、一个指向类加载器的引用
6)、一个指向Class类的引用
重点说下常量池,常量池中除了存储直接常量(final常量、字符串)外,还存储类,字段和方法的符号引用。常量池解析指的就是把这些符号引用替换为相应的指针。
堆:
Java程序在运行时创建的所有类实例或数组都放在同一个堆中。而一个Java虚拟机实例中只存在一个堆空间,因此所有线程都共享这个堆。因此对于多个线程访问这个堆中的同一实例(单例)时需要考虑线程同步问题,多例不用考虑线程同步。
举个例子:在web开发中,通过main方法启动tomcat后就启动了一个Java虚拟机实例,且此后一直仅有这个虚拟机实例,因此也就只存在一个堆空间,所有线程都共享这个堆。而每个用户通过浏览器发送请求访问服务器上的Action时就启动了一个线程,对于struts1.x而言,Action为单例,即每个线程访问的Action都是堆中的同一个类实例,需要考虑线程同步。而对于struts2.x而言,Action为多例,即每个线程访问的Action都是不同的类实例,不需要考虑线程同步。
堆中都存了些什么东东?堆中存储的Java类实例由它所属的类及其超类的实例变量组成。另外这个类实例必须能够找到自己相应的类数据(存储在方法区中),因此在类实例中还应该存储一个指向方法区类数据的指针(否则你光有数据,没有方法信息,怎么执行?)。下面的两张可能的堆空间设计图,其中都包含了方法区的指针(ptr to class data)。
[img]http://dl.iteye.com/upload/attachment/568959/646b90c4-5ef9-33e7-8311-14d0162c4127.png[/img]
[img]http://dl.iteye.com/upload/attachment/568962/ca1417f8-09a4-302b-b70b-3de820a0ac58.png[/img]
PC寄存器:
每一个线程都有它自己的PC寄存器,也是该线程启动时创建的。PC寄存器的内容总是指向下一条将被执行指令的地址,这里的地址可以是一个本地指针,也可以是在方法区中相对应于该方法起始指令的偏移量。
Java栈:
每一个线程都有它自己的Java栈,栈中可以存储方法参数,返回值,局部变量以及中间结果等等。
每个线程都有自己的pc寄存器(程序计数器)和java栈,如果线程正在执行一个方法,pc寄存器的值总是指向下一条将被执行的指令。
Java栈是由许多栈帧组成的,一个栈帧包含一个Java方法调用的状态。当线程调用一个Java方法时,虚拟机压入一个新的栈帧到该线程的Java栈中,当该方法返回时,这个栈帧被从Java栈中弹出并抛弃。
[img]http://dl.iteye.com/upload/attachment/584673/d3399477-83f3-3e5d-a98c-114e6c66a58e.png[/img]
栈帧由3部分组成:局部变量区、操作数栈和帧数据区。
局部变量区:用于存储方法参数,返回值,局部变量以及中间结果。
操作数栈:用于存储操作数。它执行标准的栈操作:进栈、出栈。如果某个指令把一个值压入到操作数栈中,稍后另一个指令就可以弹出这个值来使用。
看一个例子:
void add() {
int m = 4;
int n = 5;
int l = m + n;
}
上面的这个方法对应的指令操作如下(注释是我添加的):
0: iconst_4 //将int型4推送至操作数栈顶
1: istore_1 //将栈顶int型数值存入第2个局部变量
2: iconst_5 //将int型5推送至操作数栈顶
3: istore_2 //将栈顶int型数值存入第3个局部变量
4: iload_1 //将第2个int型局部变量推送至操作数栈顶
5: iload_2 //将第3个int型局部变量推送至操作数栈顶
6: iadd //将操作数栈顶两int型数值相加并将结果压入栈顶
7: istore_3 //将栈顶int型数值(相加结果)存入第4个局部变量
8: return //从当前方法返回,此方法对应的帧栈被从java栈中弹出并抛弃
本地方法栈:
本地方法栈用于支持native方法的执行,存储了native方法的调用状态。