最近想要复习一下Java的体系知识,所以记录了JVM的学习。
文章目录
1.编译
JVM中运行的是.class文件,那么如何将.java文件转换为.class文件呢,这一过程就叫做编译。那又是怎么编译的呢?
源码文件 (HelloWorld.java)
↓ 执行javac命令
┌─────────────────────────────────────────┐
│ 词法分析 (Lexical Analysis) │
│ 源代码字符流 → 标记(Token)流 │
└─────────────────────────────────────────┘
我的理解就是识别"单词",识别常量名、变量名、关键字等标识符,
因为Java的标识符是有规则的,首字符必须为字母、下划线。
那这个检查就是在这个阶段的
↓
┌─────────────────────────────────────────┐
│ 语法分析 (Syntax Analysis) │
│ 标记流 → 抽象语法树(AST) │
└─────────────────────────────────────────┘
这个阶段就是识别语法,像英语一样,Java也有自己的语法,你不能
违背语法。
↓
┌─────────────────────────────────────────┐
│ 语义分析 (Semantic Analysis) │
│ 类型检查、符号解析、语法树标注 │
└─────────────────────────────────────────┘
语义分析是检查代码的逻辑含义是否正确的过程。语法正确的代码不一定有意义,语义分析就是找出那些"通顺但不合理"的代码。
↓
┌─────────────────────────────────────────┐
│ 注解处理 (Annotation Processing) │
│ 处理编译时注解,生成新代码 │
└─────────────────────────────────────────┘
↓
┌─────────────────────────────────────────┐
│ 语法树转换 (AST Transformations) │
│ 去除语法糖、内联常量、优化等 │
└─────────────────────────────────────────┘
↓
┌─────────────────────────────────────────┐
│ 生成字节码 (Bytecode Generation) │
│ AST → 字节码指令 │
└─────────────────────────────────────────┘
↓
┌─────────────────────────────────────────┐
│ 字节码优化 (Bytecode Optimization) │
│ 简化、压缩、优化字节码 │
└─────────────────────────────────────────┘
↓
.class 文件
2.运行
现在已经生成了.class文件,使用java命令可以对这个文件进行运行,那运行的机理又是怎样的呢?
2.1完整流程
执行 java HelloWorld
↓
操作系统启动JVM进程
↓
JVM初始化(创建JVM实例)
↓
├─ 1. 类加载
│ ├─ 加载(Loading)
│ ├─ 链接(Linking)→ 验证、准备、解析
│ └─ 初始化(Initialization)
│
├─ 2. 运行时数据区
│ ├─ 方法区(Method Area)
│ ├─ 堆(Heap)
│ ├─ 虚拟机栈(Stack)
│ ├─ 程序计数器(PC Register)
│ └─ 本地方法栈(Native Stack)
│
├─ 3. 执行引擎
│ ├─ 解释器
│ ├─ JIT编译器
│ └─ 垃圾回收器
│
└─ 4. 本地库接口
2.2操作系统启动JVM进程
首先需要在操作系统上开辟一个进程,具体的执行如下:
1. 解析命令:java是JRE的可执行程序
2. 创建进程:
- 分配内存空间
- 创建进程控制块(进程控制块(Process Control Block,简称PCB)就像是一个进程的"身份证"+"档案"+"病历"。PID就在这里面)
- 设置初始堆栈(例如下面的命令)
java -Xms512m # 初始堆大小
-Xmx1024m # 最大堆大小
-Xss256k # 线程栈大小
-cp . # 类路径
HelloWorld # 主类
arg1 arg2 # 命令行参数
3. 加载JVM:
- 加载jvm.dll(Windows)或 libjvm.so(Linux)
- 初始化JVM
2.3JVM初始化
JVM的初始化,首先会调用JNI方法创建一个JVM实例,然后才会实现JVM的初始化。
2.3.1类加载
JVM初始化后,就如何加载要class文件呢,这个过程被成为类加载。具体的流程如下:
.class文件(硬盘上)
↓
[1. 寻找文件] → 在哪些地方找.class文件?
↓
[2. 读取字节] → 把.class文件读取到内存
↓
[3. 解析格式] → 解析成ClassFile结构
↓
[4. 创建Class对象] → 在堆中创建Class对象
↓
[5. 放入方法区] → 存储类的元信息
↓
准备好被使用!
- 寻找文件
这里面体现如何寻找文件,会从以下四个加载器中加载,至于加载的机制被称为双亲委派机制。
1. 启动类加载器 (Bootstrap ClassLoader)
搜索路径: $JAVA_HOME/jre/lib/*.jar
查找顺序: rt.jar → jsse.jar → jce.jar → ...
2. 扩展类加载器 (Extension ClassLoader)
搜索路径: $JAVA_HOME/jre/lib/ext/*.jar
加载: javax.xml, javax.swing, javax.crypto等扩展类
3. 应用程序类加载器 (Application ClassLoader)
搜索路径: CLASSPATH环境变量 或 -cp参数指定的路径
加载: 你自己的类,如com.example.MyClass
4. 自定义类加载器
搜索路径: 自定义的任何地方
双亲委派机制如下:
你要加载 com.example.MyClass
↓
应用类加载器收到请求
↓
"我先不加载,让我爸爸(扩展类加载器)试试"
↓
扩展类加载器收到请求
↓
"我也让我爸爸(启动类加载器)试试"
↓
启动类加载器收到请求
↓
1. 在rt.jar中找com/example/MyClass.class
2. 找不到!返回null
↓
扩展类加载器自己尝试
↓
1. 在ext/*.jar中找com/example/MyClass.class
2. 找不到!返回null
↓
应用类加载器自己尝试
↓
1. 在CLASSPATH中找com/example/MyClass.class
2. 找到!加载成功