学习Java虚拟机

来源:互联网 发布:dota2最新比赛数据 编辑:程序博客网 时间:2024/05/22 06:36
一个Java虚拟机实例的职责就是负责运行一个Java程序,当通过main启动一个Java程序时,一个虚拟机实例就诞生了。如果同时运行三个Java程序,将得到三个Java虚拟机实例。例子:tomcat启动之后就只有一个Java虚拟机实例。 
当Java虚拟机运行一个程序时需要内存来存储很多东西,例如,字节码,从已装载的class文件中得到的信息,对象实例,方法参数,返回值,局部变量以及中间结果等等,Java虚拟机把这些东东组织到几个“运行时数据区”中,便于管理。如下图所示: 

 


方法区: 
在方法区中主要保存如下信息: 
          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)。 



 


PC寄存器: 
每一个线程都有它自己的PC寄存器,也是该线程启动时创建的。PC寄存器的内容总是指向下一条将被执行指令的地址,这里的地址可以是一个本地指针,也可以是在方法区中相对应于该方法起始指令的偏移量。  

Java栈: 
每一个线程都有它自己的Java栈,栈中可以存储方法参数,返回值,局部变量以及中间结果等等。 
每个线程都有自己的pc寄存器(程序计数器)和java栈,如果线程正在执行一个方法,pc寄存器的值总是指向下一条将被执行的指令。 
Java栈是由许多栈帧组成的,一个栈帧包含一个Java方法调用的状态。当线程调用一个Java方法时,虚拟机压入一个新的栈帧到该线程的Java栈中,当该方法返回时,这个栈帧被从Java栈中弹出并抛弃。 

 

栈帧由3部分组成:局部变量区、操作数栈和帧数据区。 
局部变量区:用于存储方法参数,返回值,局部变量以及中间结果。 
操作数栈:用于存储操作数。它执行标准的栈操作:进栈、出栈。如果某个指令把一个值压入到操作数栈中,稍后另一个指令就可以弹出这个值来使用。 
看一个例子: 
Java代码  收藏代码
  1. void add() {  
  2.     int m = 4;  
  3.     int n = 5;  
  4.     int l = m + n;  
  5. }  

上面的这个方法对应的指令操作如下(注释是我添加的): 
Java代码  收藏代码
  1. 0:   iconst_4       //将int型4推送至操作数栈顶  
  2. 1:   istore_1       //将栈顶int型数值存入第2个局部变量  
  3. 2:   iconst_5       //将int型5推送至操作数栈顶  
  4. 3:   istore_2       //将栈顶int型数值存入第3个局部变量  
  5. 4:   iload_1        //将第2个int型局部变量推送至操作数栈顶  
  6. 5:   iload_2        //将第3个int型局部变量推送至操作数栈顶  
  7. 6:   iadd       //将操作数栈顶两int型数值相加并将结果压入栈顶  
  8. 7:   istore_3       //将栈顶int型数值(相加结果)存入第4个局部变量  
  9. 8:   return     //从当前方法返回,此方法对应的帧栈被从java栈中弹出并抛弃  

本地方法栈: 
本地方法栈用于支持native方法的执行,存储了native方法的调用状态。