当前位置:   article > 正文

JVM(Java虚拟机)的类加载(ClassLoader)机制_jvm类加载机制

jvm类加载机制

JVM(Java虚拟机)的类加载机制是指在运行Java程序时,将类的字节码加载到内存中,并将其转换为可以被JVM执行的对象的过程。

1、类加载时机

    类加载时机,一般分为以下几种情况:
    1. 当创建类的实例对象时,需要先加载该类。

例如: `MyClass obj = new MyClass();` 

    2. 当访问类的静态成员(静态变量或静态方法)时,需要先加载该类。

例如: `int value = MyClass.staticVariable;` 

    3. 当使用反射方式访问类时,需要先加载该类。

例如: `Class<?> cls = Class.forName("MyClass");` 

    4. 当程序中使用到某个类的子类时,需要先加载父类。

 例如: `MyChildClass obj = new MyChildClass();`


2、类加载的过程

    Java类加载的时间是在程序运行时动态加载类的过程中进行的。
    类加载的过程主要分为加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、 初始化(Initialization)几个步骤:

    2.1、加载(Loading)

        查找并加载类的字节码文件。该阶段将类的字节码加载到内存中,并创建对应的Class对象。根据类的全限定名查找字节码文件,并将其加载到内存中。
        
        主要步骤:

            1.通过类的全限定名查找字节码文件:在Loading阶段,JVM会根据类的全限定名(包括包名和类名)来查找对应的字节码文件。通常情况下,字节码文件会存储在类路径下的相应位置。
            2.将字节码文件加载到内存中:一旦找到了字节码文件,JVM会将其读取并加载到内存中。这个过程会将字节码数据转换为JVM可以理解和执行的数据结构。
            3.创建对应的Class对象:在加载字节码文件的过程中,JVM会为该类创建一个对应的Class对象。这个Class对象包含了该类的各种信息,可以用来获取类的结构、访问类的成员变量和方法等。

            注意,Loading阶段只是将类的字节码文件加载到内存中,并创建对应的Class对象,但并不会执行类的初始化代码。类的初始化是在后续的Initialization阶段进行的。 

示例:`Class.forName("com.example.MyClass");` 

    2.2、验证(Verification)

        检查字节码文件的结构、语法和语义是否合法。验证字节码文件的正确性和安全性。该阶段确保字节码文件符合Java虚拟机规范,并且不会引发安全问题。

           主要步骤:
            1.类文件格式验证:验证字节码文件是否符合Java虚拟机规范的格式要求。
               示例:检查字节码文件的结构、标记等是否符合规范。
            2.元数据验证:验证字节码文件中的元数据信息是否正确。
               示例:检查类的继承关系、实现的接口、字段和方法的描述符等是否正确。
            3.字节码验证:验证字节码文件的字节码指令是否合法、符合语义规范。
               示例:检查字节码指令的类型、操作数的类型和数量等是否符合规范。
            4.访问权限验证:验证类、字段和方法的访问权限是否符合规定。
               示例:检查类、字段和方法的修饰符,如public、private、protected等是否符合访问权限规则。
            5.安全性验证:验证字节码文件是否存在安全漏洞或违规操作。
               示例:检查字节码文件中的敏感操作、访问权限等是否符合安全规范。

            注意,验证阶段的具体操作和示例可能因具体的Java虚拟机实现而有所不同。Java虚拟机在验证阶段会对字节码文件进行全面的检查,以确保加载的类是合法且安全的。


    2.3、准备(Preparation)

        为类的静态变量分配内存,并设置默认初始值。该阶段为静态变量分配内存空间,并设置默认值。

        主要步骤:
            1. 为静态变量分配内存空间:在准备阶段,JVM会为类的所有静态变量分配内存空间,以便后续使用。   

示例: `public static int count = 0;` 

            2. 设置默认初始值:在分配内存空间后,JVM会为静态变量设置默认的初始值。

示例:对于整型变量,初始值为0;对于布尔型变量,初始值为false;对于引用类型变量,初始值为null

            注意,准备阶段只是为静态变量分配内存并设置默认初始值,并不涉及静态变量的赋值操作。实际的静态变量赋值是在初始化阶段进行的。


    2.4、 解析(Resolution)

        将Java虚拟机常量池内的符号引用转换为直接引用。该阶段将符号引用转换为内存中的直接引用,以便程序可以直接访问目标,提高执行效率。通过解析,Java虚拟机可以将符号引用解析为实际的内存地址,从而避免了每次访问目标时都需要重新解析的开销。

        2.4.1、符号引用(Symbolic Reference)
            符号引用是在编译阶段或者类加载过程中使用的一种符号表示,它通过符号来描述所引用的目标,包括类、方法、字段等。符号引用并不直接指向内存中的实际位置,而是一个符号化的标识。

        2.4.2、直接引用(Direct Reference)
            直接引用是在解析阶段将符号引用转换为实际的内存地址,以便程序可以直接访问目标。直接引用是指向内存中实际位置的指针、句柄或偏移量,可以直接用于访问类、方法、字段等。
            例如,在解析阶段,将类的符号引用转换为直接引用后,可以直接访问类的静态变量、调用类的方法等,而不需要再通过符号引用进行查找和解析。

            总结来说,解析阶段的作用是将符号引用转换为直接引用,使得程序可以直接访问目标,提高执行效率和性能。


        2.4.3、主要步骤

            1. 类解析:将类的符号引用转换为对应的直接引用。
               示例:将类 `com.example.MyClass` 的引用转换为实际的内存地址。

            2. 方法解析:将方法的符号引用转换为对应的直接引用。
               示例:将方法 `myObject.doSomething()` 的引用转换为实际的内存地址。

            3. 字段解析:将字段的符号引用转换为对应的直接引用。
               示例:将字段 `myObject.count` 的引用转换为实际的内存地址。

            解析阶段的目的是为了在类加载完成后能够快速、直接地访问类的成员。通过将符号引用解析为直接引用,程序可以直接使用类、方法、字段等,而无需再进行符号引用的解析过程。
            注意,解析阶段的具体操作和示例可能因具体的Java虚拟机实现而有所不同。Java虚拟机在解析阶段会根据符号引用的信息,将其转换为实际的内存地址,以便程序可以直接访问。


    2.5、 初始化(Initialization)

          Java类加载的初始化(Initialization)阶段是在类加载过程中的最后一个阶段,执行类的初始化代码,包括静态变量赋值和静态代码块的执行。该阶段执行类的初始化代码,初始化静态变量,并执行静态代码块中的逻辑。

           主要步骤:
            1. 静态变量赋值:在初始化阶段,会为静态变量赋予指定的值,可以是默认值或者程序员指定的初始值。

示例: `public static int count = 0;` 

            2. 静态代码块的执行:在初始化阶段,会执行类中的静态代码块,这些代码块可以包含任意的Java语句,用于完成一些静态数据的初始化或其他静态操作。

  1. 示例:
  2. static {
  3.     // 静态代码块中的逻辑
  4.     System.out.println("Static block executed.");
  5. }

            3. 调用其他类的初始化方法:在初始化阶段,如果类的父类还未被初始化,则会先触发父类的初始化操作,确保父类的初始化在子类之前完成。

            注意,初始化阶段是在类加载过程中的最后一个阶段,只有在真正使用类时才会进行初始化。而且,初始化操作只会执行一次,即使多次加载同一个类,也只会执行一次初始化代码。

            示例中,当程序执行到 `Main` 类的 `main` 方法时,会触发对 `MyClass` 类的初始化。在初始化阶段,静态代码块会被执行,将静态变量 `count` 赋值为10。然后,通过调用 `MyClass` 类的静态方法 `doSomething()` ,会执行静态方法中的逻辑。            

  1. 示例:
  2.             public class MyClass {
  3.                 public static int count = 0;
  4.                 
  5.                 static {
  6.                     // 静态代码块中的逻辑
  7.                     System.out.println("Static block executed.");
  8.                     count = 10;
  9.                 }
  10.                 
  11.                 public static void doSomething() {
  12.                     // 静态方法中的逻辑
  13.                     System.out.println("Doing something.");
  14.                 }
  15.             }
  16.             public class Main {
  17.                 public static void main(String[] args) {
  18.                     // 使用类的静态成员
  19.                     System.out.println(MyClass.count); // 输出:10
  20.                     MyClass.doSomething(); // 输出:Doing something.
  21.                 }
  22.             }


    2.6、 使用(Usage)

        使用(Usage)阶段是在类加载完成后,通过创建实例对象或直接访问类的静态成员来使用类。通过实例对象可以调用实例方法和访问实例变量,而通过类名可以调用静态方法和访问静态变量。

           可能执行的一些操作

            1. 创建类的实例对象:在使用阶段,可以通过关键字 `new` 来创建类的实例对象,以便使用类的实例方法和实例变量。
               示例: `MyClass obj = new MyClass();` 

            2. 调用类的实例方法:通过类的实例对象,可以调用类的实例方法来执行相应的逻辑操作。
               示例: `obj.doSomething();` 

            3. 访问类的实例变量:通过类的实例对象,可以访问类的实例变量来获取或修改其值。
               示例: `int value = obj.getCount();` 

            4. 调用类的静态方法:可以直接通过类名调用类的静态方法,无需创建类的实例对象。
               示例: `MyClass.doStaticSomething();` 

            5. 访问类的静态变量:可以直接通过类名访问类的静态变量,无需创建类的实例对象。
               示例: `int value = MyClass.staticVariable;` 


            示例中,通过创建 `MyClass` 类的实例对象 `obj` ,可以调用实例方法 `doSomething()` 和访问实例变量 `count` 。同时,通过类名 `MyClass` 可以调用静态方法 `doStaticSomething()` 和访问静态变量 `staticVariable` 。            

  1. 示例:
  2.             public class MyClass {
  3.                 public int count;
  4.                 public static int staticVariable;
  5.                 public void doSomething() {
  6.                     // 实例方法中的逻辑
  7.                     System.out.println("Doing something.");
  8.                 }
  9.                 public static void doStaticSomething() {
  10.                     // 静态方法中的逻辑
  11.                     System.out.println("Doing static something.");
  12.                 }
  13.             }
  14.             public class Main {
  15.                 public static void main(String[] args) {
  16.                     // 创建类的实例对象
  17.                     MyClass obj = new MyClass();
  18.                     
  19.                     // 调用实例方法
  20.                     obj.doSomething();
  21.                     
  22.                     // 访问实例变量
  23.                     obj.count = 10;
  24.                     System.out.println(obj.count);
  25.                     
  26.                     // 调用静态方法
  27.                     MyClass.doStaticSomething();
  28.                     
  29.                     // 访问静态变量
  30.                     MyClass.staticVariable = 20;
  31.                     System.out.println(MyClass.staticVariable);
  32.                 }
  33.             }

    2.7、 卸载(Unloading)

        Java类卸载(Unloading)阶段是在类不再被引用或程序结束时进行的,它会释放类所占用的内存空间。

           可能执行的一些操作

            1. 卸载类的实例对象:当类的实例对象不再被引用或被垃圾回收器回收时,卸载阶段会释放实例对象所占用的内存空间。
               示例:当实例对象 `obj` 不再被引用时,可能会触发卸载阶段。

            2. 卸载类的静态成员:当类的静态成员不再被引用或程序结束时,卸载阶段会释放静态成员所占用的内存空间。
               示例:当静态变量 `count` 不再被引用时,可能会触发卸载阶段。

            注意,卸载阶段的具体触发时机是由Java虚拟机的垃圾回收器来决定的。当类不再被引用或无法被访问时,垃圾回收器可能会判断该类可以被卸载,并在适当的时候进行卸载操作。

            示例中,当程序运行结束后,可能会触发对 `MyClass` 类的卸载。在卸载阶段,会释放类所占用的内存空间。            

  1. 示例:
  2.             public class MyClass {
  3.                 // 类的定义
  4.             }
  5.             public class Main {
  6.                 public static void main(String[] args) {
  7.                     // 使用类的实例对象或静态成员
  8.                     MyClass obj = new MyClass();
  9.                     obj.doSomething();
  10.                     int value = MyClass.staticVariable;
  11.                     // 程序结束后,可能触发类的卸载
  12.                 }
  13.             }

            注意,Java虚拟机的具体实现可能会有所不同,卸载阶段的触发时机和具体操作可能因虚拟机的不同而有所差异。

3、类加载器

    类加载器(ClassLoader)是负责加载类的重要组件,它负责将类的字节码文件加载到内存中。
    JVM内置了三种类加载器:启动类加载器(Bootstrap ClassLoader)、扩展类加载器(Extension ClassLoader)、应用程序类加载器(Application ClassLoader)、用户自定义加载器。

    3.1、启动类加载器

        启动类加载器(Bootstrap ClassLoader)是Java虚拟机内置的类加载器,负责加载Java运行时环境的核心类库,如 `java.lang` 等。它是虚拟机的一部分,由C++实现,并不是一个Java类。启动类加载器无法被Java程序直接引用。

    3.2、扩展类加载器

        扩展类加载器(Extension ClassLoader)是Java虚拟机的内置类加载器之一,负责加载Java扩展类库,如 `javax` 等。它是由Java编写的类加载器,是 `sun.misc.Launcher$ExtClassLoader` 的实例。

    3.3、应用程序类加载器

        应用程序类加载器(Application ClassLoader)是Java虚拟机的内置类加载器之一,也被称为系统类加载器,负责加载应用程序的类。它是由Java编写的类加载器,是 `sun.misc.Launcher$AppClassLoader` 的实例。

    3.4、用户自定义加载器

        用户可以根据需要自定义自己的类加载器(User ClassLoader),继承 `java.lang.ClassLoader` 类,并实现加载类的逻辑。用户自定义加载器可以用于加载特定的类或资源,实现类加载的定制化。        示例中,自定义加载器 `CustomClassLoader` 继承自 `ClassLoader` 类,并重写了 `findClass` 方法来实现类加载的逻辑。然后通过实例化自定义加载器,并调用 `loadClass` 方法来加载指定的类。

  1. 示例:
  2.         public class CustomClassLoader extends ClassLoader {
  3.             @Override
  4.             public Class<?> findClass(String name) throws ClassNotFoundException {
  5.                 // 自定义加载类的逻辑
  6.                 byte[] byteCode = loadClassData(name);
  7.                 return defineClass(name, byteCode, 0, byteCode.length);
  8.             }
  9.             private byte[] loadClassData(String name) {
  10.                 // 加载类字节码的逻辑
  11.                 // ...
  12.             }
  13.         }
  14.         // 使用自定义加载器加载类
  15.         CustomClassLoader customClassLoader = new CustomClassLoader();
  16.         Class<?> myClass = customClassLoader.loadClass("com.example.MyClass");
  17.         

4、双亲委派

    双亲委派(Parents Delegation Model)是类加载器的一种工作机制。当一个类加载器收到加载类的请求时,它首先将该请求委派给父类加载器,只有在父类加载器无法加载该类时,才由当前类加载器自己加载。这种机制可以确保类的加载是从上至下的层级关系,避免重复加载和冲突。
    Delegation [ˌdelɪˈɡeɪʃn]  n. 代表团; 委托; 委派;

    双亲委派的工作过程简述

        1. 当一个类加载器收到加载类的请求时,它首先会检查是否已经加载过该类。如果已经加载过,则直接返回该类的Class对象。
        2. 如果该类还未被加载过,当前类加载器会将加载请求委派给其父类加载器。
        3. 父类加载器接收到加载请求后,会按照相同的方式进行检查和委派。如果父类加载器能够加载该类,则返回该类的Class对象。
        4. 如果父类加载器无法加载该类,则将加载请求再次委派给它的父类加载器,依次往上层的父类加载器进行委派。
        5. 如果所有的父类加载器都无法加载该类,则当前类加载器会尝试自己加载该类。它会通过自己的加载机制,查找类的字节码文件,并将其加载到内存中。
        6. 加载完成后,当前类加载器会创建该类的Class对象,并返回给上层的类加载器。

        通过这种委派方式,类加载器会按照层级关系从上至下依次尝试加载类。这样可以确保类的加载是有序的,避免了重复加载和冲突的问题。

    
    示例:
        假设有以下类加载器层级关系:
        Bootstrap ClassLoader(启动类加载器) -> Extension ClassLoader(扩展类加载器) -> Application ClassLoader(应用程序类加载器)。

        当应用程序需要加载类 `com.example.MyClass` 时,双亲委派模型的工作过程如下:

        1. Application ClassLoader收到加载请求,首先检查是否已加载过 `com.example.MyClass` 。
        2. 如果没有加载过,则将加载请求委派给Extension ClassLoader。
        3. Extension ClassLoader接收到请求后,也会检查是否已加载过该类。如果没有加载过,则将请求委派给Bootstrap ClassLoader。
        4. Bootstrap ClassLoader检查后发现没有加载过该类,因此尝试自己加载 `com.example.MyClass` 。
        5. 如果Bootstrap ClassLoader也无法加载该类,则会返回给Extension ClassLoader。
        6. Extension ClassLoader收到返回后,再次尝试加载该类。如果仍然无法加载,则将请求返回给Application ClassLoader。
        7. 最后,Application ClassLoader尝试自己加载该类。如果成功加载,则返回 `com.example.MyClass` 的Class对象。


 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/码创造者/article/detail/1006978
推荐阅读
相关标签
  

闽ICP备14008679号