本篇文章是根据java虚拟机规范(java SE 7版),所编写。
一、java虚拟机数据结构
数据类型
java虚拟机可以操作的数据类型有两类:原始类型和引用类型。
1.原始类型
原始类型不需要在运行期来确定其数据类型,虚拟机的字节码指令本身就可以确定它的操作数指令型是什么。举个例子:
iadd,ladd,fadd,dadd这几条指令的含义是将操作数相加并返回结果,每一条指令都有自己的操作数类型,那顺序为int,long,float,double。
原始类型包括了数值类型(数值类型又分为整数类型和浮点类型),布尔类型,returnAddress类型。其中returnAddress是无法与java数据类型所对应的,也无法在运行期间改变它的值,而布尔类型在Java虚拟机中没有相关的指令,它的运算在编译之后用int型来代替,它的数组的修改和byte共用baload,bastore.
2.运行时数据区
栈帧
栈帧是用来存储局部变量和部分过程结果的数据结构,同时也用来存储动态链接,方法返回值,异常分派。
栈帧随着方法的调用而创建,随着方法的销毁而结束。每一个栈帧都有自己的局部变量表,操作数栈,和指向所属类的运行常量池的引用。
局部变量表
一个局部变量可以保存一个类型为 boolean、byte、char、short、float、reference
和 returnAddress 的数据,两个局部变量可以保存一个类型为 long 和 double 的数据。
局部变量表用来完成方法调用时的参数传递。当一个实例方法被调用的时候,局部变量0必须存储当前实例的引用(即this),后续的参数将传递至1之后的连续的局部变量表上。
操作数栈
java虚拟机从局部变量表或者对象实例的字段中复制常量值或变量值到操作数栈中。在调用方法的时候,操作数栈也用来准备调动方法的参数,以及接收方法的返回结果。
动态链接
每一个栈帧都包含一个引用指向运行时常量池用来实现动态链接。在Class文件中,通过一个方法调用其他方法,或者调用其成员变量时通过符号引用。动态链接就是将符号引用转换为实际引用。类加载的过程中,并将这变量转化为访问这些变量的存储结构所在的运行时内存位置的正确偏移量。由于动态链接的存在,通过晚期绑定使用的其他方法和变量时,将不会对调用他们的方法产生影响。
虚拟机栈
一般用来存放基本数据类型(变量名和值)和对象的引用(引用名和指向堆中对象的指针,这个指针一般指向对象句柄)
java虚拟机线程都有自己的虚拟机栈,这个栈与线程同时创建,用于存储栈帧。所以根据战阵的定义我们可以推出,Java虚拟机栈是存储局部变量和一些过程运行结果的地方,而且与方法的调用与返回也有着关系。
虚拟机栈允许固定大小和动态大小,固定大小超出最大范围会抛StackOverFlowError;
动态大小,如果没有内存可以申请扩展,或者新建立线程是没有足够的内存会抛出OutOfMemoryError。
堆
堆是各线程共享的运行时区域 ,也是供所有实例或数组对象分配内存的区域。
它在Java迅即启动的时候就被创建,他储存了被垃圾回收机器(Garbage Collector GC)所管理的各种对象,这些对象无法显示的被销毁。
如果实际所需的堆超过了自动内存管理系统最大容量就会抛,OutOfMemoryError。
方法区
java7之前,方法区位于永久代(PermGen),永久代和堆相互隔离,永久代的大小在启动JVM时可以设置一个固定值,不可变; java7中,static变量从永久代移到堆中; java8中,取消永久代,方法存放于元空间(Metaspace),元空间仍然与堆不相连,但与堆共享物理内存,逻辑上可认为在堆中
方法区在虚拟机启动的时候被创建,是堆的逻辑组成部分。他存储了一些类的结构信息,例如运行常量池,字段和方法数据、构造函数和普通方法的字节码内容。
抛的异常是OutOfMemoryError。
常量池
所谓静态常量池,即*.class文件中的常量池,class文件中的常量池不仅仅包含字符串(数字)字面量,还包含类、方法的信息,占用class文件绝大部分空间。这种常量池主要用于存放两大类常量:字面量(Literal)和符号引用量(Symbolic References),字面量相当于Java语言层面常量的概念,如文本字符串,声明为final的常量值等,符号引用则属于编译原理方面的概念,包括了如下三种类型的常量:
-
类和接口的全限定名
-
字段名称和描述符
-
方法名称和描述符
运行时常量池,则是jvm虚拟机在完成类装载操作后,将class文件中的常量池载入到内存中,并保存在方法区中,我们常说的常量池,就是指方法区中的运行时常量池。
运行时常量池相对于CLass文件常量池的另外一个重要特征是具备动态性,Java语言并不要求常量一定只有编译期才能产生,也就是并非预置入CLass文件中常量池的内容才能进入方法区运行时常量池,运行期间也可能将新的常量放入池中,这种特性被开发人员利用比较多的就是String类的intern()方法。String的intern()方法会查找在常量池中是否存在一份equal相等的字符串,如果有则返回该字符串的引用,如果没有则添加自己的字符串进入常量池。常量池的好处
常量池是为了避免频繁的创建和销毁对象而影响系统性能,其实现了对象的共享。
例如字符串常量池,在编译阶段就把所有的字符串文字放到一个常量池中。
(1)节省内存空间:常量池中所有相同的字符串常量被合并,只占用一个空间。
(2)节省运行时间:比较字符串时,比equals()快。对于两个引用变量,只用判断引用是否相等,也就可以判断实际值是否相等。
本地方法栈
用来支持native方法(其他语言编写的方法 如c)的执行。