一、虚拟机内存结构
在jvm虚拟机运行程序的过程中,会管理着一块内存区域,称为运行时数据区。
在运行时数据区包含一下几块区域:
- 程序计数器
- 虚拟机栈
- 本地方法栈
- 堆区
- 方法区
二、程序计数器
程序计数器也可以叫做PC寄存器,它的作用是用来存储指向下一条指令的地址,也就是即将要执行的指令代码,由执行引擎来读取下一条指令。Java程序中分支,循环,跳转,异常处理,线程恢复等都需要依赖这个PC寄存器来完成。
特点:
- 内存占用很小,几乎可以忽略不记,也是运行速度最快的存储区域。
- 每个线程都拥有自己的程序计数器,是线程私有的。
- 生命周期与线程相同。
- 是虚拟机中唯一一个没有OutOfMemoryError的内存区域。
那么什么是指令地址?我们来举个例子:
public class PcRegister {
public static void main(String[] args) {
int a = 10;
int b = 20;
int c = a+b;
}
}
上面的代码,我们编译之后会生成.class文件,然后通过javap -v命令来查看这个字节码内容,如图所示,圈出的部分就是指令地址,后面的就是具体的指令。
三、虚拟机栈
又称Java栈,作用是保存方法的局部变量,部分结果,并参与方法的调用和返回。
特点:
- 每个线程都有自己的栈,线程私有。
- 生命周期与线程相同。
- 每个方法执行时,都会创建一个栈帧。
- 栈帧又包含:局部变量表,操作数栈,动态链接,方法返回地址和一些附加信息。
异常:
Java虚拟机规范允许Java栈的大小时动态的或者固定不变的。
- 如果是固定大小的虚拟机栈,当分配的栈容量超过虚拟机栈的最大容量时,会抛出StackOverflowError。
- 如果时动态大小的虚拟机栈,当动态扩展时无法申请到足够的内存时,会抛出OutofMemoryError。
四、本地方法栈
虚拟机栈用于管理Java方法的调用,而本地方法栈用于管理本地方法的调用。
特点:
- 每个线程都有自己的本地方法栈,线程私有。
- 生命周期与线程相同。
- 异常方面与虚拟机栈相同。
五、堆区
一个Jvm实例中,只存在一个堆内存,堆是Jvm内存中最核心的区域,也是垃圾回收器管理的主要的区域,堆区在Jvm启动时被创建,其空间大小被确定,是Jvm管理最大的一块内存空间,主要作用是用来存储Java对象实例。
特点:
- Jvm中占内存最大。
- Jvm中所有线程共享堆。堆中还可以划分线程私有的缓冲区。
- 方法结束后,堆中的对象不会马上被移除,需要在垃圾回收时才会移除。
- 由于收集器基本都采用分代收集算法,所以堆内存又细分为:新生代和老年代。
新生代:年轻代又分为一个Eden空间,Survivor0和Survivor1空间(有时也叫from,to区)新创建的对象就是先进入新生代。
老年代:大对象直接进入老年代。如果新生代中的对象,经过一段时间还未被回收就会进入老年代。
异常:
当堆中没有足够的内存完成对象的分配时,会抛出OutOfMemoryError。
六、方法区
方法区和堆区相同的是,都是各个线程共享的内存区域,jvm启动时创建,关闭时释放,并且它实际的物理地址空间和堆区一样都可以是不连续的,大小也可以固定大小或可扩展,主要作用是存储已被虚拟机加载的类的信息,方法信息,字段信息,常量,静态变量,编译后的代码缓存等。
特点:
- Jvm中所有线程共享方法区。
异常:
方法区的大小决定了系统可以保存多少个类,如果定义了太多的类,导致方法区溢出,同样会抛出OutOfMemoryError。