JVM运行时数据区
概念
Java虚拟机在执行Java程序的过程中会把它管理的内存分成若干个不同的数据区域,这些区域都有各自的用途以及创建和销毁的时间,有的区域时跟随虚拟机的启动而存在,有些区域则依赖于线程的启动和结束建立和销毁。
区域分类
上图中堆和方法区属于线程共享区域,虚拟机栈、本地方法栈和程序计数器(PC计数器)属于线程私有区域
方法区
作用:
用于存放已经被虚拟机加载的类信息、常量、静态变量、实时编译的数据。
特点:
- 线程共享的内存区域。
- 可能会出现OutOfMemoryError(内存溢出)。
- 方法区是可以进行垃圾收集的,也可以选择不进行GC。主要回收两部分:常量池中废弃的常量和不再使用的类型。
运行时常量池
介绍:
属于方法区的一部分。
class文件中类或接口常量池表的表现形式,若干常量组成,比如:编译期可知的数值字面量、运行时解析的方法和字段引用等。
特点:
- 线程共享区域。
- 翻译出来的直接引用也存储在运行时常量池中。
- 可以动态的加入程序运行时产生的常量,比如string。
- 当无法申请到内存时将抛出OutOfMemoryError(内存溢出)。
堆
作用:
存放所有类实例和数组对象。
也根据不同的版本号存放静态变量和字符串常量等
在虚拟机启动就根据相关堆参数,创建堆,也是垃圾收集器工作的主要区域。
特点:
- 线程共享的区域。
- 为了解决线程安全问题,堆内部会划分出多个线程私有的分配缓冲区(Thread Local Allocation Buffer, TLAB)
- 可能抛出异常,OutOfMemoryError(内存溢出),也就是当堆中没有内存足以给实例分配了。
堆主要有两个部分:年轻代和年老代,比例是1:2。可以通过参数-XX:NewRatio=x来进行分配,-XX:NewRatio=2这是默认值。
年轻代
存放新诞生的对象。
8:1:1的比例分配:
- Eden(伊甸园)
- Survivor0(简称s0区)和Survivor1(简称s1区)
特点:
- 主要存放新诞生的对象。
- GC垃圾回收的主要场所。
- GC比较频繁。
年老代
年轻代中没有被垃圾回收掉的对象
特点:
- 不会频繁进行垃圾回收。对资源的消耗比较大。
更多堆相关内容请点击Java体系结构——之运行时数据区——堆
虚拟机栈
存储栈帧。方法的调用与返回基于栈帧来实现。
特点:
- 线程私有区域。
栈帧
用来存储数据和部分过程结果的数据结构,同时也用来处理动态链接、方法返回、异常分派等工作。
特点:
- 栈帧的生命周期是跟方法一致的,随着方法的调用而创建,方法的结束或异常而销毁。
每个栈帧都由局部变量表、操作数栈、动态链接组成。
局部变量表
每个栈帧内部都包含一组称为局部变量表的列表,变量表的长度在编译期决定。
一个局部变量可以存储一个基本数据类型或一个对象引用(referance),returnAddress的数据。存储long或double需要两个局部变量才能存储。
当虚拟机要使用局部变量表里的数据时通过索引来定位,默认从0开始,由于long和double占用两个局部变量所以它的索引较特殊,取决于最小的那个值,比如某个long类型数据在索引n和n+1里存储了,那么它对应的索引值就是n.
虚拟机通过局部变量表来完成方法调用时的参数传递。如果是类方法,它的参数依次从0开始的位置传递到局部变量表,如果是实例方法则第0位置存储所在对象的引用(this),从1开始传递参数。
操作数栈
操作数栈是属于栈帧中的栈,其实它的全名叫做当前栈帧的初操作数栈。栈,栈帧,操作数栈的关系需要梳理清楚:
- 栈:是虚拟机运行时数据区的一个逻辑区域,里面存储了一个个栈帧。
- 栈帧:栈帧代表一个方法的整个生命周期,里头存储了局部变量表,操作数栈,动态链接
- 操作数栈: 刚刚创建时操作数栈是空的。虚拟机提供一些指令从局部变量表把一些常量或者变量值加载到操作数栈,也提供了从操作数栈取走数据的指令。
调用方法时操作数栈用来准备调用方法参数以及接受方法的返回结果。
动态链接
动态链接是用来完成运行时绑定操作的。在栈帧中有一个指向常量池的当前类的一个引用。在class文件里一个方法要是调用其他方法或者方法其他成员变量,则需要通过符号引用来表示。
- 动态链接的作用就是将符号引用转换为直接引用。
- 类加载的过程中将要解析尚未被解析的符号引用,并且把对变量的访问转换为正确的偏移量。
本地方法栈
与java虚拟机栈类似,但是不同的是Java虚拟机栈是为java方法服务(字节码),本地方法栈是为非java语言但是虚拟机又使用到的方法服务的(比如类加载器中最顶层的实现使用的是c++)。同样的,本地方法栈也会抛出StackOverflowError和OutOfMemory异常。
程序计数器(PC寄存器)
Program Counter Register:PC寄存器或者叫做程序计数器。
概念:
记录了当前线程所执行的字节码的行号指示器,并记录了下一条指令所在的地址。
作用:
1、字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令。
2、线程恢复等基础功能都需要依赖这个计数器来完成。实现程序的分支、循环、跳转、异常处理、线程控制等。
特点:
- 线程私有,每个线程都有一个程序计数器。
- 线程之间的程序计数器互不影响,独立存储。
- 在Java虚拟机规范里边,唯一一个没有规定任何OutOfMemoryError(内存溢出)的区域。
- 生命周期随着线程的创建而创建,随着线程的结束而消亡。
- 是一块较小的内存空间。
- 程序计数器执行本地native方法时值为空(Undefined),因为native调用的是C/C++库,并非Java实现,所以无法产生相应的字节码文件,自然也就无法记录指令的地址。
- 线程正在执行java方法时,计数器的值为当前字节码指令的地址。