JVM之类加载过程

类加载过程

类加载过程分为以下三个过程 加载 -> 链接 -> 初始化,而链接又可以分为三个过程 验证 -> 准备 -> 解析
整个类加载过程入下图
在这里插入图片描述

类的整个生命周期在此基础上又加了两个过程 使用 -> 卸载
在这里插入图片描述

  • 加载
    查找并加载类的二进制数据。

  • 链接

    • 验证
      确保类的信息都是正确的,符合当前使用虚拟机的规范。
      • 文件格式验证:验证字节流是否符合Class文件的格式规范,且能被当前虚拟机处理
      • 源数据验证:对字节码描述的信息进行语义分析,以保证其描述的信息符合Java语言规范。。
      • 字节码验证:通过数据流和控制流分析,确定程序语义是合法的符合逻辑的。
      • 符号引用验证:对类自身以外的信息进行匹配性校验。
    • 准备
      为类的静态变量分配空间(方法区)并初始化为默认值。
      需要注意一下几点
      • 对于基本类型来说,类变量和全局变量如果不显式的对其赋值而直接使用,系统则为其赋值为默认的零值;对于局部变量在使用之前必须显式的为其赋值,否则编译不通过。
      • 对于同时被finalstatic修饰的常量,必须在声明的时候就为其显式的赋值,否则编译不通过;而只被final修饰的常量既可以在声明是显式的赋值,也可以在类初始化时显示的复制,总之在使用之前必须显式的复制。
      • 对于引用类型,如果没有对其显示的赋值而直接使用,系统会为其赋值为默认的零值。
      • 对于数组在初始化时没有对数组中的各个元素复制,那么系统会根据数据对应的元素类型赋值默认的零值
    • 解析
      将符号引用转为直接引用(地址引用)的过程。直接引用就是直接指向目标的指针、相对偏移量或者一个间接定位到目标的句柄。解析主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符这七类符号引用进行。
  • 初始化
    JVM对类初始化,为类的静态变量赋正确值初始值。对类变量进行初始值设置有两种方式:

    • 声明类变量时指定初始值;
    • 使用静态代码块为类变量指定初始值。

    类初始化时机
    只用当类主动使用的时候才会导致类的初始化。主动是要包含一下6种:

    • 创建类的示例,new;
    • 访问某个类或接口的静态变量,或者对静态变量赋值;
    • 调用类的静态方法;
    • 反射;
    • 初始化某个类的子类;
    • Java虚拟机启动是被表名为启动类的类,直接使用java.exe命令来运行某个类。

如果大家觉得对于类的整个生命周期不方便我们记忆,我们把它想象成购物买手机的过程就很好记忆了。

故事是这样的…
故事是这样的…
张三存了好久的钱想买一个手机,于是他在网上买了一个IPhone13 Pro Max 1TB 远峰蓝手机,下单就是(加载);
怀着激动心情等了几天终于收到新手机(链接);
收到手机后我们肯定不能直接使用,还有一些使用前的流程,看看手机屏幕有没有刮痕,电池有没有鼓包等等(验证);
如果没有问题,我们贴膜,把手机卡从旧手机中拿出来,放到新手机中等等(准备);
然后开机,开机过程中会解析我们的手机卡是移动、联调亦或者电信(解析);
正常开启后我们就需要对手机进行数据同步呀等等(初始化);等都操作完我们就可以开心的用新手机了(使用);
过了些许时间,张三又买了其他的手机,于是就把这个手机给卖了(卸载);
至此该手机的使命就完成。
于是整个流程就是

  • 下单(加载
  • 收货(链接
    • 验货(验证
    • 使用前准备( 准备
    • 插卡开机(解析
  • 使用(使用
  • 换手机(卸载

类加载器

从虚拟机的角度来讲,类加载器可分为两类:

  • 启动类加载器:使用C++实现(也有java实现的),是虚拟机自身的一部分。
  • 其他类加载器:由java实现,独立于虚拟机之外,全部继承于java.lang.ClassLoader,这些类加载器需要由启动类加载器加载到内存后才能去加载其他类。

从开发人员的角度来看,类加载器可大致分为三类:

  • 启动类加载器:BootstrapClassLoader,最顶层的类加载器,负责加载JAVA_HOME/lib目录下的jar和类或者被 -Xbootclasspath 参数指定的路径中的所有类。
  • 扩展类加载器:ExtensionClassLoader,负责加载JAVA_HOME/lib/ext目录下的jar包和类,或者被 java.ext.dirs系统变量所指定的路径下的jar包。
  • 应用程序加载器:ApplicationClassLoader,面向开发人员的加载器,负责加载当前应用classpath下的所有jar包和类。

双亲委派

在类被加载的时候,系统会首先判断当前类是否被加载过,如果加载过直接返回,否则才会尝试加载。加载的时候,首页会把改请求委派给父类加载器来加载当前类,因此所有的类加载最终都会传到顶层的启动类加载器 BootstrapClassLoader中。当父加载器无法加载当前类是,才由自己来处理。当父类加载器为null时,会使用启动类加载器来加载。

所以类加载流程如下

我们可以简单验证下子父关系

public class ClassLoaderTest {
    public static void main(String[] args) {
        System.out.println("当前类的类加载器 ===> " + ClassLoaderTest.class.getClassLoader());
        System.out.println("当前类的类加载器的父类 ===> " + ClassLoaderTest.class.getClassLoader().getParent());
        System.out.println("当前类的类加载器的父类的父类 ===> " + ClassLoaderTest.class.getClassLoader().getParent().getParent());
    }
}

输出如下

当前类的类加载器 ===> sun.misc.Launcher$AppClassLoader@18b4aac2
当前类的类加载器的父类 ===> sun.misc.Launcher$ExtClassLoader@76fb509a
当前类的类加载器的父类的父类 ===> null

当父类为空时类加载器为启动类加载器

类加载源码

源码在 java.lang.ClassLoader.loaderClass() 中,代码很简单

protected Class<?> loadClass(String name, boolean resolve)
            throws ClassNotFoundException {
        synchronized (getClassLoadingLock(name)) {
            // 首先,查询当前类是否被加载过
            Class<?> c = findLoadedClass(name);
            // 没被加载过
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    // 如果父加载器不为空就调用父类加载
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        // 父类为空就调用顶层启动类加载器加载
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }
                // 如果父类都不能加载
                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    long t1 = System.nanoTime();
                    // 尝试自己加载
                    c = findClass(name);

                    // this is the defining class loader; record the stats
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

双亲委派的好处

避免类被重复加载;保证Java核心API类不被篡改;如果没有双亲委派机制,每个类都用自己的类加载器,如果我们也编写一个和java核心类一样的类,系统中就会出现多个同名的类,例如我们编写一个java.lang.String,那么系统总就会有两个String类

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: JVMJava Virtual Machine)是Java程序的执行环境。当你运行一个Java程序时,它首先被编译成字节码,然后JVM将字节码解释成机器码并执行。 JVM类加载过程可以分为以下几个步骤: 1. 加载(Loading):加载指的是将.class文件读入内存,并为之创建一个java.lang.Class对象。类加载器会负责从文件系统、JAR文件或网络中加载的字节码数据。 2. 链接(Linking):链接分为三个阶段,分别是验证(Verification)、准备(Preparation)和解析(Resolution)。 * 验证:验证字节码是否符合JVM规范,并且不会危害JVM的安全。如果验证失败,则会抛出java.lang.VerifyError异常。 * 准备:为的静态变量分配内存,并将其初始化为默认值(0、null等)。 * 解析:将、接口、字段和方法的符号引用解析为实际引用。这个过程可能需要在运行时进行。 3. 初始化(Initialization):在类加载过程中,初始化是最后一步。在这个阶段,静态变量被初始化,静态块被执行。如果初始化一个时发生异常,则会抛出java.lang.ExceptionInInitializerError异常。 JVM类加载器有以下几种: 1. 启动类加载器(Bootstrap ClassLoader):它是最顶层的类加载器,负责加载JVM的核心库,如java.lang和java.util等。 2. 扩展类加载器(Extension ClassLoader):它加载Java平台扩展库的。默认情况下,它从$JAVA_HOME/jre/lib/ext目录加载。 3. 系统类加载器(System ClassLoader):也称应用程序类加载器,它加载应用程序路径上的。 4. 用户自定义类加载器:开发人员可以继承java.lang.ClassLoader,以实现自己的类加载器。 总之,JVM类加载过程Java程序运行的重要部分,它可以确保Java程序的正确执行。 ### 回答2: JVMJava虚拟机)类加载过程,是指JVM将字节码文件加载到内存,并转化为可以被JVM执行的可执行代码的过程。其中,解析是类加载过程一个重要步骤。 解析是JVM或接口的常量池中的符号引用进行直接引用的过程。在解析阶段,JVM将符号引用转换成直接引用,使得或接口可以直接被调用和执行。解析包括以下几个步骤: 1. 或接口的符号引用:在或接口的常量池中,使用符号引用表示对其他或接口的引用,符号引用包括的全限定名、方法的签名以及字段的描述符等。 2. 或接口的符号解析:JVM将符号引用转换成直接引用的过程。直接引用是一个指向、方法、字段在内存中的地址,JVM可以根据直接引用直接访问、方法或字段。 3. 的初始化:在的解析过程中,JVM还会执行的初始化。的初始化包括为静态变量赋值、执行静态代码块等。的初始化是在解析过程的最后阶段执行的,确保在被解析之后可以正常执行。 需要注意的是,或接口的解析并不一定发生在加载过程的一开始,JVM会根据需要进行解析。同时,在解析过程中,如果发生了符号引用无法解析的错误,JVM会抛出NoClassDefFoundError异常。 总之,JVM类加载过程中的解析是将或接口的符号引用转换成直接引用的过程,使得程序可以直接访问和执行、方法以及字段。解析是类加载过程的关键步骤之一,保证了的正确加载和正常执行。 ### 回答3: JVM类加载过程包括:加载、验证、准备、解析和初始化五个阶段。其中,解析是指将常量池中的符号引用替换为直接引用的过程。 1. 加载阶段:JVM通过类加载器将字节码文件加载到内存中。加载阶段包括三个步骤:通过的全限定名找到定义的二进制数据文件,将二进制数据读入内存并创建一个Class对象,并在内存中生成一个代表该的Class对象。 2. 验证阶段:JVM对字节码进行验证,确保字节码文件符合JVM规范,并且没有安全方面的问题,如是否包含不合法或危险的代码。 3. 准备阶段:JVM的静态字段分配内存并设置默认初始值。这些值保存在方法区的静态变量区域中,方法区是JVM中的一块内存区域,用于存储的结构信息。 4. 解析阶段:在解析阶段,JVM、接口、字段和方法的符号引用替换为直接引用。符号引用是一组描述被引用的目标的符号,而直接引用是直接指向目标的指针、句柄或偏移量。在解析阶段,JVM将符号引用转换为直接引用,以便后续的执行中可以直接访问到目标。 5. 初始化阶段:在初始化阶段,JVM会对的静态变量赋予正确的初始值,并执行静态代码块。静态代码块中的代码主要用于初始化的静态变量和执行静态初始化块。只有在这个阶段,的实例才会被真正地创建。 总结来说,JVM类加载过程中,解析阶段的主要目的是将常量池中的符号引用转换为直接引用,以便后续的执行中可以直接访问到目标。这个过程类加载的第三个阶段准备之后进行。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值