JVM内存结构
一、程序计数器
1.1 定义
程序计数器是一个记录着当前线程所执行的字节码的行号指示器,它记的是下一条JVM指令的执行地址。它是Java
对硬件的抽象,在物理上是由寄存器实现的。
1.2 作用
在说明程序计数器的作用之前,先来看看这段简单的Java
代码。
public class PrintNum {
public static void main(String[] args) {
System.out.println(1);
System.out.println(2);
System.out.println(3);
}
}
下面是该源代码被编译成JVM
指令的结果。
观察这张图,不难发现每个指令前都有一个数字,这是指令的偏移地址,简称为行号,程序计数器的作用就是存储下一条指令的行号。
以上图为例,当第一条指令执行时,JVM
会将下一条指令的行号“3”放入程序计数器中。当第一条指令执行完时,解释器会根据程序计数器中的行号拿到下一条指令,继续运行。
为什么要多此一举先把下一条指令的行号放入程序计数器中,直接取指令然后执行不是更简单吗?
。JVM
是支持多个线程同时运行的,这就涉及到CPU
的调度问题了。线程甲正执行的好好的, 大哥CPU
告诉甲说,你累了,我陪会儿乙,甲只好乖乖休息。一段时间后,大哥回来了,这时甲就可以根据程序计数器中的行号取到下一条指令接着执行。
这里有一点要注意,因为程序计数器记录的是行号,是会重复的,所以多个线程不能同时用一个,不然就乱了。所以程序计数器是线程私有的。
1.3 特点
- 线程私有。
- 不会存在内存溢出。程序计数器中的行号永远只会有一个,当前指令执行时,会拿下一条指令的行号替换当前的行号。因此就不存在内存溢出问题。
二、虚拟机栈
2.1 定义
栈:虚拟机栈是每个线程运行所需要的内存。
栈帧:每个栈由多个栈帧组成,对应着每次方法调用时所占用的内存。
2.2 作用
虚拟机栈是用于描述Java
方法执行的内存模型。每一个方法执行时,会创建一个栈帧,栈帧的结构为:局部变量表、操作数栈、动态链接、方法出口,可以理解成一种数据结构,专门用于描述Java方法执行的数据结构。虚拟机栈的最小单位就是栈帧。
线程运行中,当执行到一个方法时,就会生成一个栈帧并压入栈中。当这个方法执行完之后,会将这个栈帧弹出,释放对应的内存空间。
一个栈中可能同时存在多个栈帧,如方法一调用方法二时,就会将两个栈帧都压入栈中。
注意每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法。
简单理解:如当执行main
方法时,就会开启一个main
线程。这时虚拟机就要拿出一块区域给这个线程使用,这一块区域就是虚拟机栈。同时也不难理解,虚拟机栈也和程序计数器是线程私有的。
Java
的线程的运行其实就是在执行一个个方法,JVM
通过栈帧来表示方法的执行。于是当线程中有方法执行时,就需要对应生成一个栈帧放入虚拟机栈中。表示此线程有此方法正在运行。当该方法执行完之后,这个栈帧也会弹栈,释放其内存,所以也不涉及垃圾回收。
2.3 特点
- 不涉及垃圾回收。因为当方法执行完,栈帧会弹栈,对应的内存就会释放掉。不会产生垃圾。
- 注意栈的大小,当分配给栈的内存过大时,相应地,同时可运行地线程数就会减少。
三、本地方法栈
3.1定义
本地方法:Java
语言是有限制的,不能直接和操作系统打交道,因此就需要通过调用C
,C++
编写的方法来和底层交互。这些用C
,C++
编写地方法就是本地方法。(代码中用native
修饰的方法就是本地方法。)
本地方法栈:本地方法执行用到的内存空间就是本地方法栈。
四、堆
4.1 定义
堆是运行时数据区中最重要的区域,也是垃圾回收的重点关注对象。由于现代垃圾收集器大部分都是基于分代收集理论设计的,在这种理论下,堆的结构被分为新生代,老年代,其中新生代又被分为伊甸园和幸存区,如下图。
当在程序中new
一个对象时,JVM
会首先将其放到伊甸园区。如果伊甸园区即将占满,没有空间给下一个对象时,就会进行一个MinorGC
,此时会回收新生代中已死的对象。并将存活对象放入幸存区From
中。这些存活对象由于经历过一次GC
,所以它们的寿命为1
。之后如果伊甸园区又满了,就会再进行MinorGC
,此时会将伊甸园中和幸存区From
中的对象放入幸存区To
中,并将这些对象的寿命加1
,之后交换From
区和To
区。
一个对象经过15
次GC
(这个数值不是固定的,最大为15
)后依旧存活,会将其放入老年代。当老年代和新生代的空间都被占满时,会进行一次FullGC
,对整个堆空间进行一次垃圾回收。
如果想要了解堆的垃圾回收,可以参考这篇博文。链接: JVM垃圾回收机制
4.2 特点
- 存在内存溢出。如果新建的对象一直被回收就会造成内存溢出问题。
- 是线程共享的。所以堆中对象都要考虑线程安全问题。
五、方法区
5.1 定义
方法区是JVM
所有线程共享的区域,它存储了与类结构相关的信息:运行时常量池,成员变量,方法数据,成员方法、构造方法的代码。它的大小就决定了系统可以保存多少种类。
方法区在虚拟机启动时创建,它在逻辑上是堆的组成部分(具体实现上是否为堆的一部分视厂商而定,如永久代,元空间都是其实现)。JVM
规范并不强制方法区的位置。
5.2 具体实现
在1.6中方法区的实现叫永久代,1.8叫元空间。在1.8中,方法区被移出JVM
内存,被放置在本地内存(操作系统内存)中。
从这两张图中可以看出有一个特别的区域——字符串常量池StringTable
,它在1.7之后被放进了堆中。如果想要了解这块区域,可以看看这篇文章,链接: 字符串常量池StringTable简介