JVM类加载过程

部署运行你感兴趣的模型镜像
详见: [url]http://blog.yemou.net/article/query/info/tytfjhfascvhzxcytpo5[/url]
JVM:加载、链接和初始化
JVM要解释Java字节码,就必须对所需的类和接口执行如下3步操作:
(1) 加载:JVM在加载类时,会查找该类或该接口的二进制表示,并根据找到的二进制表示(通常是由Java编译器创建的类文件)创建一个Class对象。该Class对象中封装了类或接口的运行时状态。

由类加载器执行,该步骤将查找字节码,并从这些字节码中创建一个class对象。

(2) 链接:链接这一过程是指取得已加载的类或接口、结合JVM运行时环境、准备执行该类或该接口。

在链接阶段将验证类中的字节码,为静态域分配内存空间,并且如果必须的话,将解析这个类创建的对其他类的引用。

(3) 初始化:初始化是指JVM调用该类或该接口的初始化方法。

如果该类具有超类,则对其初始化,执行静态初始化器和静态初始化块。
初始化延迟到了对静态方法(构造器默认也是静态的)或者非常数静态域进行首次引用时才执行
1. 第一步
启动一个单机Java程序时,JVM首先做的是另外创建一个Class对象,用于表示包含public static void main(String [ ] args)方法的Java类。然后JVM会链接并初始化该Java类,调用main()方法,并用main()方法驱动所引用的其他类和接口的加载、链接和初始化过程。
2. 加载
加载过程是由类加载器完成的,该加载器是ClassLoader的子类,并且该类加载器会对所加载的类或接口进行一些校验检查。当表示已编译类或接口的二进制数据有错,则类或接口使用的类文件格式版本不被支持,类加载器找不到类或接口的定义,或者如果出现类循环,都会抛出异常。类循环是指类或接口的父类是其自身的情况。
类加载器一般有两种类型:由JVM提供的引导类加载器(bootstrap class loader)和用户定义的类加载器。用户定义的类加载器也是Java的ClassLoader类的子类,用于从非标准的、用户定义的源创建Class对象,以便提高安全性。例如,从加密文件中提取Class对象。一个加载器可以将部分甚至整个加载过程委托给另一个加载器。最终生成Class对象的加载器称为定义加载器(defining loader),而开始该加载过程的加载器称为启动加载器(initiating loader)。
使用默认引导类加载器的加载过程如下:根据所要加载的类文件,引导类加载器会判断自身是否已经成为该类的启动加载器。如果是,则Class对象存在,加载器停止(注意,加载一个类并不等于创建该类的一个实例,这一步骤仅仅是在JVM中加入该类)。如果类还没有加载,则加载器会搜索对应的类文件,并在找到后根据该类文件创建Class对象。如果找不到类文件,那么就会产生NoClassDefFoundError异常。
使用用户定义类加载器时,整个加载过程稍有不同。与引导加载器一样,用户定义的加载器首先判断自身是否已经成为目标类文件的启动加载器。如果是,则Class对象已经存在,加载器停止,而如果不是,用户定义的加载器会调用loadClass()方法。loadClass()方法返回所需的类文件并将表示类的二进制字节装配成ClassFile结构,然后调用defineClass()方法,由该方法从ClassFile结构创建Class对象。另外,loadClass()方法也可以将加载过程委托给另一个类加载器。
3. 链接
链接过程的第一步是校验需要链接的类文件。
Java类文件校验
由于JVM与Java编译器是完全分离的,因此,用来解释类文件的JVM无法保证类文件的形式正确,甚至无法保证该文件确实由Java编译器所生成。另一个问题在于继承与类兼容性。如果给定类文件所表示的类继承自另一个类文件表示的父类,那么JVM必须确保该子类的类文件与父类的类文件兼容。
JVM会校验每个类文件是否满足Java语言规范对类文件的约束,不过Java类校验器与Java语言无关。用某些其他语言编写的程序同样也能编译成类文件格式,编译之后,该类文件也能通过校验过程。
校验过程分为4个步骤:
(1) 第一步由JVM加载类文件并检查文件是否符合类文件的基本格式。类文件的长度必须准确。类文件必须确实表示类(检查其中一个特殊数字)。常量池中不能包含任何不可识别的信息,并且每个属性的长度正确。
(2) 校验过程的第二步在文件链接时进行。这一步执行的操作包括确保final关键字约束的保留。这表示final类不能派生子类,final方法也不能被重写。然后确保常量池中的元素符合Java语言的规定。验证常量池中的所有字段和方法引用,并检查每一个类(Object类除外)是否具有直接父类。
(3) 第三个校验步骤也在链接阶段进行。这一步检查类文件中引用的每一个方法,确保符合Java语言对方法的规定。方法调用中参数的数量和类型必须正确。操作数栈必须总保持相同大小,并包含相同类型的值。局部变量在访问前应当包含合适的值。必须为字段指定正确类型的值。
(4) 校验的最后一步是处理第一次调用方法时出现的事件,并保证一切按规范进行。这些检查包括:确保给定类中存在某个引用的字段或引用的方法,确认引用的字段或引用的方法具有正确的描述符,并确保一个方法在运行时能够访问该引用字段或引用方法。
准备
在校验类文件之后,JVM准备初始化类,包括为类变量分配内存空间并设置为默认初始值。这些值是标准的默认值,例如int类型为0,Boolean类型为false等。在初始化阶段,这些值会设为程序相关的默认值。
解析
在这一可选的步骤中,JVM把运行时常量池中引用的符号解析成具体值。
4. 初始化
链接过程完成后,会调用静态字段和静态初始化器。静态字段的值即使在类没有实例化时也能够访问得到,而静态初始化器用于单个表达式无法表示的静态初始化。JVM把所有这类初始化器收集到一个特殊的方法中。例如,类所有初始化器的集合就是初始化方法<clinit>。
不过,JVM在初始化一个类时不仅需要调用该类的初始化方法(只有JVM能够调用),而且需要初始化所有的父类(即需要调用这些父类的<clinit>)。结果就是,总是需要最先初始化Object类。另外,包含应用程序main()方法的类总是要初始化。

您可能感兴趣的与本文相关的镜像

Qwen-Image

Qwen-Image

图片生成
Qwen

Qwen-Image是阿里云通义千问团队于2025年8月发布的亿参数图像生成基础模型,其最大亮点是强大的复杂文本渲染和精确图像编辑能力,能够生成包含多行、段落级中英文文本的高保真图像

### JVM 类加载机制详细过程 JVM(Java虚拟机)的类加载机制是Java程序运行的基础之一,它负责将编译后的 `.class` 文件加载到内存中,并对这些数据进行校验、解析和初始化,最终形成可以直接被JVM使用的Java类型。整个过程可以分为七个阶段:加载、验证、准备、解析、初始化、使用和卸载,其中前五个阶段统称为类加载过程。 #### 加载(Loading) 加载阶段是类加载过程的第一个阶段,主要任务是通过类的全限定名获取其对应的二进制字节流,并将这些字节流转化为方法区中的运行时数据结构。同时,JVM会在堆中创建一个 `java.lang.Class` 对象,作为该类访问数据的入口。 #### 验证 验证阶段的目的是确保Class文件的字节流中包含的信息符合JVM规范的要求,避免JVM受到恶意代码的攻击。验证过程包括文件格式验证、元数据验证、字节码验证以及符号引用验证。 #### 准备 准备阶段的主要任务是为类的静态变量分配内存,并为其设置初始值。这个初始值通常是零值(如 `0`、`null`),而不是代码中显式赋值的值。显式赋值会在初始化阶段完成。 #### 解析 解析阶段是将常量池中的符号引用替换为直接引用的过程。符号引用是以一组符号来描述所引用的目标,而直接引用则是直接指向目标的指针、相对偏移量或一个能间接定位到目标的句柄。解析阶段并不一定在初始化之前完成,某些解析操作可能会延迟到初始化之后进行,这是为了支持Java的动态绑定特性。 #### 初始化 初始化阶段是类加载过程的最后一个阶段,它负责执行类构造器 `<clinit>` 方法。`<clinit>` 方法是由编译器自动收集类中的所有静态变量的赋值动作和静态代码块中的语句合并生成的,代码会按照书写顺序依次执行。如果类的父类尚未初始化JVM会先触发其父类的初始化过程,以确保继承链的完整性。此外,JVM会确保 `<clinit>` 方法在多线程环境中被正确加锁和同步,以保证线程安全。 例如,在以下代码中,`ConstClass` 类的静态代码块不会在 `NotInit` 类的 `main` 方法执行时被触发初始化,因为 `HELLOWORLD` 是一个 `static final` 常量,其值在编译期就已经确定,并直接嵌入到调用类的常量池中: ```java public class ConstClass { static { System.out.println("常量类初始化!"); } public static final String HELLOWORLD = "hello world!"; } public class NotInit { public static void main(String[] args) { System.out.println(ConstClass.HELLOWORLD); } } ``` #### 类加载JVM中的类加载器分为以下几类: 1. **启动类加载器(Bootstrap ClassLoader)**:负责加载JVM核心类库(如 `rt.jar` 中的类),通常是用C++实现的,而不是Java代码。 2. **扩展类加载器(Extension ClassLoader)**:负责加载Java的扩展类库(如 `jre/lib/ext` 目录下的类)。 3. **系统类加载器(System ClassLoader)**:负责加载应用程序的类路径(ClassPath)上的类,通常是最常用的类加载器。 4. **自定义类加载器(Custom ClassLoader)**:用户可以通过继承 `ClassLoader` 类来实现自定义的类加载逻辑。 类加载器之间遵循 **双亲委派模型**,即当一个类加载器收到类加载请求时,它会首先委托给父类加载器去加载,只有在父类加载器无法完成加载时,才会尝试自己加载。这种机制可以确保类的唯一性和安全性。 #### 示例代码 以下是一个简单的类加载器示例,展示了如何通过自定义类加载器加载类: ```java public class CustomClassLoader extends ClassLoader { @Override protected Class<?> findClass(String name) throws ClassNotFoundException { byte[] classData = loadClassData(name); if (classData == null) { throw new ClassNotFoundException(); } else { return defineClass(name, classData, 0, classData.length); } } private byte[] loadClassData(String className) { // 实现类的加载逻辑,例如从文件或网络读取字节码 return new byte[0]; } } ``` #### 相关问题 - 类加载的双亲委派模型是如何工作的? - 如何实现一个自定义的类加载器? - 为什么 `static final` 常量不会触发类的初始化? - 类加载过程中验证阶段的作用是什么? - 解析阶段为什么可以延迟到初始化之后?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值