探究java内存模型以及工作流程
JAVA内存模型
1.JAVA程序执行流程
- java文件经过编译器编译生成class文件
- class文件进入jvm,由各种类加载器加载
- 加载完毕后交给jvm执行引擎执行
- jvm内存模型就是运行时数据区,程序运行时用到的数据以及相关信息保存区
2.jvm内存模型结构
以下是内存模型图
1.PC程序计数器
- 每一个线程对应有一个计数器
- 各线程的计数器是私有的,互不影响的,线程安全的
- 程序计数器可以记录线程正在执行的内存地址,以便被中断线程恢复执行时再次按照中断时的指令地址继续执行
2.Java栈JavaStack(虚拟机栈JVM Stack)
- 每个线程会对应一个java栈
- 每个栈由若干个栈帧组成
- 一个栈帧对应一个方法
- 栈帧在方法调用的时候创建并且入栈,方法返回时,该栈帧弹出栈帧中的元素作为返回值,并且该栈帧出栈被清除。也就是说,我们在使用java的时候,方法中调用方法,在内部方法返回之前,调用方的方法一直处于调用中,只有被调用的方法执行结束,调用方的方法才可以继续进行,也就符合我们这个java栈的结构特点,栈最底层为最先调用的方法,每当方法调用一个新的方法,就添加一个栈帧,先进后出原则,必须后来的栈帧清除了,下面的栈帧才可以离开
- 栈顶的栈帧叫做活动栈,是当前CPU正在执行的方法
- 线程请求的栈深度大于虚拟机所允许的深度,将抛出的StackOverflowError异常
- 栈扩展时无法申请到足够的内存,就会抛出OutOfMemoryError异常
关于栈帧:
- 局部变量表:一个局部变量可以保存一个类型为boolean、byte、char、short、int、float、reference和returnAddress类型的数据。reference类型表示对一个对象实例的引用。returnAddress类型是为jsr、jsr_w和ret指令服务的,目前已经很少使用了。
- 操作数栈:当一个方法刚刚开始执行时,其操作数栈是空的,随着方法执行和字节码指令的执行,会从局部变量表或对象实例的字段中复制常量或变量写入到操作数栈,再随着计算的进行将栈中元素出栈到局部变量表或者返回给方法调用者,也就是出栈/入栈操作。一个完整的方法执行期间往往包含多个这样出栈/入栈的过程。
- 动态连接
- 在class文件中,一个方法要调用其他方法,需要将这些方法的符号引用转化为其在内存地址中的直接引用,而符号引用存在于方法区中的运行时常量池
- java虚拟机中,每个栈帧都包含一个指向运行时常量池的方法(对应该栈帧)的符号引用,目的是为了支持方法调用过程中的动态连接。
- 这些符号引用有的在类加载的时候或者第一个次使用的时候被直接引用(我理解为构造器和懒加载),这类转化称为静态解析;而有的将在每次调用对应方法的时候被直接引用,即为动态连接
- 动态方法
- 当一个方法开始执行时,可能有两种方式退出该方法:
- 正常完成出口:指方法正常完成并退出,没有抛出任何异常
- 异常完成出口:指方法执行过程中遇到异常,并且这个异常在方法体内部没有得到处理,导致方法退出
无论方法采用何种方式退出,在方法退出后都需要返回到方法被调用的位置,程序才能继续执行,方法返回时可能需要在当前栈帧中保存一些信息,用来帮他恢复它的上层方法执行状态
3.方法区(MethodArea)以及常量池
- 方法区是java堆中的永久区,也就是方法区也属于堆
- 方法区存放了要加载的类的信息(名称、修饰符等),类中的静态变量、类中定义为final的常量、类中的Field信息、类中的方法信息
- 方法区是被java线程共享的,我们平时一直讲的线程安全,其实就是为了保证该区域的数据可以被多个线程正常的使用
- 方法区要使用的内存超过其允许的大小时,会抛出异常OutOfMemoryError:PremGen space的错误信息
- 方法区中包含了常量池(运行时常量池Runtime Constant Pool):
- 存储了字面量和引用量
- 字面量:字符串、final变量等
- 类/接口、方法和字段名称和描述符
- 常量池在编译的时候就确定了,并保存在已编译的.class文件中
4.本地方法栈
- 本地方法栈和java栈所发挥的作用非常相似,区别不过是java栈为JVM执行的java方法服务,本地方法栈为JVM执行的Native方法服务(Native方法是由其他编程语言实现的)
- 本地方法也会抛出StackOverflowError和OutOfMemoryError异常
3.JVM内存模型工作流程
解释:
- 当一个java文件被编译器编译好,通过类加载器加载后其类名,方法名关键字等信息都会进入到方法区
- 执行引擎从方法区中找到方法,并在java栈中创建对应的栈帧,且生成一个程序计数器,用来记录线程执行的信息,且方法中如果创建了引用变量,真正的对象存放在堆中,对象引用地址则存储在java栈的对应栈帧的局部变量区中。
- 执行引擎请求cpu执行方法,将栈帧的参数加载到cpu的寄存器和高速缓存中
- 执行结果返回到java栈中,对应去到局部变量区中或者堆中的对象(主内存)
注意一点,在考虑CPU的情况下,主内存为jvm内存模型,工作内存为cpu,不考虑cpu的情况下,方法区和堆为主内存,java栈为工作内存