JVM 内存结构模型
JVM 虚拟机主要由 4 个部分组成:
-
Class Loader: 依据特定的格式,将 class 文件加载到内存
-
Execution Engine: 解析字节码文件并提交给操作系统执行
-
Native Interface: Java 本地接口,融合不同语言开发的原生库为 Java 所用
-
Runtime Data Area: JVM 内存结构模型
JVM 内存区域
线程私有
程序计数器 PC、虚拟机栈、本地方法栈
线程共享
方法区、堆、直接内存(非运行时数据区)
线程私有数据区域生命周期与线程相同, 依赖用户线程的启动/结束 而 创建/销毁(在 Hotspot VM 内),每个线程都与操作系统的本地线程直接映射, 因此这部分内存区域的存/否跟随本地线程的生/死对应)。
线程共享区域随虚拟机的启动/关闭而创建/销毁
直接内存并不是 JVM 运行时数据区的一部分,但也会被频繁的使用:在 JDK 1.4 引入的 NIO 提供了基于 Channel 与 Buffer 的 IO 方式,它可以使用 Native 函数库直接分配堆外内存,然后使用 DirectByteBuffer 对象作为这块内存的引用进行操作,这样就避免了在 Java 堆和 Native 堆中来回复制数据, 因此在一些场景中可以显著提高性能。
程序计数器 PC
程序计数器是一块较小的内存空间,可以看作是当前线程所执行的字节码的行号指示器。
另外,为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各线程之间计数器互不影响,独立存储,我们称这类内存区域为“线程私有”的内存。
正在执行 java 方法的话,计数器记录的是虚拟机字节码指令的地址(当前指令的地址)。如果还是 Native 方法,则为空。
这个内存区域是唯一一个在虚拟机中没有规定任何 OutOfMemoryError 情况的区域。
虚拟机栈
是描述 java 方法执行的内存模型,每个方法在执行的同时都会创建一个栈帧(Stack Frame)用于存储局部变量和部分过程的结果,栈帧由以下几部分组成:
-
局部变量表: 存储方法调用时传递的参数,从 0 号槽位开始存储 this、方法参数、局部变量
-
操作数栈: 执行中间操作,存储从局部变量表或对象实例字段复制的常量或变量值,以及操作结果,另外,还用来准备被调用方法的参数和接受方法调用的返回结果
-
动态链接: 一个指向运行时常量池的引用,将 class 文件中的符号引用(描述一个方法调用了其他方法或访问成员变量)转为直接引用
-
方法返回地址: 方法正常退出或抛出异常退出,返回方法被调用的位置
每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。栈帧随着方法调用而创建,随着方法结束而销毁 —— 无论方法是正常完成还是异常完成(抛出了在方法内未被捕获的异常)都算作方法结束。
本地方法栈
本地方法区和 Java Stack 作用类似, 区别是虚拟机栈为执行 Java 方法服务, 而本地方法栈则为 Native 方法服务, 如果一个 VM 实现使用 C-linkage 模型来支持 Native 调用, 那么该栈将会是一个 C 栈,但 HotSpot VM 直接就把本地方法栈和虚拟机栈合二为一。
堆
创建的对象和数组都保存在 Java 堆内存中,也是垃圾收集器进行垃圾收集的最重要的内存区域。由于现代 VM 采用分代收集算法,因此 Java 堆从 GC 的角度还可以细分为:新生代(Eden 区、From Survivor 区和 To Survivor 区)和老年代。
方法区
用于存储被 JVM 加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
在 HotSpot 中方法区也被称为永久代(Permanent Generation),HotSpot VM 把 GC 分代收集扩展至方法区,即 使用 Java 堆的永久代来实现方法区,这样 HotSpot 的垃圾收集器就可以像管理 Java 堆一样管理这部分内存,而不必为方法区开发专门的内存管理器(永久代的内存回收的主要目标是针对常量池的回收和类型的卸载,因此收益一般很小)。
JDK 1.8 的时候,方法区(HotSpot 的永久代)被彻底移除了,取而代之是元空间,元空间使用的是直接内存。
运行时常量池
运行时常量池(Runtime Constant Pool) 是方法区的一部分。Class 文件中除了有类的版本、字段、方法、接口等描述等信息外,还有一项信息是常量池(Constant Pool Table),用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中。
JAVA8 与元空间
在 Java8 中,永久代已经被移除,被元空间所取代。元空间的本质和永久代类似,元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用直接内存。因此,默认情况下,元空间的大小仅受本地内存限制。类的元数据放入 native memory,字符串池和类的静态变量放入 java 堆中,这样可以加载多少类的元数据就不再由 MaxPermSize 控制,而由系统的实际可用空间来控制。
执行引擎
运行时数据区存储着要执行的字节码,执行引擎将会读取并逐个执行。
Interpreter - 解释器,它对字节码的解释很快,但执行慢,有个缺点是,当方法被多次调用时,每次都需要重新解释。
JIT Compiler- JIT 编译器, 解决了解释器的缺点,仍使用解释器来转换字节代码,但发现有代码重复执行时,会使用 JIT 编译器,将整个字节码编译成本地代码,将本地代码用于重复调用,从而提高系统的性能,有以下几部分组成:
-
中间代码生成器 - 生成中间代码
-
代码优化器 - 负责优化上面生成的中间代码
-
目标代码生成器 - 负责生成机器代码或本地代码
-
Profiler - 一个特殊组件,负责查找热点,判断该方法是否被多次调用
Garbage Collector —— 垃圾收集器,收集和删除未引用的对象。
另外,还包括执行引擎所需的本地库(Native Method Libraries)和与其交互的 JNI 接口(Java Native Interface)。
参考 https://www.cnblogs.com/wskwbog/p/11349042.html
来源:CSDN
作者:X_信仰
链接:https://blog.csdn.net/weixin_44584387/article/details/104636805