本文是JVM系列文章第一篇,主要介绍运行时数据结构及相关的基本概念。
一、简介
- 本文主要介绍虚拟机的运行时数据结构及基本的概念信息。
二、运行时数据区图
![]()
三、概念解析
3.1 堆(Heap)
- 堆是线程共享的;
- 几乎所有对象的实例和数组都在堆上分配;
- 如果从分配内存的角度看,所有线程共享的java堆中可以划分出多个线程私有的分配缓冲区(Thread Local Allocation Buffer, TLAB),以提升对象分配时的效率;
- 堆在垃圾回收时有分年轻代和年老代,年轻代又分eden区和俩个survivor区[一个from,一个to],三者的比值默认是8:1:1;
- 最大堆默认的大小是总内存的1/4, 年轻代默认为堆内存的1/3,年老代默认为堆内存的2/3;
- 堆的可配置参数有:最大堆内存: -Xmx, 最小堆内存: -Xms
- 如果在java堆中没有内存完成实例分配,并且堆也无法再扩展时,java虚拟机将会抛出OutOfMemberError异常;
3.2 虚拟机栈(Java Virtual Machine Stack)
3.2.1 运行时栈帧结构
3.2.2 局部变量表
3.2.3 操作数栈
3.2.4 动态链接
3.2.5 返回地址
- 虚拟机栈是线程隔离的,生命周期和线程相同;
- 虚拟机栈描述的是java方法执行的线程模型:每个方法被执行的时候,java虚拟机都会同步创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法被调用直至执行完毕的过程,就对应着一个栈帧在虚拟机栈中从入栈到出战的过程。
- 局部变量表用来存放编译期可知的各种java虚拟机基本数据类型(boolean, byte, char, short, int, float, long, double)、对象引用(reference类型,它并不等同于对象本身,可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或者其他与此对象相关的位置)和returnAddress类型(指向了一条字节码指令的地址)。
- 局部变量表中存储的数据类型的存储空间以局部变量槽(slot)来表示,其中64位长度的long和double类型的数据会占用俩个变量槽,其余的数据类型只占用一个。
- 局部变量表所需的内存空间在编译期间完成分配,当进入一个方法时,这个方法需要在栈帧中分配多大的局部变量空间是完全确定的,在方法运行期间不会改变局部变量表的大小。注意:该处说的大小是指变量槽的数量,而非内存空间大小,虚拟机真正使用多大的内存空间(譬如按照1个变量槽占用32个比特、64个比特,或者更多)来实现一个变量槽,这是完全由具体的虚拟机实现自行决定的事情。
- 虚拟机栈的可配置参数有: 栈大小: -Xss
- 如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常;如果java虚拟机允许动态扩展,当栈扩展时无法申请到足够多的内存则会抛出OutOfMemberError异常;
3.3 本地方法栈(Native Mehtod Stacks)
- 本地方法栈是线程隔离的;
- 本地方法栈与虚拟机栈所发挥的作用非常相似,区别就是虚拟机栈为虚拟机执行java方法(也就是字节码)服务的,而本地方法栈则是为虚拟机执行本地方法(native)服务的;
- 本地方法栈和虚拟机栈一样也会抛出StackOverflowError和OutOfMemberError异常;
3.4 程序计数器(Program Couonter Register)
- 程序计数器是线程隔离的;
- 可以看作是当前线程所执行的字节码的行号指示器,字节码解释器工作时就是通过改变这个计数器的值来选取吓一跳需要执行的字节码指令,他是程序控制流的治时期,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成;
- 如果线程正在执行的时一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址,如果正在执行的是本地(native)方法,这个计数器值则为空(Undefined);
- 该区域是唯一一个在规范中没有任何OutOfMemberError情况的区域;
3.5.1 jdk8的新概念,TODO
3.5.2 方法区(Method Area)
- 方法区是线程共享的内存区域;
- 方法区的别名叫做非堆(Non-Heap),目的是与java堆区分出来;
- 方法区用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据;
- 如果方法区无法满足新的内存分配需求时,也会抛出OutOfMemberError异常;
3.5.3 运行时常量池(Runtime Constant Pool)
- 运行时常量池是方法区的一部分,即也是线程共享的内存区域;
- Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池表(Constant Pool Table),用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中;
- 运行时常量池也会抛出OutOfMemberError异常;
- 方法区本身是一个逻辑上的区域,在jdk7及之之前,HotSpot使用永久代来实现方法区时,实现时完全符合这种概念的,而在jdk8及之后,类变量会随着Class对象一起存放在java堆中,这时候“类变量在方法区”就完全时一种对逻辑概念的表述了;
3.6 直接内存(Direct Memory)
- 直接内存并不是虚拟机运行时数据区的一部分,也不是《java虚拟机规范》中定义的区域;
- 直接内存会受到本机总内存(包括物理内存、SWAP分区或者页文件)大小以及处理器寻址空间的限制,一般服务器管理员配置虚拟机参数时,会根据时机内存区设置-Xmx等参数信息,但经常忽略掉直接内存,是的各个内存区域总和大于物理内存限制(包括物理的和操作系统级的限制),从而导致动态扩展时出现OutOfMemberError异常;
三、总结