要想对jvm内存模型有深度的了解,要先了解jvm虚拟机是由哪几部分组成的,每一部分的作用是什么。以下是对jdk8进行说明。
jvm虚拟机的组成:类装载子系统、字节码执行引擎、运行时数据区(内存模型)。
我们常说的堆、栈等都是属于运行时数据区内部的,先看下图,再进行详细的讲解
接下来,我们写一个简单的java程序,用这个java程序来执行过程来很好地说明每一块内容所做的内容。
public class Demo {
public static void main(String[] args) {
Demo math = new Demo();
math.compute();
}
public int compute(){
int a = 1;
int b = 2;
int c = (a + b) * 10;
return c;
}
}
该代码运行后,首先会通过类装载子系统加载(C++实现),将字节码放到运行时数据区,由字节码执行引擎(C++实现)执行代码。其中最核心的就是运行时数据区,其中jvm调优主要也是针对该部分区域。
运行时数据区主要有五个部分,栈、方法区、本地方法栈、程序计数器、 堆。
栈
当我们的代码主方法运行,会在栈上分配出一块空间,供给主方法使用。当有多个用户(主方法)运行时,栈会开辟出多块区域,每一块区域运行一个主方法。类似于多线程运行。
main()方法运行后,会在栈上开辟一块区域供给代码运行,其中每个方法会在该区域内创建一个栈帧。如图
每个栈帧中又有局部变量表、操作数栈、动态连接和方法出口。
程序执行的流程就是compute()字节码文件中执行“int a = 1;”时,先把“1”压入操作数栈,然后再把“1”出操作数栈放入局部变量表中的“a”变量。(具体参见jvm指令合计)
操作数栈说白了就是程序在运行过程中临时进行中转的地方。
局部变量表就是compute()中的a、b、c变量。
动态连接就是把符号引用转变为直接引用。看到这里可能一脸懵,什么是符号引用,在我们jvm加载过程中,会把字节码放到方法区中由执行引擎执行。例如“math.compute();”这样的代码一般是由解释器(暂时理解,后续更新JIT时会讲解,里面涉及到一个阈值)逐行解析运行,这样的是符号引用。而动态链接会将“math.compute();”的符号引用转变成compute()里面的代码。动态连接比较抽象,后续涉及到对象头还会再次进行说明。
方法出口,顾名思义就是跳出方法的用途。
方法区
方法区中有很多的常量池,例如,运行时常量池、字符串常量池。主要由常量+静态变量+类信息等组成。类信息就像一些类的代码字节码这种。这部分后面后面会更新jvm优化的部分,其中GC的原因就有常量池相关的。如下图。
本地方法栈
运行过程需要调用一些本地方法,如C++等。比如“new Thread().start();”这行代码,底层调用的就是C++的.dll文件,调用这些方法也需要存储空间,这个空间就是本地方法栈。
程序计数器
每执行过一行代码,字节码执行引擎会动态的修改程序计数器的值。当切换线程时能够准确地执行,每个线程的程序计数器和本地方法栈都是独有的。
堆
jdk8默认分为年轻代和老年代,年轻代又分为Eden区和Suvivor区。年轻代垃圾回收做minorGC,而FullGC会回收整个堆的垃圾对象。但这个过程中会产生Stop the wrold,会导致用户线程卡顿,所以我们进行jvm优化处了配置一些合理的参数外,还要根据业务情况不断地持续优化堆的参数,已达到减少FullGC。当然也不全是优化FullGC,比如JIT等也是有必要做适当的优化。
那么JVM内存模型你懂了吗?如果你有不同的见解,欢迎友好交流,我是小菜。