1.jvm是什么?jvm是用来做什么的?
我们可以这样简单的理解。你编写一个.java文件,经过编译后生成.class文件。.class文件是与平台无关的字节码,JVM的作用就是执行字节码的。JVM是一个抽象的计算机,和实际的计算机一样,具有指令集,并使用不同的存储区域,他负责执行指令,管理数据、内存与寄存器。
2.jvm什么时候产生?
当启动一个java程序时,一个虚拟机就诞生了,当运行完程序,jvm就死亡了。如果在一个计算机上同时运行三个java程序,那么将得到三个jvm,每个jvm都有自己的指令集、寄存器、栈、垃圾回收堆、存储区等。
3.jvm运行机制
3.1jvm的存储机制
当jvm运行一个程序时,需要内存来存储很多东西,如:字节码、从已装载的class文件中得到的其他信息、程序创建的对象、传递给方法的参数等等,Jvm把这些东西都存储到几个“运行时数据区”中,便于管理。某些运行时数据区是由程序中所有线程共享的,还有一些则只能由一个线程拥有。每个jvm都有一个方法区与一个堆,它们由jvm中的所有线程共享。jvm中的每个线程都有一个PC寄存器与一个java栈,PC寄存器的值总是指向下一条将被执行的指令,java栈存储java方法调用的状态,如局部变量、实参等。jvm没有寄存器,使用java栈来存储中间数据。
(1)方法区
当jvm装载某个类型时,它使用类装载器定位相应的class文件,然后读入这个class文件(一个线性二进制数据流),然后将它传输到jvm中,紧接着jvm提取其中的类型信息,并将这些信息存储到方法区。方法区使用示例如下
class Lava{ private int speed=5; } class Volcano { public static void main(String[] args) { Lava lava=new Lava(); lava.flow(); } } |
步骤2:jvm从class文件里的二进制数据中提取类型信息并放到方法区中。
步骤3:通过执行方法区中的字节码,jvm开始执行main()方法,在执行中,它会一直持有指向当前类(Volcano类)的常量池的指针。
步骤4:main()的第一条指令告知jvm,列在常量池第一项的类分配足够的内存。常量池第一项是一个对Lava类的符号引用,然后检查方法区,看Lava类是否被装载。当jvm发现没有装载过名为“Lava”的类时,开始检查并装载“Lava.class”,并把其中的二进制数据提取的信息放到方法区中。
步骤5:jvm以一个指向方法区Lava类数据的指针来替换常量池第一项(就是字符串Lava),以后接可以用这个指针快速的访问Lava类
步骤6:jvm为一个新的Lava对象分配内存。使用Volcano类常量池的第一项指针来访问Lava类型信息,找到其中记录的这样一个信息:一个Lava对象需要分配多少堆空间
步骤7:jvm确定了一个Lava对象的大小后,就在堆上分配这么大小的空间,并把这个对象的变量speed初始化为默认值0.
步骤8:把新生成的Lava对象的引用压到栈中,main()方法的第一条指令就完成了
步骤9:执行下一条指令,将speed对象初始化为正确的初始值5
步骤10:调用Lava对象的flow()方法
(2)堆
java程序在运行时创建的所有类实例或数组都存放在同一个堆中。
(3)PC寄存器
对于一个运行中的java程序而言,其中的每一个线程都有它自己的PC寄存器。PC寄存器的内容总是下一条将被执行指令的“地址”。
(4)java栈
每启动一个线程,java虚拟机就会为其分配一个java栈。java栈以帧为单位保存线程的运行状态。栈帧有三部分组成:局部变量区、操作数栈、帧数据区
4.模拟
class Act{ public static void doMathForver() { int i=0; for(;;) { i+=1; I*=2; } } } |
每按一下step,就执行一条指令,pc计数器指向下一条将要执行的指令,下面来具体看看每一条指令执行的操作
doMathForver()方法只有一个局部变量i,
(1)iconst_0将0入栈
(2)istore_0将0出栈,存贮在局部变量区
(3)iinc:把i加上1
(4)iload:把局部变量的值压入操作数栈
(5)iconst_2:把2压入操作数栈
(6)imul:把操作数栈顶部的两个int弹出,执行乘法运算,再把结果压入
(7)istore_0:把乘法结果弹出,放入到局部变量中
goto:把程序计数器送回到iinc指令。