Java虚拟机(Java Virtual Machine,简称 JVM ),是java程序运行的核心组件之一,它为java程序运行提供了环境。其核心价值在于实现了" 一次编写,多处运行 " (Write once,run anywhere )的跨平台特性,还提供了内存管理、垃圾回收、安全性以及性能优化等。
JVM 的架构可分为类加载子系统、运行时数据区、执行引擎、本地方法接口四大核心模块,各模块协同工作完成字节码的加载、执行和资源管理。
Java 的类加载机制是 JVM 将 .class
字节码文件加载到内存中,并对数据进行校验、转换解析、初始化,最终形成可直接使用的 Java 类型的过程。
类加载过程主要包括:加载、验证、准备、解析、初始化,五个阶段。
该阶段主要是通过类的完全限定名获取类的二进制字节流,并生成一个代表该类的Class对象,并将其保存在元空间中。
.class
文件);JavaCompiler
动态编译、CGLib 动态代理生成字节码);该阶段主要验证class文件中的字节流包含的信息是否符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。
该阶段是在元空间中为静态变量分配内存,并设置其默认初始值(非显示赋值)。
该阶段只要是将常量池中的符号引用替换为直接引用。
该阶段为类加载的最后一步,由
在 Java 中,类的加载时机由 JVM 规范严格定义,主要分为主动引用和被动引用两种情况。主动引用会触发类的初始化(初始化前的加载、验证、准备阶段会自动触发),而被动引用不会触发初始化。
当使用 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 初始化
除主动引用外,所有引用类的方式都不会触发加载,称为被动引用。
创建类的数组时,不会触发类的初始化
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
类加载器是 Java 类加载机制的核心组件,负责将字节码文件加载到 JVM 内存中,并生成对应的 Class
对象。
1. 启动类加载器
rt.jar
、resources.jar
),路径通常为 $JAVA_HOME/jre/lib
。2. 扩展类加载
jre/lib/ext
目录或 java.ext.dirs
系统属性指定的路径)3. 应用程序类加载器
4. 自定义类加载器
当类加载器收到一个类加载请求时,会首先委派给我自己的父类加载器,而不是自己加载,当向上委派到启动类加载器时,若其无法加载,则该类由子类自行尝试加载。