Inside JVM体系结构详解

当InsideJVM运行程序时,字节码,创建的对象,传递给方法的参数,返回值,局部变量以及运算的中间结果保存在运行时数据区中。规范本身对运行时数据区只有抽象的描述,也使得JVM可以容易的在各种计算机和设备上实现。

简单介绍InsideJVM体系结构

JVM的体系结构根据JVM的规范可分为类装载子系统,运行时数据区,执行引擎。本文将为大家介绍InsideJVM体系结构。当InsideJVM运行程序时,字节码,创建的对象,传递给方法的参数,返回值,局部变量以及运算的中间结果保存在运行时数据区中。规范本身对运行时数据区只有抽象的描述,也使得JVM可以容易的在各种计算机和设备上实现。

InsideJVM运行时数据区

1方法区:

JVM中被装载的类型信息存储在一个逻辑上被称为方法区的内存中,JVM在装载完CLASS文件后提取其中的类型信息并将之存储在方法区。该类型的静态变量同样也存储在方法区中。由于所有的线程共享方法区,所以对方法区数据的访问必须考虑到线程的同步。

方法区的大小是不固定的,JVM可以通过装载新类型或者卸载已有类型来动态的调整方法区的大小,即可以改变方法区所占用的内存。方法区不一定是连续的,方法区可以在一个堆中自由分配。

JVM保存的在方法区中的存储类型以下信息

此类型的全限定名

此类型的直接超类的全限定名(除非这个类型为java.lang.object,它没有超类)

此类型为接口还是类

此类型的访问修饰符号(public,abstact或final的某个子集)

除以上基本信息,还得存储以下类型的具体信息

此类型的常量池

字段信息

方法信息

除常量以外的所有静态变量

一个到类classLoader的引用

一个到Class类的引用

常量池可以理解为本地指针数组,在JAVA的动态连接中起核心的左右,后边再做详细的介绍。

为了尽可能的提高访问的效率,必须优化存储在方法区中的类型信息的数据结构。所以,实现中还可以加如其他数据结构以加快访问原始数据的速度,如方法表。JVM为每个装载的非抽象类,都生成一个方法表,把他作为类信息的一部分保存在方法区。方法表也是一个本地指针数组,其元素为方法的入口地址。方法表所指向实例方法的数据包括以下信息:

此方法的操作数栈和局部变量的大小

此方法的字节码

异常表

JVM可以为每个对象生成一份方法表的copy(这样比较耗内存,但能提高访问的速度)或只在对象里保存到方法区中方法表的引用。这和C++中的VTBL很象,在C++中,对象有实例数据和一组指向对象可以调用的虚拟函数指针组成。

2堆

Java程序在运行时创建的所有类室例或数组都放在同一个堆中,而一个JVM实例只有一个堆空间,所有线程都共享这个堆。堆空间可以自由的伸缩,也不必是连续的。

常见的堆空间的设计:

a.把堆空间分为两部分:句柄池和对象池,对象的引用为指向句柄池的本地指针,句柄池里的每个条目分为两部分,一部分为指向对象池的本地指针,一部分为指向方法区类数据区的本地指针。对象池里保存的是实例对象的数据,此数据是实例私有的。这种设计的好处有利于内存碎片的整理,当移动对象池中的对象时,句柄部分只需要修改指向对象池条目的地址。缺点就是两级指针的访问。

b.使对象指针直接的指向对象数据,该数据包括指向方法区方法区数据类类型的指针和对象的实例数据。这样的优缺点正好和前边的方法相反。当移动堆中的对象时,对象的指针也得跟着改变,这就必须在整个运行时数据区中更新被移动对象的引用。

以上两种方法的思想可以类比为对链表和数组的删除和加入操作。

在Java中,数组和其他对象一样,总是存储在堆中并拥有一个与他们的类向关联的CLASS实例,所有具有相同维度和类型的数组都是一个类的实例,而不管数组的长度。

3程序记数器

每个线程都有自己的程序记数器,它的内容总是下一条将被执行指令的地址。

4Java栈

当一个线程被创建时都将得到自己的程序记数器和Java栈,Java栈以帧为单位保存调用信息。当线程调用一个方法时,JVM会压如一新的栈帧到Java栈,反之则弹出。也就是说,JVM只会对Java栈执行两种操作:以帧为单位的压栈和弹栈。帧的大小根据调用信息是可变的,后边做详细的介绍。由于Java栈上的数据是此线程私有的,因此不需考虑多线程下的栈数据的线程安全问题。

InsideJVM栈帧由三部分组成:局部变量区,操作数栈和帧数据区。

a.局部变量区

局部变量区的大小由调用方法的参数和方法的局部变量所决定。编译器按声名顺序将他们放到局部变量数组,此数组以字长为单位,从0开始记数。如果是实例方法,数组的第一个元素为实例的this指针。

在Java中,所有的对象都按引用传递,并且对象存储在堆中,在局部变量或操作数栈中不会有对象的COPY,只有对象引用。

b操作数栈

操作数栈也是以字长为单位的数组,但不同于局部变量数组以索引去访问,它是通过标准的栈操作,压栈和弹栈来访问的。JVM没有寄存器,程序记数器也无法被程序指令直接访问。JVM的运行方式是基于栈的而非基于寄存器的,JVM的指令是从操作数栈中而不是寄存器中取得操作数的。虽然指令也可以从其他地方取得操作数,比如从字节码流中跟随在操作码之后的字节中或从常量池中,但主要还是从操作数栈中获取操作数。

JVM把操作数栈作为它的工作区,大多数指令都要从这里弹出数据,执行运算,然后把结果压回操作数栈,然后等相关的指令将结果再次弹出。操作数栈扮演了暂存操作数的角色。

C帧数据区

除局部变量区和操作数栈外,Java栈帧还数据来支持常量池的解析、正常方法的返回以及异常派发机制。这些信息都保存在Java栈帧的帧数据区中。JVM可以通过帧数据区中指向常量池的指针来执行某个需要用到常量池数据的指令。

5本地方法栈

当线程调用本地方法时,JVM会保持Java栈不变,不再在线程的Java栈中压入新的帧,InsideJVM只是简单地动态连接并直接调用本地方法。但是,本地方法有可能回调JVM中的Java方法,此时该线程会保存本地方法栈的状态并进入Java栈,在Java栈压入新的栈帧。

相关推荐