一. JVM 执行流程
- 程序在执行之前先要把java代码转换成字节码(class文件).
- JVM 首先需要把字节码通过类加载器(ClassLoader) 把文件加载到内存中 运行时数据区(Runtime Data Area) .
- 而字节码文件是 JVM 的一套指令集规范,并不能直接交个底层操作系统去执行,因此需要特定的命令解析器 执行引擎(Execution Engine)将字节码翻译成底层系统指令再交由CPU去执行.
- 而这个过程中需要调用其他语言的接口 本地库接口(Native Interface) 来实现整个程序的功能
这就是这4个主要组成部分的职责与功能。
总结来看, JVM 主要通过分为以下 4 个部分,来执行 Java 程序的,它们分别是:
- 类加载器(ClassLoader)
- 运行时数据区(Runtime Data Area)
- 执行引擎(Execution Engine)
- 本地库接口(Native Interface)
二. JVM 运行时数据区
JVM 运行时数据区域也叫内存布局,但需要注意的是它和 Java 内存模型(( Java Memory Model,简称 JMM)完全不同,属于完全不同的两个概念,它由以下 5 大部分组成:
1. 堆(线程共享)
堆的作用:程序中创建的所有对象都在保存在堆中。
堆里面分为两个区域:新生代和老生代。
新生代放新建的对象,当经过一定 GC 次数之后还存活的对象会放入老生代。
新生代还有 3 个区域:一个 Eden (伊甸区) + 两个 Survivor(S0/S1)。
垃圾回收的时候会将 Eden 中和正在使用的 Survivor 区中存活的对象放到一个未使用的 Survivor 中,并把当前的 Eden 和正在使用的 Survivor 清除掉。
2. Java虚拟机栈(线程私有)
Java 虚拟机栈的作用:描述 Java 方法执行的内存模型, 每个方法在执行的同时都会创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。
我们常说的堆内存、栈内存中,栈内存指的就是虚拟机栈。
注意:Java虚拟机栈 是每个线程都有一份, 每个线程都有自己的方法调用关系
Java 虚拟机栈中包含了以下 4 部分:
- 局部变量表: 存放方法参数和局部变量。
- 操作栈:每个方法会生成一个先进后出的操作栈。
- 动态链接:指向运行时常量池的方法引用。
- 方法返回地址:PC 寄存器的地址。
什么是线程私有?
- JVM的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现
- 因此在任何一个确定的时刻,一个处理器核心都只会执行一条线程中的指令
- 为了切换线程后能恢复到之前正确的执行位置,每条线程都需要独立的程序计数器,各条线程之间计数器互不影响,独立存储
我们就把类似这类区域称之为"线程私有"的内存。
3. 本地方法栈(线程私有)
本地方法栈和虚拟机栈类似,只不过 Java 虚拟机栈是给 JVM 使用的,而本地方法栈是给本地方法使用的。
(内部是 C++ 代码, 存储的是 native 方法之间的调用关系)
4. 程序计数器(线程私有)
程序计数器的作用:用来记录当前线程执行的行号的。
程序计数器是一块比较小的内存空间,可以看做是当前线程所执行的字节码的行号指示器。
- 如果当前线程正在执行的是一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;
- 如果正在执行的是一个Native方法,这个计数器值为空。
程序计数器内存区域是唯一一个在JVM规范中没有规定任何OOM (Out Of Memory) 情况的区域!
5. 方法区(线程共享)
方法区的作用:用来存储被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据的。
在《Java虚拟机规范中》把此区域称之为“方法区”,JDK 8 中叫做元空间(Metaspace)。
运行时常量池:
运行时常量池是方法区的一部分,存放字面量与符号引用。
字面量 : 字符串常量 (JDK 8 移动到堆中) 、final常量、基本数据类型的值。
符号引用 : 类和结构的完全限定名、字段的名称和描述符、方法的名称和描述符。