【Java】JVM虚拟机(基本概念、类加载机制)
一、基本概念
1、什么是JVM
Java虚拟机(Java Virtual Machine,简称 JVM ),是java程序运行的核心组件之一,它为java程序运行提供了环境。其核心价值在于实现了" 一次编写,多处运行 " (Write once,run anywhere )的跨平台特性,还提供了内存管理、垃圾回收、安全性以及性能优化等。
2、JVM的组成
JVM 的架构可分为类加载子系统、运行时数据区、执行引擎、本地方法接口四大核心模块,各模块协同工作完成字节码的加载、执行和资源管理。
二、类加载机制
1、概述
Java 的类加载机制是 JVM 将 .class
字节码文件加载到内存中,并对数据进行校验、转换解析、初始化,最终形成可直接使用的 Java 类型的过程。
2、类的生命周期
3、类的加载过程
类加载过程主要包括:加载、验证、准备、解析、初始化,五个阶段。
a、加载
该阶段主要是通过类的完全限定名获取类的二进制字节流,并生成一个代表该类的Class对象,并将其保存在元空间中。
- 字节码来源:
- 本地文件系统(
.class
文件); - 网络(如 Applet 从网络加载);
- 动态生成(如
JavaCompiler
动态编译、CGLib 动态代理生成字节码); - 归档文件(如 JAR、WAR 包);
- 数据库或其他数据源。
- 本地文件系统(
b、验证
该阶段主要验证class文件中的字节流包含的信息是否符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。
c、准备
该阶段是在元空间中为静态变量分配内存,并设置其默认初始值(非显示赋值)。
d、解析
该阶段只要是将常量池中的符号引用替换为直接引用。
e、初始化
该阶段为类加载的最后一步,由<clinit>()编译器自动收集类中所有类变量的赋值和静态代码块中的语句,进行排序并执行。
4、类加载的时机
在 Java 中,类的加载时机由 JVM 规范严格定义,主要分为主动引用和被动引用两种情况。主动引用会触发类的初始化(初始化前的加载、验证、准备阶段会自动触发),而被动引用不会触发初始化。
a、主动引用
- new() 创建类的实例
当使用 new
关键字创建类的对象时,类会被初始化。
public class demo01 {public static void main(String[] args) {// 触发 MyClass 初始化MyClass obj = new MyClass();}
}
class MyClass {static {System.out.println("MyClass 初始化");}
}//结果:
MyClass 初始化
- 调用类的静态方法
当调用类的静态方法(static
方法)时,类会被初始化。
public class demo02 {public static void main(String[] args) {// 触发 Calculator 初始化int result = Calculator.add(1, 2);}
}
class Calculator {static {System.out.println("Calculator 初始化");}public static int add(int a, int b) {return a + b;}
}//结果:
Calculator 初始化
- 访问静态变量
当访问类的静态变量(static
变量)时,类会被初始化。
public class demo03 {public static void main(String[] args) {// 触发 Config 初始化int port = Config.PORT;System.out.println("======================");// 不触发 Config 初始化(直接从调用类的常量池获取值)int max = Config.MAX;System.out.println("main结束!!!");}
}class Config {static {System.out.println("Config 初始化");}public static int PORT = 8080; // 非 final 静态变量public static final int MAX= 100; // 编译期常量
}// 结果:
Config 初始化
======================
main结束!!!
- 反射调用
当使用反射newInstance()加载类时,类会被初始化。
public class demo04 {public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {// 触发 MyClass 初始化Class cls = MyClass01.class;cls.newInstance();}
}
class MyClass01 {static {System.out.println("MyClass 初始化");}
}//结果:
MyClass 初始化
- 初始化子类
当初始化子类时,若父类尚未初始化,则会先触发父类的初始化。
public class demo05 {public static void main(String[] args) {// 触发 Parent 和 Child 依次初始化Child child = new Child();}
}
class Par {static {System.out.println("Parent 初始化");}
}
class Child extends Par {static {System.out.println("Child 初始化");}
}// 结果:
Parent 初始化
Child 初始化
b、被动引用
除主动引用外,所有引用类的方式都不会触发加载,称为被动引用。
- 创建数组
创建类的数组时,不会触发类的初始化
public class demo06 {public static void main(String[] args) {data01[] data = new data01[10];System.out.println("main函数结束!");}
}
class data01{static {System.out.println("data01被初始化");}
}//结果:
main函数结束!
通过子类引用父类的静态变量
子类引用父类的静态变量时,仅触发父类的初始化,子类不会被初始化。
public class demo07 {public static void main(String[] args) {// 仅触发 Parent 初始化,Child 不会初始化System.out.println(Child.value); // 输出 10}
}
class Parent {static {System.out.println("Parent 初始化");}public static int value = 10;
}
class Child extends Parent {static {System.out.println("Child 初始化");}
}//结果:
Parent 初始化
10
5、类加载器
类加载器是 Java 类加载机制的核心组件,负责将字节码文件加载到 JVM 内存中,并生成对应的 Class
对象。
1、类加载器类型
1. 启动类加载器
- 加载范围:负责加载 JRE 核心类库(如
rt.jar
、resources.jar
),路径通常为$JAVA_HOME/jre/lib
。
2. 扩展类加载
- 加载范围:负责加载 JRE 扩展目录中的类(如
jre/lib/ext
目录或java.ext.dirs
系统属性指定的路径)
3. 应用程序类加载器
- 加载范围:负责加载用户类路径上的类,即开发者编写的类和依赖的第三方库。
4. 自定义类加载器
- 实现:用于实现特殊加载逻辑(如从网络、数据库加载类)。
2、双亲委派模型
当类加载器收到一个类加载请求时,会首先委派给我自己的父类加载器,而不是自己加载,当向上委派到启动类加载器时,若其无法加载,则该类由子类自行尝试加载。