引言
当在简历上写上了解JVM的知识时,类的生命周期这个知识点就有很大可能要被问,所以了解好这个知识点很重要。这个栏目也会继续讲其他JVM的常见知识点,感兴趣的同学可以关注一下❤️
类的生命周期🤓
那什么是类的生命周期呢?
Java类的生命周期是指从类被加载到Java虚拟机(JVM)开始,直到它最终被卸载的过程。这个过程包括了几个关键阶段:加载、连接、初始化、使用和卸载
加载阶段😗
在Java类的生命周期中,加载(Loading)是第一步,也是非常关键的一个步骤。它涉及到将编译好的.class文件读入内存,并创建一个java.lang.Class对象的过程。这个阶段主要由类加载器完成,下面详细讲解一下加载阶段的具体内容:
类加载器
Java提供了三种主要类型的类加载器:
- 启动类加载器(Bootstrap Class Loader) :这是最基础的类加载器,用于加载核心Java库中的类,通常位于
$JAVA_HOME/jre/lib目录下或通过参数指定的位置。 - 扩展类加载器(Extension Class Loader) :负责加载Java的扩展类库,一般位于
$JAVA_HOME/jre/lib/ext目录下。 - 应用程序类加载器(Application Class Loader) :也称为系统类加载器,负责加载应用的classpath下的所有类。
此外,还可以有用户自定义的类加载器,它们都是继承自java.lang.ClassLoader类。
加载过程
- 定位和加载类文件:当需要使用某个类时,JVM会委托相应的类加载器去寻找并加载该类的
.class文件。这可能涉及到从本地文件系统、网络或其他数据源获取类文件。 - 读取二进制数据:一旦找到了
.class文件,类加载器就会读取其内容。这些内容是由Java编译器生成的二进制字节码。 - 转换为方法区内的数据结构:读取到的二进制字节码会被解析并转换成运行时数据区的方法区内存结构的一部分。这里包括了类的信息(如类名、父类名)、字段信息、方法信息等。
- 创建Class对象:最后一步是在堆内存中创建一个
java.lang.Class对象,该对象代表正在加载的类,并提供了一个接口,允许程序访问类的数据(例如,通过反射API)。
对于开发者,只需要访问堆中的Class对象,而不需要访问方法区的所有信息,这样做JVM可以很好地控制开发者访问数据的范围
连接阶段😣
在Java类的生命周期中,连接(Linking)阶段紧随加载阶段之后,是确保类能够被正确执行的重要步骤。连接阶段主要分为三个子阶段:验证(Verification)、准备(Preparation)和解析(Resolution)。下面详细讲解每个子阶段:
1. 验证(Verification)
验证阶段的主要目的是确保.class文件的结构是正确的,并且其内容是安全的、符合Java语言规范的。这是为了防止恶意代码对JVM造成破坏或引发不一致的行为。验证过程包括以下几个方面:
- 文件格式验证:检查.class文件是否具有正确的魔数(magic number)、版本号等基本属性,以确认它是一个有效的.class文件。
- 元数据验证:验证字节码描述的类、字段、方法等是否符合Java语言规范,比如确保所有的类都有父类(除了
java.lang.Object),所有抽象方法都有实现等。 - 字节码验证:检查字节码指令序列的有效性,保证它们不会违反JVM的安全规则,例如,不会导致栈溢出或下溢。
- 符号引用验证:这一部分通常在解析阶段进行,但有时也会被视为验证的一部分。它涉及到检查符号引用能否被正确地转换为直接引用。
2. 准备(Preparation)
在准备阶段,JVM会为类的静态变量分配内存,并设置默认初始值。需要注意的是,这里的初始化并不是执行程序中的赋值语句,而是根据数据类型的默认值进行初始化。例如,int类型的静态变量会被初始化为0,对象引用则会被初始化为null。
final修饰的基本数据类型的静态变量,准备阶段会直接将代码中的值直接赋值
此外,准备阶段还可能涉及一些与平台相关的准备工作,例如调整静态变量的存储布局以适应特定的操作系统或硬件架构。
3. 解析(Resolution)
解析阶段负责将类、接口、字段和方法中的符号引用替换为直接引用。符号引用是以字符串形式存在于.class文件中的,它们指向其他类、接口、字段或方法的名字。而在解析过程中,这些符号引用会被替换为实际的内存地址或指针,以便于后续的直接访问。
解析可以分为两类:
- 类或接口解析:当一个类引用了另一个类时,需要确定被引用类的实际位置。
- 字段和方法解析:类似于类或接口解析,但对于字段和方法而言,还需要考虑到继承关系的影响,即如果当前类没有声明某个方法或字段,JVM需要向上查找它的父类直到找到为止。
总的来说,连接阶段通过验证、准备和解析三个步骤,确保了.class文件的正确性和安全性,并为类的初始化做好了必要的准备。这个阶段对于确保Java应用程序的稳定性和性能至关重要
初始化阶段😪
在Java类的生命周期中,初始化(Initialization)阶段是连接阶段之后的一个重要步骤。这个阶段主要涉及到为类的静态变量赋予初始值,并执行静态代码块中的代码。初始化确保了类在首次使用之前已经完成了必要的准备工作。以下是关于初始化阶段的详细讲解:
触发条件
Java类并不是在其加载到JVM时就立即进行初始化的。初始化仅在以下几种情况下触发:
- 创建类的实例,即通过
new关键字创建对象。 - 调用类的静态方法。
- 访问或设置类的静态字段(但如果是final修饰的基本类型或字符串常量,则不会触发初始化,因为它们在编译期就已经确定)。
- 使用反射API对类进行操作,如
Class.forName()。 - 初始化一个类时,如果其父类尚未被初始化,则先初始化其父类。
初始化过程
- 静态变量赋值和静态代码块执行:当上述任一条件满足时,JVM会开始初始化该类。首先,它会执行静态变量的赋值操作以及静态代码块中的代码。这些操作是由编译器自动收集并放入特殊的
<clinit>()方法中的。需要注意的是,静态变量的赋值和静态代码块按照它们在源文件中出现的顺序执行。 - 父类优先原则:如果一个类有直接父类,则必须先初始化其父类,然后才能初始化子类。这保证了继承体系中更基础的部分首先完成初始化。
- 线程安全性:类的初始化过程是线程安全的。如果有多个线程同时尝试初始化同一个类,那么只有一个线程会被允许执行初始化过程,其他线程则需要等待,直到第一个线程完成初始化。
特殊情况
- 接口的初始化:与普通类不同,接口也可能包含静态成员和静态初始化块。然而,接口的初始化规则稍有不同,特别是对于静态成员的处理上。
- 延迟加载:由于类的初始化是在第一次主动使用时才发生的,因此称为“延迟加载”。这种方式有助于提高程序的启动速度,因为它避免了不必要的类初始化工作。
- 构造代码块会比构造方法优先执行
总之,初始化阶段确保了类的所有静态成员都被正确地设置了初始值,并且所有的静态初始化逻辑都得到了执行。这一过程对于确保类的行为符合预期至关重要。理解初始化的时机和机制有助于编写更加高效、稳定的Java程序。
使用阶段😈
经过上面三个阶段,Java类加载并初始化之后,就可以正常使用类的功能了。包括创建对象,调用方法等
卸载阶段😭
天下没有不散的宴席。
当某个类相关的Class对象不再被引用,即没有实例、静态变量或任何地方引用该类时,这个类就可能被卸载。类的卸载由垃圾回收机制决定,当垃圾回收器运行时,若发现某类的Class对象可以被回收,则会进行卸载处理。
如果大家对垃圾回收机制感兴趣,笔者会尽快出相关内容❤️
总结❤️
这就是Java类的生命周期相关内容了
如果你看了这篇文章有收获可以点赞+关注+收藏🤩,这是对笔者更新的最大鼓励!如果你有更多方案或者文章中有错漏之处,请在评论区提出帮助笔者勘误,祝你拿到更好的offer!