JVM内存区域结构

Smile_slime_47

运行时数据区域


线程共享 堆(Heap)
方法区(Method Area) 运行时常量池(Runtime Constant Pool) 字符串常量池(String Constant Pool) JIT缓存
线程私有 虚拟机栈(VM Stack)
本地方法栈(Native Method Stack)
程序计数器(Program Counter Register)

其中方法区及其运行时常量池在JDK 8后被元空间(Metaspace)替代,存储在本地内存空间而非JVM内存中

线程私有区域


线程私有区域的生命周期是和线程绑定的,他们随着线程的创建而创建,随着死亡而死亡

程序计数器

PC指示当前线程执行到的字节码行号,每个线程都有自己的PC计数器

  • 对于线程本身而言,PC支持了线程了分支、跳转、循环等功能
  • 对于多线程而言,PC使得当前线程被切换回来后仍能继续运行

虚拟机栈和本地方法栈

不论是虚拟机栈还是本地方法栈,JVM中栈的数据单元都为栈帧(Stack Frame),每个栈帧存放了一个方法调用的局部空间,每一次方法调用就会将其栈帧推入栈,而每一次方法调用结束就会将其栈帧出栈

虚拟机栈存放的是Java方法调用(即字节码)的栈帧,而本地方法栈存放的是Native方法调用的栈帧

栈帧的组成包含

  • 局部变量表
    • 局部变量表存放了方法中的局部变量,包括8个基本数据类型和reference引用类型
  • 操作数栈
    • 操作数栈用于存放执行过程中的临时结果和临时变量
  • 动态链接
    • 当该方法需要调用其他类和方法时,需要通过方法区中的运行时常量池存储的引用链接寻找,而栈帧的动态链接存储了调用方法对应在运行时常量池中的引用(如下图所示)
  • 返回地址

对于栈而言,日常编写代码会遇到两种常见的Exception——StackOverFlowOutOfMemory

  • 当栈容量无法扩展,程序运行时不断地推入栈帧导致达到栈容量上限时,则会抛出StackOverFlow异常
  • 当栈容量可以扩展,程序运行时不断地推入栈帧导致栈容量无法继续申请内存空间,则会抛出OutOfMemory异常

线程共享区域


堆用于存放程序运行过程中的对象实例,由于GC主要干涉堆,因此也被叫做GC堆

根据对象的代数不同,堆还可分为:

  • 新生代
    • 新生代根据还可细分为Eden区、Suvivor 0区和Survivor 1区
  • 老年代

根据对象访问的方式,分为句柄访问直接访问

  • 对于句柄访问的虚拟机,堆中还会分配一块空间叫做句柄池,引用指向句柄池中对应的句柄,句柄存储了两个地址,分别为堆中实例对象的地址和方法区中Class对象的地址
  • 对于直接访问的虚拟机,引用直接指向堆中的实例对象,Class对象地址存储在实例对象的对象头中

方法区

方法区是JVM虚拟机规范中规定的一片区域,不同的虚拟机及JDK版本有不同的实现方式

方法区用于存储JVM读取的Class对象,包含Class文件中的类信息、字段信息、方法信息、常量、静态变量、即时编译器编译后的代码缓存等数据

永久代元空间分别是方法区的两种实现方式

  • 永久代在JDK 8之后永久代被中移除,改为在本地内存中常驻的元空间存储
  • 元空间是在本地内存而非虚拟机内存中,这导致JVM有了耗尽系统内存的可能性

运行时常量池

Class文件中有一部分是存储着字面量和符号引用常量池

  • 其中符号引用也是一种字面量,它可以帮助虚拟机找到调用类、方法、接口、字段等目标的地址,但是与地址不同的是,符号引用的值并非内存地址,而是某种形式的字符串,如文件路径

在加载Class对象时,文件中的常量池会被同步加载到方法区中的运行时常量池中

字符串常量池

字符串常量池本质上是一个HashMap,保存了程序中String对象的引用(value)字符串内容(key)的映射关系,从而避免字符串的重复创建,举例来说:

1
2
3
String aa = "ab";
String bb = "ab";
System.out.println(aa==bb);// true

因为aa和bb指向的是字符串常量池中的同一个对象,所以会返回true

当我们通过字面量赋值新建一个String a=”ab”时,JVM会先在字符串常量池中找是否存在key为”ab”的键值对

  • 若不存在键值对,则字符串常量池会创建出一个字符串值(Byte数组)->字符串对象的键值对对象
  • 在找到键值对后,JVM会直接返回字符串常量池中的String对象的地址
  • 对于new新建如 String a=new String("ab"),String对象仍然会被新建在堆中而非字符串常量池,但是仍然会在字符串常量池中新建对应的键值对

在JDK 1.7之后,字符串常量池被转移到了堆中,因为字符串仍然属于被经常回收的对象

Comments