每个程序都运行在一个虚拟机实例上
JVM生命周期
当启动一个java程序时,一个虚拟机的实例就产生了,当程序关闭退出时,JVM也随着消失,如果一个同一台计算机上运行3个java程序,那么就有3个JVM实例。
JVM的体系结构
每个JVM都有一个方法区和一个堆,是所有线程共享的,方法区放置类数据,当程序运行时把创建的对象放到堆中。当每一个线程被创建时,它得到自己的PC寄存器以及一个java栈,如果线程执行的是java-method,pc寄存器总是指示下一条将被执行的指令,java栈总是存储java-method调用的状态(局部变量,传入的参数,返回值,以及中间结果等)
JVM没有寄存器,指令集使用java栈来存储中间数据,java栈有许多栈桢stack frame组成,一个stack frame 包含一个java-method调用的状态,当线程调用一个java-method的时候,JVM压入一个新的stack frame到该线程的java栈中,当方法返回时,这个statck frame被从java栈中弹出并抛弃。这样的存储结构保持了JVM的指令集尽量紧凑,也有助于运行动态编译和即时编译的代码优化,同时增加了通用性。
数据类型:
-
基本类型 其中JVM解析boolean时用int或者byte来表示,false=0,true!=0,long在任何JVM中总是一个64bit二进制补码表示的有符号整数;另外JVM有一个在内部使用的基本类型数据,returnAddress,它被用来实现java程序中的finally子句
-
引用类型 reference,有三种:类类型(对类实例的引用)接口类型(对实现了该接口的某个类实例的引用),数组类型(对数组对象的引用),还有一种特殊的引用值null,它表示该引用变量没有引用任何对象。
字长的考量:
-
JVM中最基本的数据单元是字word
-
字长必须足够大,至少是一个字单元就足以持有byte,short,int ,char,float,returnAddress,reference的值,2个字长足以持有long,double的值。
类装载子系统:
-
装载方式:启动类装载器在系统类的安装路径查找要装入的类
-
用户自定义的的类装载器在classpath中查找要装载的类。
-
常量池,JVM必须为每个被装载的类型维护一个常量池,所有常量的有序集合
-
字段信息,所有的字段都在方法区保存为,字段名,类型,修饰符
-
方法信息,方法区存放 方法名,返回类型,修饰符,如果方法不是抽象的,还要存放方法的字节码,操作数栈和该方法的栈桢的局部变量区的大小,异常表
-
类(静态)变量,类变量是由所有类实例共享的,即使没有实例,他们也是可被访问的,这些变量只和类有关,和实例无关,在使用某个类之前,必须在方法区为类变量分配空间
-
指向ClassLoader类的引用 类装载的时候,JVM必须跟踪它是由谁装载的
-
指向Class类的引用,对于每个被装载的类型,JVM都会相应的为它创建一个java.lang.Class类的实例,而且以某种方式把这个实例与存储在方法区的类型数据关联起来,因此用户可以得到任何已装载的类的Class实例的引用 public static Class forName(String className);也可以根据类对象的引用得到Class,instance.getClass();
-
方法表 , JVM对每个装载的类都生成一个方法表,它是一个数组,它的元素是所有它的实例都可能被调用的实例方法的直接引用
-
常量池解析,把常量池中的符号引用替换为直接引用
堆
java程序运行时创建的所有实例或数组都放在同一个堆中,JVM实例中只存放一个堆空间,因此所有的线程都共享这个堆,又由于一个java程序独占一个JVM实例,那么java程序都有自己的堆空间,互不干扰。JVM有一条在堆中分配新对象的指令,却没有释放内存的指令,正如java代码中无法明确释放一个对象一样,字节码也没有对应的功能,只能用GC
GC
自动回收不在被运行的程序引用的对象所占的内存,或者移动那些还在使用的对象,减少堆碎片。
对象的内部表示
java对象中包含的基本数据由它所属的类及其所有超类声明的实例变量组成,只要有一个对象引用,JVM必须能快速的定位对象实例的数据,并且必须能通过该对象的引用访问相应的类数据,因为当程序运行时需要转换类型时JVM必须要检查这个转换是否被运行,在程序执行instanceof的时候,JVM也进行这样的操作
程序计数器
PC的大小是一个字长,它总是下一条将被执行的指令的地址,如果该线程在在执行一个本地方法,那么PC的值是undefined
Java栈
每当线程调用一个Java方法时,JVM都会在该线程的Java栈中压人一个新桢,这个新桢就成了当前桢,用来存放参数,局部变量,中间运算结果等数据。不管java方法是通过return返回还是Exception终止,JVM都会释放当前桢,这样上一个方法的桢就成了当前桢了,java栈上的数据都是线程私有的。
栈桢
共分为三部分:局部变量区,操作数栈,桢数据区,其中前2个的大小都是按字长计算的,在编译时就确定了并放在class文件中,后者则是依赖于具体的实现。
-
局部变量区:以字长为单位,从0开始计数的数组,byte,short,bolean都存储为int
-
操作数栈:不是通过索引访问,而是通过栈操作(压栈出栈)来访问,类型同上
-
桢数据区:存放来支持常量池解析,正常方法返回以及异常派发机制的数据信息
执行引擎
java中的每个线程都是一个独立的JVM执行引擎