jvm内存模型
程序计数器+虚拟机栈+本地方法栈 构成了线程独享的内存区域
方法区+堆区 是线程共享的内存区域
1.程序计数器
作用:流程控制、线程切换
特点:线程私有、伴随线程的生命周期、唯一不会产生内存溢出错误的区域
解释:对于一个处理器(或者是多核cpu的一核),在一个确定的时刻都只会执行一条线程中的指令,一条线程中有多个指令,为了线程切换可以恢复到正确执行位置,每个线程都需有独立的一个程序计数器,不同线程之间的程序计数器互不影响,独立存储。
注意:
- 如果线程执行的是个java方法,那么计数器记录虚拟机字节码指令的地址。
- 如果为native【底层方法】,那么计数器为空。
- 这块内存区域是虚拟机规范中唯一没有OutOfMemoryError的区域。
2.虚拟机栈
介绍:虚拟机栈是一个后入先出的栈。 栈帧是保存在虚拟机栈中的,栈帧是用来存储数据和存储部分过程结果的数据结构,同时也被用来处理动态链接(Dynamic Linking)、方法返回值和异常分派(Dispatch Exception)。 线程运行过程中,只有一个栈帧是处于活跃状态,称为“当前活跃栈帧”,当前活动栈帧始终是虚拟机栈的栈顶元素
特点:线程私有、伴随线程的生命周期、方法和栈帧对应(方法调用时产生栈帧、结束时栈帧消失)
栈帧内容:局部变量表、操作数栈(真实工作区,从栈取数运算送回栈)、返回地址(方法出口)、动态链接(运行期间将符号引用转为直接引用)
局部变量表
局部变量表是一组局部变量值存储空间,用于存放方法参数和方法内部定义的局部变量。在Java文件编译为Class文件时,就在方法表的Code属性的max_locals数据项中确定了该方法需要分配的最大局部变量表的容量。
返回地址
一般来说,方法正常退出时,调用者PC计数器的值就可以作为返回地址,栈帧中很可能会保存这个计数器值。而方法异常退出时,返回地址是要通过异常处理器来确定的,栈帧中一般不会保存这部分信息。
关于符号引用转为直接引用:
每个栈帧都包含一个指向运行时常量池中该栈帧所有属性方法的引用,持有这个引用是为了支持方法调用过程中的动态链接。在Class文件的常量池中存有大量的符号引用,字节码中的方法调用指令就以常量池中指向方法的符号引用为参数。
这些符号引用一部分会在类加载阶段转化为直接引用,这种转化称为静态解析。另外一部分将在每一次的运行期期间转化为直接引用,这部分称为动态连接。
关于静态解析和动态链接:
解析(Resolve)是将部分符合条件的符号引用转换成直接引用的过程;
是类加载的其中一个过程,该过程其中一个作用是将部分符合条件的符号引用转换成直接引用。而符合这以条件的方法有:静态方法、私有方法、构造方法、父类方法。它们在类加载时就会把符号引用解析(resolve)为该方法的直接引用,这些方法可以称为非虚方法,与之相反称为虚方法(除final方法)。而虚方法则会在程序运行期间才解析成直接引用。
3.本地方法栈
和虚拟机栈所发挥的作用非常相似,区别是: 虚拟机栈为虚拟机执行 Java 方法 (也就是字节码)服务,而本地方法栈则为虚拟机使用到的 Native 方法服务。
本地方法被执行的时候,在本地方法栈也会创建一个栈帧,用于存放该本地方法的局部变量表、操作数栈、动态链接、出口信息。方法执行完毕后相应的栈帧也会出栈并释放内存空间,也会出现 StackOverFlowError 和 OutOfMemoryError 两种异常。
4.堆
特点:线程共享、存放对象实例、多线程需要同步、有垃圾回收机制
划分:
- young区:包括两个survivor区+1个Eden区; Eden是刚出生的对象,eden和survivor区放满了之后会采用parNew回收方法 复制 到另一个survivor区
- old区:存放age>15的对象或者大对象; 当old区满的时候,就会对整个young区和old区进行清理,stop the world,使用的是CMS标记清理法
- 持久代:据说1.8以后就没了,相对应的内容搬到方法区了;
常见的垃圾回收法 - 标记清理:会产生内存碎片
- 标记整理:代价大
- 复制:需要额外空间,就像上面young区有两个survivor一样,可以倒腾
5.方法区
内容:用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据
class文件信息:魔数、版本号、字段、接口、方法等描述信息
class静态常量池:用于存放编译期间生成的各种字面量和符号引用
GC:垃圾收集行为在这个区域是比较少出现的,但并非数据进入方法区后就“永久存在”了
运行时动态常量池
Class 文件常量池将在类加载后进入方法区的运行时常量池中存放。
一个类加载到 JVM 中后对应一个运行时常量池,运行时常量池相对于 Class 文件常量池来说具备动态性,Class 文件常量(静态常量池)只是一个静态存储结构,里面的引用都是符号引用。
而运行时常量池可以在运行期间将符号引用解析为直接引用。可以说运行时常量池就是用来索引和查找字段和方法名称和描述符的。给定任意一个方法或字段的索引,通过这个索引最终可得到该方法或字段所属的类型信息和名称及描述符信息,这涉及到方法的调用和字段获取。
静态常量池&动态常量池
静态常量池存储的是当class文件被java虚拟机加载进来后存放在方法区的一些字面量和符号引用,字面量包括字符串,基本类型的常量,符号引用其实引用的就是常量池里面的字符串,但符号引用不是直接存储字符串,而是存储字符串在常量池里的索引。
动态常量池是当class文件被加载完成后,java虚拟机会将静态常量池里的内容转移到动态常量池里,在静态常量池的符号引用有一部分是会被转变为直接引用的,比如说类的静态方法或私有方法,实例构造方法,父类方法,这是因为这些方法不能被重写其他版本,所以能在加载的时候就可以将符号引用转变为直接引用,而其他的一些方法是在这个方法被第一次调用的时候才会将符号引用转变为直接引用的。