Java虚拟机学习笔记(一)
Java虚拟机运行时数据区域
Java虚拟机将其所管理的内存划分为若干个不同的数据区域。这些区域都有着各自的用途,以及创建和销毁时间。其中有一些会随着虚拟机启动而启动,随着虚拟机退出而销毁;有些则是与线程一一对应,这些与线程对应的数据区域会随着线程开始和结束而创建和销毁。划分的几个运行时区域如下:
1. PC寄存器
Java虚拟机支持多条线程同时执行,每条线程都有自己的PC寄存器(program counter register)。PC寄存器用来指示当前线程所执行字节码指令的地址,说白了就是记录各个线程执行的位置。如果正在执行的是Native方法,PC寄存器的值则为空(undefined)。
2. Java方法栈
与PC寄存器一样,Java方法栈也是线程私有的。这个栈的生命周期与线程相同,与线程同时创建,线程结束也会随着销毁。Java方法栈用于存储栈帧(Frame,栈帧是方法运行时的基础数据结构,包括局部变量表、操作数栈、动态链接、方法出口等信息)。每一个方法从调用直至执行结束完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。(栈容量设置:-Xss设定)
Java虚拟机规范中,定义了两种异常情况:
(1)如果线程请求分配的栈深度超过Java方法栈允许的最大深度,Java虚拟机将会抛出一个StackOverflowError异常。
(2)如果Java方法栈可以动态扩展,并且在尝试扩展的时候无法申请到足够的内存,或者在创建新的线程时没有足够的内存去创建对应的虚拟机栈,那么Java虚拟机将会抛出OutOfMemoryError异常。
3. 本地方法栈
本地方法栈(Native Method Stack)与虚拟机栈的作用类似。虚拟机栈为虚拟机执行Java方法服务,而本地方法栈则为虚拟机执行Native方法服务。与虚拟机栈一样,本地方法栈也会抛出StackOverflowError异常和OutOfMemoryError异常。
4. Java堆
Java虚拟机中,Java堆(Java heap)是被所有线程共享的一块区域,在虚拟机启动时创建。此内存区域的唯一目的是存放类实例对象和数组对象。几乎所有的类实例对象和数组对象都在这里分配内存。是Java虚拟机所管理的内存中最大的一块。(存储对象实例数据)
Java堆可以是固定大小的,也可以是扩展的。当前主流的Java虚拟机都是按照可扩展来实现,通过参数-Xmx和-Xms设置堆的最小值和最大值。
Java堆可能抛出异常的情况:堆中没有足够的内存完成实例分配,并且堆也无法再扩展时,Java虚拟机将会抛出OutOfMemeroryError异常。
5. 方法区
与Java堆一样,方法区(method area)也是各个线程共享的区域,在虚拟机启动时创建。主要是用于存储每一个类的结构信息,例如,运行时常量池(runtime constant pool)、字段和方法数据、构造函数和普通方法的字节码内容。还包括一些在类、实例、接口初始化时用到的特殊方法。(存储对象类型数据)。配置方法区大小使用参数:-XX:PermSize和-XX:MaxPermSize。
方法区可能抛出异常的情况:方法区的内存空间不能满足内存分配需求时,Java虚拟机将会抛出一个OutOfMemoryError:PermGenspace的异常。
Java虚拟机加载class文件后,其中Java类会被存储到方法区(Method Area)。实际运行时,虚拟机会执行方法去内的代码。
6. 直接内存
NIO类是基于通道(Channel)与缓冲区(Buffer)方式,它可以使用Native函数库直接分配堆外内存,然后通过一个存储在Java堆中的DirectByteBuffer对象作为这块内存的引用进行操作。NIO操作需要使用到直接内存(Direct Memory)。
直接内存:可通过-XX:MaxDirectMemorySize调整大小。内存不足时抛出OutOfMemoryError或者OutOfMemoryError:Direct buff memory。
6. 运行时常量
运行时常量池(runtime constant pool)是方法区的一部分。在加载类和接口到虚拟机后,就在方法区创建对应的运行时常量池。它是class文件中每一个类或接口的常量池表的运行时表示,它包含了若干种不同的常量。用于存放在编译期间生成的各种字面量个符号引用。
可能抛出异常的情况:在创建类和接口的运行时常量池时,当常量池所需的内存空间超过方法区所能提供的最大值时,抛出OutOfMemoryError异常。
7. 栈帧结构
栈帧存储于Java虚拟机栈中。栈帧是一种数据结构,用于存储方法运行时的数据,包括局部变量表、操作数栈、动态链接、方法出口等信息。每一个栈帧都有自己的局部变量表、操作数栈、和指向当前方法所属的类的运行时常量池的引用。栈帧随着方法调用而创建,随方法结束而销毁。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈、出栈的过程。
7.1 局部变量表
局部变量表是一组变量值存储空间,用于存放实例方法的”this指针”、方法参数以及方法内部定义的局部变量。在Java程序编译为Class文件时,就在方法的Code属性的max_locals数据项中确定了该方法所需要分配的局部变量表的最大容量。
一个局部变量可以保存一个类型为boolean、byte、char、short、int、float、reference、和retuanAddress的数据。两个局部变量可以保存一个类型为long或者double的数据。这么看来,局部变量表等价于一个数组,并且可以用正整数来索引。出了long、double值需要用两个数组单元来存储之外,其他基本类型以及引用类型的值均占用一个数组单元。(javap命令编译出来的class文件,方法里面的局部变量是用槽(slot)来表示的,double两个槽,int一个槽)。
因此,在32位的HotSpot中基本类型将占用4个字节;而在64位的HotSpot中,基本类型将占用8个字节。
参考资料:
【1】 周志明. 深入理解Java虚拟机:JVM高级特性与最佳实践(第二版)【M】.北京:机械工业出版社,2013
【2】爱飞翔,周志明(译).Java虚拟机规范(Java SE 8版)【M】.北京:机械工业出版社,2015.