学习Java类加载机制

简介: 在Java的世界里,每一个类或者接口,在经历编译器后,都会生成一个个.class文件。

在Java的世界里,每一个类或者接口,在经历编译器后,都会生成一个个.class文件。

类加载机制指的是将这些.class文件中的二进制数据读入到内存中,并对数据进行校验,解析和初始化。最终,每一个类都会在方法区保存一份它的元数据,在堆中创建一个与之对应的Class对象。

类的生命周期,经历7个阶段,分别是加载、验证、准备、解析、初始化、使用、卸载。

除了使用和卸载两个过程,前面的5个阶段 加载、验证、准备、解析、初始化 的执行过程,就是类的加载过程。

加载

加载是类加载过程的第一个阶段,在加载阶段,虚拟机需要完成以下三件事情:

通过一个类的全限定名去找到其对应的.class文件

将这个.class文件内的二进制数据读取出来,转化成方法区的运行时数据结构

在Java堆中生成一个代表这个类的java.lang.Class对象,作为对方法区中这些数据的访问入口

Java虚拟机并没有规定类的字节流必从.class文件中加载,在加载阶段,程序员可以通过自定义的类加载器,自行定义读取的地方,例如通过网络、数据库等。

 

验证

Class文件中的内容是字节码,这些内容可以由任何途径产出,验证阶段的目的是保证文件内容里的字节流符合Java虚拟机规范,且这些内容信息运行后不会危害虚拟机自身的安全。

验证阶段会完成以下校验:

文件格式验证:验证字节流是否符合Class文件格式的规范。例如:是否以0xCAFEBABE开头、主次版本号是否在当前虚拟机的处理范围之内、常量池中的常量是否有不被支持的类型 ...... 等等

元数据验证:对字节码描述的元数据信息进行语义分析,要符合Java语言规范。例如:是否继承了不允许被继承的类(例如final修饰过的)、类中的字段、方法是否和父类产生矛盾 ...... 等等

字节码验证:对类的方法体进行校验分析,确保这些方法在运行时是合法的、符合逻辑的。

符号引用验证:发生在解析阶段,符号引用转为直接引用的时候,例如:确保符号引用的全限定名能找到对应的类、符号引用中的类、字段、方法允许被当前类所访问 ...... 等等

验证阶段是非常重要的,但不是必须的,它对程序运行期没有影响,如果所引用的类经过反复验证,那么可以考虑采用-Xverifynone参数来关闭大部分的类验证措施,以缩短虚拟机类加载的时间。

验证阶段不是必须的,虽然这个阶段非常重要。Java虚拟机允许程序员主动取消这个阶段,用来缩短类加载的时间,可以根据自身需求,使用 -Xverify:none参数来关闭大部分的类验证措施。

准备

这个阶段,类的静态字段信息(即使用 static 修饰过的变量)会得到内存分配,并且设置为初始值。

对于该阶段有以下几个知识点需要注意:

1、内存分配仅包括 static 修饰过的变量,而不包括实例变量,实例变量得等到对象实例化时分配内存。

2、初始值指的是变量数据类型的默认值,而不是被在Java代码中被显式地赋予的值。但是,当字段信息被 final 修饰成常量(ConstantValue)时,这个初始值就是Java代码中显式地赋予的值。

例如:public static int value = 3

类变量 value 在准备阶段设置的初始值 是 0,不是 3。把value赋值为3的 putstatic 指令是在程序编译后,存放于类构造器 <clinit>() 方法中的,所以把 value 赋值为 3 的动作将在初始化阶段才会执行。

当使用 final 修饰后:public static  final int value = 3

类变量 value 在准备阶段设置的初始值 是 3,不是 0。

3、在JDK8取消永久代后,方法区变成了一个逻辑上的区域,这些类变量的内存实际上是分配在Java堆中的。

解析

这个阶段,虚拟机会把这个Class文件中,常量池内的符号引用转换为直接引用。主要解析的是 类或接口、字段、类方法、接口方法、方法类型、方法句柄等符号引用。我们可以把解析阶段中,符号引用转换为直接引用的过程,理解为当前加载的这个类,和它所引用的类,正式进行“连接“的过程。

什么是符号引用?

Java代码在编译期间,是不知道最终引用的类型,具体指向内存中哪个位置的,这时候会用一个符号引用,来表示具体引用的目标是"谁"。Java虚拟机规范中明确定义了符号引用的形式,符合这个规范的前提下,符号引用可以是任意值,只要能通过这个值能定位到目标。

什么是直接引用?

直接引用就是可以直接或间接指向目标内存位置的指针或句柄。

引用的类型,还未加载初始化怎么办?

当出现这种情况,会触发这个引用对应类型的加载和初始化。

初始化

这是类加载的最后一个步骤啦,初始化的过程,就是执行类构造器 <clinit>()方法的过程。

当初始化完成之后,类中static修饰的变量会赋予程序员实际定义的“值”,同时类中如果存在static代码块,也会执行这个静态代码块里面的代码。

<clinit>() 方法的作用是什么?

还记得么?在准备阶段,已经对类中static修饰的变量赋予了初始值。<clinit>() 方法的作用,就是给这些变量赋予程序员实际定义的“值”。同时类中如果存在static代码块,也会执行这个静态代码块里面的代码。

<clinit>() 方法是什么?

<clinit>() 方法 和 <init> 方法是不同的,它们一个是“类构造器”,一个是实例构造器。

Java虚拟机会保证子类<clinit>() 方法在执行前,父类的 <clinit>() 已经执行完毕。而 <init> 方法则需要显性的调用父类的构造器。

<clinit>() 方法由编译器自动生成,但不是必须生成的,只有这个类存在static修饰的变量,或者类中存在静态代码块但时候,才会自动生成<clinit>()方法。

加载过程总结

当一个符合Java虚拟机规范的字节流文件,经历 加载、验证、准备、解析、初始化这些阶段相互协作执行完成之后,加载阶段读取到的Class字节流信息,会按虚拟机规定的格式,在方法区保存一份,然后Java 堆中,会创建一个 java.lang.Class 类的对象,这个对象描述了这个类所有信息,也提供了这个类在方法区的访问入口。

方法区中,使用同一加载器的情况下,每个类只会有一份Class字节流信息

Java堆中,使用同一加载器的情况下,每个类只会有一份 java.lang.Class 类的对象

相关文章
|
5月前
|
IDE Java 编译器
java编程最基础学习
Java入门需掌握:环境搭建、基础语法、面向对象、数组集合与异常处理。通过实践编写简单程序,逐步深入学习,打牢编程基础。
310 1
|
6月前
|
Java API 容器
Java基础学习day08-2
本节讲解Java方法引用与常用API,包括静态、实例、特定类型方法及构造器引用的格式与使用场景,并结合代码示例深入解析。同时介绍String和ArrayList的核心方法及其实际应用。
181 1
|
5月前
|
存储 Oracle Java
java零基础学习者入门课程
本课程为Java零基础入门教程,涵盖环境搭建、变量、运算符、条件循环、数组及面向对象基础,每讲配示例代码与实践建议,助你循序渐进掌握核心知识,轻松迈入Java编程世界。
439 0
|
5月前
|
负载均衡 Java API
grpc-java 架构学习指南
本指南系统解析 grpc-java 架构,涵盖分层设计、核心流程与源码结构,结合实战路径与调试技巧,助你从入门到精通,掌握高性能 RPC 开发精髓。
523 7
|
6月前
|
Java
Java基础学习day08-作业
本作业涵盖Java中Lambda表达式的应用,包括Runnable与Comparator接口的简化实现、自定义函数式接口NumberProcessor进行加减乘及最大值操作,以及通过IntProcessor处理整数数组,实现遍历、平方和奇偶判断等功能,强化函数式编程实践。
98 5
|
6月前
|
Java 程序员
Java基础学习day08
本节讲解Java中的代码块(静态与实例)及其作用,深入介绍内部类(成员、静态、局部及匿名)的定义与使用,并引入函数式编程思想,重点阐述Lambda表达式及其在简化匿名内部类中的应用。
177 5
|
6月前
|
Java
Java基础学习day07-作业
本作业包含六个Java编程案例:1)动物类继承与多态;2)加油卡支付系统;3)员工管理类设计;4)学生信息统计接口;5)USB设备控制;6)家电智能控制。综合运用抽象类、接口、继承、多态等面向对象技术,强化Java基础编程能力。
229 3
|
存储 安全 Java
24、使用 Java 官方教程学习:① 类变量和类方法详解;② 深入介绍 main() 方法
24、使用 Java 官方教程学习:① 类变量和类方法详解;② 深入介绍 main() 方法
226 1
【JAVA学习之路 | 进阶篇】Map接口及其实现类及常用方法
【JAVA学习之路 | 进阶篇】Map接口及其实现类及常用方法
|
Java 测试技术 C++
【JAVA学习之路 | 进阶篇】File类及常用方法
【JAVA学习之路 | 进阶篇】File类及常用方法