本篇内容介绍了“Java的JVM类加载机制是什么”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情...
本篇内容介绍了“Java的JVM类加载机制是什么”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!
Java虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这个过程被称作虚拟机的类加载机制。当Java程序运行时,Java虚拟机会按需加载类,即在程序需要使用某个类时才会加载该类。
类的生命周期如下图:
JVM的类加载机制包括加载、连接( 验证、准备、解析)、初始化 3个阶段。
加载(Loading)
加载(Loading) 阶段主要是查找并加载字节码文件,这个文件可以是来自本地文件系统、网络、jar包等地方。加载后,生成一个对应的Class对象。
加载类时会做以下工作:
根据类的全限定名查找并读取类的二进制数据。类的二进制数据可以来自文件、网络、数据库等各种数据源。
将类的二进制数据转换成方法区内部的数据结构。在转换的过程中,JVM会对类的二进制数据进行解析和校验。
在方法区内存储该类的相关信息,包括类的名称、修饰符、常量池、字段描述符、方法描述符、接口描述符、方法表等。
生成一个代表该类的Class对象,并将该对象存放在JVM的堆内存中。Class对象包含了类的各种信息,可以用于创建类的实例、获取类的方法和字段等操作。
需要注意的是,在加载类的过程中,JVM会遵循一定的双亲委派机制,即先委派给父类加载器尝试加载,如果父类加载器无法加载,则由当前类加载器进行加载。这样可以保证类的加载不会重复,避免出现类似的类被多次加载的情况。有关类加载器可以查看我之前的文章。
加载阶段与连接阶段的部分动作(如一部分字节码文件格式验证动作)是交叉进行的,加载阶段尚未完成,连接阶段可能已经开始,但这些夹在加载阶段之中进行的动作,仍然属于连接阶段的一部分,这两个阶段的开始时间仍然保持着固定的先后顺序。
连接(Linking)
连接阶段是Java虚拟机将类文件中的符号引用转换为直接引用的过程,会对字节码文件进行验证、准备、解析。
验证(Verification):在这个阶段,JVM会对字节码进行验证,以确保其符合Java虚拟机规范,并且不会对虚拟机造成安全威胁。验证的内容包括静态分析、字节码验证、符号引用验证等。如果验证失败,JVM会抛出VerifyError异常。
准备(Preparation):在这个阶段,JVM会为类的静态变量分配内存,并将其初始化为默认值(零值)。这个阶段不会执行任何Java代码,只是为静态变量分配内存空间。例如将int类型的静态变量赋值为0。
public static int staticValue = 123;
准备阶段初始化只是将静态变量初始化为默认值,比如上面这段代码,不同数据类型都有其默认值,初始化是只是将staticValue
赋予默认值0
,也就是staticValue = 0
,只有在初始化阶段才会将staticValue
赋值为123,也就是staticValue = 123
。但是如果是staticValue是个常量public static final int staticValue = 123
,准备阶段才会将staticValue
赋值为123。
初始化(Initialization)
初始化阶段是指在类被首次主动使用时执行的阶段,虚拟机会执行类的初始化代码,包括静态变量的赋值和静态代码块的执行等操作。
初始化阶段是类加载的最后一个阶段,在该阶段,JVM会执行以下操作:
执行静态变量赋值操作:在这个阶段,JVM会对所有的静态变量进行初始化并赋值。这些静态变量的值通常在类定义中已经被明确定义了,JVM会根据定义进行相应的赋值操作。
执行静态代码块:如果类定义中包含有静态代码块,那么在该阶段JVM会执行这些静态代码块中的代码。
调用类的初始化方法:在Java程序中,可以使用static关键字来定义一个类的静态初始化方法(即static void methodName())。在该阶段,JVM会调用这个类的静态初始化方法。
类初始化的时机
类初始化时机包括以下四种情况:
创建该类的实例对象时,例如使用 new 关键字创建对象,类的初始化将被触发 如果一个类是程序执行的入口类(即包含 main() 方法的类),那么也会触发该类的初始化操作。
子类初始化会触发父类初始化:当一个子类被初始化时,其父类也会被初始化。这意味着,如果一个类没有被使用,那么它的父类也不会被初始化。
当调用类的静态方法(不包括final方法和private方法)或访问类的静态字段(不包括final字段)时,类的初始化将被触发。
当使用反射API对类进行某些操作时(例如使用Class.forName()方法加载类、调用Class.newInstance()方法创建对象、调用Method.invoke()方法调用方法等),类的初始化将被触发。
初始化是线程安全的JVM保证一个类的初始化只会由一个线程去执行,其他线程需要等待该线程完成后才能访问该类。
下面用一个简单的Java代码示例,展示JVM类加载机制中初始化阶段的示例
public class MyClass {
// 静态变量
public static String staticStr = "Hello, world!";
static {
System.out.println("MyClass is initialized.");
}
// 构造方法
public MyClass() {
System.out.println("MyClass constructor is called.");
}
// 静态方法
public static void staticMethod() {
System.out.println("MyClass staticMethod is called.");
}
}
在上述代码中,类MyClass
包含一个静态变量staticStr
、一个静态代码块和一个构造方法,以及一个静态方法staticMethod
。当程序首次使用MyClass
类时,JVM将会触发MyClass类的初始化阶段。可以通过下面的代码来测试类的初始化:
public class Test {
public static void main(String[] args) {
System.out.println(MyClass.staticStr); // 调用静态变量,触发类初始化
MyClass.staticMethod(); // 调用静态方法,触发类初始化
MyClass obj = new MyClass(); // 创建对象,触发类初始化
}
}
在上面的代码中,首先输出了MyClass类的静态变量staticStr
,此时会触发MyClass类的初始化;然后调用了静态方法staticMethod
,同样会触发MyClass类的初始化;最后创建了一个MyClass对象,也会触发MyClass类的初始化。运行上述代码,可以看到以下输出:
MyClass is initialized.
Hello, world!
MyClass staticMethod is called.
MyClass constructor is called.
输出结果表明,MyClass类的初始化确实在首次使用该类时被触发,包括静态变量、静态代码块、静态方法和构造方法都被执行了。
此外,如果一个类是另一个类的子类,那么在使用子类时,父类也会被初始化。例如:
public class MyBaseClass {
static {
System.out.println("MyBaseClass is initialized.");
}
}
public class MySubClass extends MyBaseClass {
static {
System.out.println("MySubClass is initialized.");
}
}
public class Test {
public static void main(String[] args) {
MySubClass obj = new MySubClass(); // 创建子类对象,触发父类和子类初始化
}
}
在上述代码中,当创建MySubClass类的对象时,将会触发MyBaseClass和MySubClass类的初始化。运行上述代码,可以看到以下输出:
MyBaseClass is initialized.
MySubClass is initialized.
“Java的JVM类加载机制是什么”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识可以关注捷杰建站网站,小编将为大家输出更多高质量的实用文章!