JVM之类加载运行全过程

本文详细阐述了JVM的类加载全过程,包括加载、验证、准备、解析和初始化五个步骤。介绍了类加载器的层次结构,如引导类加载器、扩展类加载器和应用程序类加载器,以及双亲委派机制的工作原理。此外,文章还讨论了自定义类加载器的实现和为何有时需要打破双亲委派机制,以Tomcat为例进行了说明。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

JVM之类加载运行全过程

public class ClassLoading{
   
   
    public static final int data = 666;
    public static User user = new User();
    public int handle() {
   
   
        int a = 1;
        int b = 2;
        int c = a + b;
        return c;
    }
    public static void main(String[] args) {
   
   
        Math math = new Math();
        math.compute();
    }
}

以上面的类为例,当我们用java命令运行类的main函数启动程序时,首先会启动jvm实例进行进行一系列的类加载的前期准备和类记载过程。过程如下图
在这里插入图片描述
其中loadClass的类加载过程有如下几步:
加载 >> 验证 >> 准备 >> 解析 >> 初始化 >> 使用 >> 卸载

1.加载:

通过一个类的全限定名获取定义此类的二进制字节流;
将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构;
在内存中生层一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。
加载.class文件的方式:
从本地系统中直接加载
通过网络获取,典型场景: Web Applet
从zip压缩包中读取,成为日后jar、war格式的基础
运行时计算生成,使用最多的是: 动态代理技术
由其他文件生成,典型场景:JSP应用
从专有数据库中提取. class文件,比较少见
从加密文件中获取,典型的房Class文件被反编译的保护措施
类被加载到方法区中后主要包含 运行时常量池、类型信息、字段信息、方法信息、类加载器的引用、对应class实例的引用等信息。
类加载器的引用:这个类到类加载器实例的引用
对应class实例的引用:类加载器在加载类信息放到方法区中后,会创建一个对应的Class 类型的对象实例放到堆(Heap)中, 作为开发人员访问方法区中类定义的入口和切入点。
注意,主类在运行过程中如果使用到其它类,会逐步加载这些类。
jar包或war包里的类不是一次性全部加载的,是使用到时才加载。

2.验证

校验字节码文件的正确性

3.准备

给类的静态变量分配内存,并赋予默认值

4.解析

将符号引用替换为直接引用,该阶段会把一些静态方法(符号引用,比如main()方法)替换为指向数据所存内存的指针或句柄等(直接引用),这是所谓的静态链接过程(类加载期间完成),动态链接是在程序运行期间完成的将符号引用替换为直接引用

5.初始化

对类的静态变量初始化为指定的值,执行静态代码块,注意只有静态的。

类加载器

上面的类加载过程主要是通过类加载器来实现的,Java里有如下几种类加载器
引导类加载器:
负责加载支撑JVM运行的位于JRE的lib目录下的核心类库,比如rt.jar、charsets.jar等。
扩展类加载器:
负责加载支撑JVM运行的位于JRE的lib目录下的ext扩展目录中的JAR类包
应用程序类加载器:负责加载ClassPath路径下的类包,主要就是加载你自己写的那些类。
自定义加载器:负责加载用户自定义路径下的类包

类加载器初始化过程

参见上图中类运行加载全过程可知其中会创建JVM启动器实例sun.misc.Launcher。
在Launcher构造方法内部,其创建了两个类加载器,分别是sun.misc.Launcher.ExtClassLoader(扩展类加载器)和sun.misc.Launcher.AppClassLoader(应用类加载器)。
JVM默认使用Launcher的getClassLoader()方法返回的类加载器AppClassLoader的实例加载我们的应用程序。
可以自行去看Launcher的源码,这里附一段Launcher初始化源码:

//Launcher的构造方法
public Launcher() {
   
   
    Launcher.ExtClassLoader var1;
    try {
   
   
        //构造扩展类加载器,在构造的过程中将其父加载器设置为null
        var1 = Launcher.ExtClassLoader.getExtClassLoader();
    } catch (IOException var10) {
   
   
        throw new InternalError("Could not create extension class loader", var10);
    }
    try {
   
   
        //构造应用类加载器,在构造的过程中将其父加载器设置为ExtClassLoader,
        //Launcher的loader属性值是AppClassLoader,我们一般都是用这个类加载器来加载我们自己写的应用程序
        this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
    } catch (IOException var9) {
   
   
        throw new InternalError("Could not create application class loader", var9);
    }

    Thread.currentThread().setContextClassLoader(this.loader);
    String var2 = System.getProperty("java.security.manager");
    。。。。。。//省略一些不需关注代码
}

双亲委派机制

JVM类加载器是有亲子层级结构的,如下图
在这里插入图片描述
这里类加载其实就有一个双亲委派机制,加载某个类时会先委托父加载器寻找目标类,找不到再委托上层父加载器加载,如果所有父加载器在自己的加载类路径下都找不到目标类,则在自己的类加载路径中查找并载入目标类。
比如我们的ClassLoading类,最先会找应用程序类加载器加载,应用程序类加载器会先委托扩展类加载器加载,扩展类加载器再委托引导类加载器,顶层引导类加载器在自己的类加载路径里找了半天没找到ClassLoading类,则向下退回加载ClassLoading类的请求,扩展类加载器收到回复就自己加载,在自己的类加载路径里找了半天也没找到ClassLoading类,又向下退回ClassLoading类的加载请求给应用程序类加载器,应用程序类加载器于是在自己的类加载路径里找ClassLoading类,结果找到了就自己加载了。
双亲委派机制说简单点就是,先找父亲加载,不行再由儿子自己加载
我们来

### 回答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的类加载过程中,解析阶段的主要目的是将常量池中的符号引用转换为直接引用,以便后续的执行中可以直接访问到目标。这个过程在类加载的第三个阶段准备之后进行。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

斯巴达人

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值