参考:https://www.cnblogs.com/liululee/archive/2019/09/04/11461998.html
https://www.cnblogs.com/zongheng14/p/12041005.html
一.jvm体系结构图
JVM是如何工作的
首先要知道JVM分为三个主要子系统:
1、类加载器子系统
2、运行时数据区
3、执行引擎
类加载器子系统
Java的动态类加载功能由类加载器子系统处理。它装载的链接。在运行时而不是编译时首次引用类时初始化类文件。
1.1 加载【loading】
类将由该组件加载。引导类加载器、扩展类加载器和应用程序类加载器是有助于实现这一目标的三个类加载器。
启动类加载器 – 负责从引导类路径加载类,除了rt.jar什么也没有。这个加载程序将获得最高优先级。
扩展类加载器 – 负责加载ext文件夹(jre\lib)中的类。
应用程序类加载器 –负责加载应用程序级类路径、所述环境变量的路径等。
上述类加载器在加载类文件时将遵循委托层次结构算法。
所以,Java 的类的加载机制,永远是从 启动类加载器 --> 拓展类加载器 --> 应用程序类加载器 这样的一个顺序进行加载
1.2 链接【linking】
验证 – 字节码验证器将验证生成的字节码是否正确,如果验证失败,我们将得到验证错误。
准备 – 内存将为所有静态变量分配默认值。
解析 – 所有符号内存引用将被来自方法区域的原始引用所替换。
1.3 初始化【initialing】
这是类加载的最后阶段;在这里,所有静态变量都将被赋初始值,并且静态块也会被执行。
2. 运行时数据区
运行时数据区被分为五个主要组件:
2.1方法区 – 所有类级数据都将存储在这里,包括静态变量。每个JVM只有一个方法区,它是资源共享的。(**类信息,静态变量,常量(final修饰的)**)
2.2堆 –所有对象及其对应的实例变量和数组都将存储在这里。每个JVM也仅有一个堆。由于方法区和堆被多个线程共享内存,因此存储的数据不是线程安全的。
【堆空间不够用,会报outofmemoryerror的错误】
2.3java虚拟机栈–每个线程将创建一个单独的运行时栈。每个方法调用都会在栈内存中生成一个条目,称为栈帧。所有本地变量都将在栈内存中创建。栈区域是线程安全的,因为它不是内存共享的。
栈区域被分为三个部分:
2.3.1局部变量数组 – 与方法相关,涉及到局部变量以及相应的值都将存储在这里。
2.3.2操作数堆栈 –如果需要执行任何中间操作,操作数堆栈充当运行时工作区来执行操作。
2.3.3帧数据 – 所有与方法对应的符号都存储在这里。在任何异常情况下,catch块信息都将保存在帧数据中。
2.4PC寄存器 – 每个线程将有单独的PC寄存器,以保持当前执行指令的地址一旦指令执行,PC寄存器能顺利地更新到下一条指令。
2.5本地方法栈 – 本机方法栈保存着本地方法信息。对于每个线程,都将创建一个单独的本机方法栈。【调用本地 native关键字修饰的方法的时候,就是每一个本地方法调用一次时插入一个栈帧,就和上面java栈插入一样】
3. 执行引擎
被分配给运行时数据区的字节码将由执行引擎执行。执行引擎读取字节码并逐个执行。
解释器 – 解释器更快地解释字节码,但执行速度很慢。解释器的缺点是,当一个方法被多次调用时,每次都需要一个新的解释。
JIT编译器
– JIT编译器消除了解释器的缺点。执行引擎将在转换字节码时使用解释器的帮助,但是当它发现重复的代码时,它使用JIT编译器,JIT编译整个字节码并将其更改为本机代码。此本机代码将直接用于重复的方法调用,从而提高系统的性能。
中间代码生成器 – 生成中间代码
代码优化器 – 负责优化上面生成的中间代码
目标代码生成器 – 负责生成机器代码或本地代码
分析器 – 一个特殊的组件,负责寻找热点,即方法是否被多次调用。
垃圾收集器:收集和删除未引用的对象。可以通过调用 System.gc()触发垃圾收集,但不能保证执行。JVM的垃圾收集收集创建的对象。
Java本地接口(JNI): JNI将与本地方法库交互,并提供执行引擎所需的本地库。
本机方法库: 这是执行引擎所需的本机库的集合。
注:
栈的理解
记住 : 栈管运行,堆管存储
栈也叫栈内存,主管Java程序的运行,是在线程创建时创建,他的生命周期是跟随线程的生命周期,线程结束那么栈内存也就随之释放, 对于栈来说不存在垃圾回收问题 ,只要线程已结束该栈就over了,是线程私有的。8种基本类型的变量 + 对象的引用变量 + 实例方法都是在函数的占内存中分配。
栈存储什么?【每执行到一个方法,就插入一个栈帧,执行完毕后进行出栈,先进后出原则】
栈中存栈帧---栈帧里面存着3类数据如下:
栈帧中主要保存 3 类数据 : (何为栈帧:即Java中的方法,只是在jvm中叫做栈帧)
- ① 局部变量表 (Local Variables) : 入参和出参 以及方法内的变量;
- ② 栈操作 (Operand Stack) : 记录出栈 和 入栈的操作;(可理解为pc寄存器的指针)
- ③ 栈帧数据 (Frame Data) : 包括类文件、方法等。
-4 动态链接(Dynamic Linking)
-5 返回数据 (Invocation completion)
栈的运行原理:
栈中的数据都是以栈帧 (Stack Frame) 的格式存在,栈帧是一个内存区块,是一个有关方法和运行期数据的数据集,
当一个方法A被调用时就产生了栈帧 F1,并被压入到栈中,
A方法又调用 B方法,于是产生栈帧F2 ,也被压入栈,
B方法又调用 C方法, 于是产生栈帧F3,也被压入栈
……
执行完毕后,先弹出F3栈帧,再弹出 F2栈帧,再弹出 F1栈帧 …… ,只有方法执行完才会出栈,要不然各个方法对应的栈帧会一直存在栈中,占用栈深度
遵循 “先进后出” / “后进先出” 原则。
每个方法执行的同时都会创建一个栈帧,用于存储局部变量表,操作数据栈,动态连接、方法出口等信息,每一个方法从调用直至执行完毕的过程,就对应着一个栈帧在虚拟机中入栈到出栈的操作过程
如果栈的深度不够用了,就会栈溢出错误 StackOverflowError(因为有可能是递归,方法调用链太长了),如果想对栈深度进行改造,可以设置jvm参数 -Xss256k进行设置【太大浪费资源,太少会出现栈溢出】
栈的大小和具体jvm的实现有关,通常在 256K ~ 756K 之间,约等于 1Mb左右