一, 什么是 JVM
JVM(Java Virtual Machine)是一个可以执行 Java 字节码文件 (即 .class 文件) 的虚拟机进程. 当 Java 源文件能被成功编译成 .class 文件, 就能在不同平台上的不同版本的 JVM 运行, 因为 JVM 能将相同的 .class 文件解释称不同平台的机器码. 正是因为 JVM 的存在, Java 被称为与平台无关的语言.
一般而言,.java 文件经过编译后会得到 .class 文件, 而将这个文件加载到内存之前需要先通过类加载器, 先简单过一下图:
二, 类加载过程
类加载的过程为: 加载 -->连接 (验证 --> 准备 -->解析)-->初始化. 下面介绍其中的几个过程.
1, 加载
这个过程主要是通过类的全限定名, 例如 java.lang.String 这样带上包路径的类名, 获取到字节码文件; 然后将这个字节码文件代表的静态存储结构 (可简单理解为对象创建的模板) 存在方法区, 并在堆中生成一个代表此类的 Class 类型的对象, 作为访问方法区中 "模板" 的入口, 往后创建对象的时候就按照这个模板创建.
举个例子, 有时候通过反射创建对象, 像当初学 JDBC 时会通过 Class.getName("com.mysql.jdbc.Driver.class").newInstance() 创建对象, 通过 Class 和相应的全限定类名获取到方法区中的 "模板" 然后创建对象.
2, 验证
验证过程主要确保被加载的类的正确性. 首先要先验证文件格式是否规范, 如果只是通过 .class 后缀来辨别, 那随便把后缀名改一下就可以跑程序了, 那岂不是很容易出事. 来看看字节码文件大概是长什么样的:
注意看前缀 cafe babe(咖啡宝贝?)这只是验证的其中一个点, 还会验证字节码文件里是否包含主次版本号等验证信息.
3, 准备
这个阶段主要是给类变量 (静态变量) 分配方法区的内存并初始化. 实例变量不是在这个阶段分配内存, 实例变量是随着对象一起分配在堆中. 另外, 给静态变量初始化为零值或空值, 比如 public static int n=5; 这里并不是马上给 n 这个变量赋值为 5, 而是先将其赋值为 0, 类似的, 如果是引用数据类型, 则默认为 null. 还有一点需要注意的是, 对于 final 类型的数据, 必须在程序内给它赋值, 系统不会自动初始化, 例如 static String str = "hello" + "world";String 是 final 类型的, 在编译阶段就给它优化成 static String str = "helloworld" , 并且将 "helloworld" 放进了常量池.
4, 初始化
这个阶段就是将静态变量赋值为初始值, 还是 public static int n=5; 这回给 n 赋值为 5 了.
三, 类加载器
启动类加载器是由 C/C++ 写的, 主要负责加载 jre\lib 目录下的类; 扩展类加载器主要负责加载 jre\lib\ext 目录下的类; 而应用程序类加载器主要负责加载我们自己编写的类; 当然还能自己写类加载器, 即自定义加载器. 程序主要由前面三个类加载器相互配合加载的.
- public class Main {
- public static void main(String[] args) {
- Main main = new Main();
- System.out.println(main.getClass().getClassLoader());
- System.out.println(main.getClass().getClassLoader().getParent());
- System.out.println(main.getClass().getClassLoader().getParent().getParent());
- }
- }
由于启动类加载器是 C/C++ 语言写的, 所以输出为 null
双亲委派机制
在类加载的过程中, 存在着双亲委派机制, 即当要加载一个类时, 先由父类加载器加载, 当父类加载器没办法加载时, 才由下面的加载器加载, 来看一个程序:
- package java.lang; // 自定义的包
- public class String {
- public static void main(String[] args) {
- System.out.println("这是自定义的 java.lang.String 类");
- }
- }
由于 jre\lib\ext 中存在 java.lang.String 类, 当加载该类的时候, 根据全限定名进行查找, 找到后由启动类加载器加载, 发现 String 类中不包含 main() 方法, 因此程序出错.
来源: https://www.cnblogs.com/lyuzt/p/12057385.html