JVM工作原理与实战(七):类的生命周期-初始化阶段

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: JVM作为Java程序的运行环境,其负责解释和执行字节码,管理内存,确保安全,支持多线程和提供性能监控工具,以及确保程序的跨平台运行。本文主要介绍了类的生命周期、类的初始化阶段等内容。

一、类的生命周期

类的生命周期描述了一个类加载、连接、初始化、使用、卸载的整个过程。


1.加载(Loading)

加载阶段是类的生命周期的起始点。当应用程序首次需要使用某个类时,Java虚拟机(JVM)会负责加载这个类。加载是通过类的加载器(ClassLoader)完成的,它会查找并加载类的二进制数据。这个过程包括将类的字节码从文件系统、JAR文件或网络加载到内存中。

2.连接(Linking)

连接阶段是加载阶段的后续,它包括验证、准备和解析三个子阶段。

  • 验证(Verification):验证阶段主要是确保被加载的类文件数据符合JVM规范,没有安全方面的隐患,以及是否与应用程序的其它部分兼容。验证过程包括文件格式验证、元数据验证、字节码验证和符号引用验证。
  • 准备(Preparation):准备阶段是为类的静态变量分配内存,并设置默认的初始值。需要注意的是,准备阶段并不会执行任何初始化操作。
  • 解析(Resolution):解析阶段是将符号引用转换为直接引用。在Java中,符号引用是一个类的全限定名,而直接引用是一个直接指向内存中的地址的指针。解析阶段发生在运行时,而不是编译时。

3.初始化(Initialization)

初始化阶段是类加载过程中的最后一步,当准备和解析阶段完成后,JVM会执行类的构造器方法,这个方法是由编译器自动收集类中的所有类变量的赋值动作和静态代码块集合来的。需要注意的是,构造器方法中的代码只在类被首次使用时执行一次。

4.使用(Using)

一旦类被成功加载、连接并初始化后,就可以被实例化并用于执行应用程序的业务逻辑。在应用程序运行期间,类可能会被频繁地使用。

5.卸载(Unloading)

当应用程序不再需要某个类时,该类的实例以及与其相关的资源将会被回收,这个过程就是卸载。但是需要注意的是,只有当一个类不再被任何活动对象所引用时,它才会被卸载。另外,JVM的垃圾回收机制(Garbage Collection, GC)负责自动处理类的卸载和资源的回收。

二、初始化阶段

1.初始化阶段分析

初始化阶段是类生命周期中的关键阶段之一,主要涉及静态代码块的执行和静态变量的初始化。这个阶段是在类首次被加载到内存中或者在首次实例化这个类的对象时发生的。

在初始化阶段,会执行静态代码块中的代码,这些代码块在类定义中以static声明的部分。这些代码块仅在类首次被加载时执行一次,主要用于执行一些仅需在类加载时进行的初始化操作,如静态变量的赋值等。

此外,初始化阶段会执行字节码文件中clinit部分的字节码指令。clinit方法是Java编译器自动生成的特殊方法,用于执行所有类级别的初始化操作,包括对静态变量的赋值、静态代码块的执行等。clinit方法的执行顺序与Java源代码中编写的顺序一致,确保了按照源代码的顺序进行初始化。

案例:

public class Demo1 {
    public static int value = 1;
    static {
        value = 2;
    }
    public static void main(String[] args) {
    }
}

image.gif

字节码信息:


<init> 构造方法
main Main方法
<clinit> 初始化阶段执行

clinit部分的字节码指令:

image.gif

0 iconst_1
1 putstatic #2 <init/Demo1.value : I>
4 iconst_2
5 putstatic #2 <init/Demo1.value : I>
8 return

image.gif

指令解析:

iconst_1 将常量1放入操作数栈
putstatic #2 <init/Demo1.value : I> 从操作数栈中获取值设置到静态变量中

初始化步骤:

image.gif

2.类的初始化触发方式

类的初始化可以通过以下几种方式触发:

  • 访问类的静态变量或静态方法。当程序首次访问类的静态变量或静态方法时,会触发类的初始化。需要注意的是,如果静态变量是final修饰的并且等号右边是常量,则不会触发初始化。

案例:

public class Demo1 {
    public static void main(String[] args) {
        int i = Test.i;
        System.out.println(i);
    }
}
class Test{
    static {
        System.out.println("初始化");
    }
    public static int i = 0;
}

image.gif

添加-XX:+TraceClassLoading 参数可以打印出加载并初始化的类。

-XX:+TraceClassLoading

image.gif

image.gif

image.gif

运行结果:

image.gif

  • 调用Class.forName(String className)方法。这个方法用于动态加载类,当调用该方法时,会触发类的初始化。

Class.forName(String className) 源码:

@CallerSensitive
    public static Class<?> forName(String var0) throws ClassNotFoundException {
        Class var1 = Reflection.getCallerClass();
        return forName0(var0, true, ClassLoader.getClassLoader(var1), var1);
    }
    @CallerSensitive
    public static Class<?> forName(String var0, boolean var1, ClassLoader var2) throws ClassNotFoundException {
        Class var3 = null;
        SecurityManager var4 = System.getSecurityManager();
        if (var4 != null) {
            var3 = Reflection.getCallerClass();
            if (VM.isSystemDomainLoader(var2)) {
                ClassLoader var5 = ClassLoader.getClassLoader(var3);
                if (!VM.isSystemDomainLoader(var5)) {
                    var4.checkPermission(SecurityConstants.GET_CLASSLOADER_PERMISSION);
                }
            }
        }
        return forName0(var0, var1, var2, var3);
    }
    private static native Class<?> forName0(String var0, boolean var1, ClassLoader var2, Class<?> var3) throws ClassNotFoundException;

image.gif

boolean var1参数表示是否初始化这个类:


案例:

public class Demo1 {
    public static void main(String[] args) throws ClassNotFoundException {
        Class<?> demo2 = Class.forName("Test.Demo2");
    }
}
class Demo2 {
    static {
        System.out.println("初始化");
    }
}

image.gif

运行结果:


  • 创建一个类的对象。当使用new关键字创建一个类的对象时,会触发类的初始化。
  • 执行Main方法的当前类。当Java应用程序的入口点是Main方法时,会自动触发当前类的初始化。

案例:

public class Demo3 {
    static {
        System.out.println("Demo3初始化");
    }
    public static void main(String[] args) throws ClassNotFoundException {
        new Demo4();
    }
}
class Demo4{
    static {
        System.out.println("Demo4初始化");
    }
}

image.gif

运行结果:


3.不执行初始化指令的特殊情况

在字节码层面,初始化阶段是通过执行clinit方法来完成的。然而,值得注意的是,clinit方法并不是在所有情况下都会出现。以下是一些特定情况下不会执行clinit方法的情况:

  • 如果类中既没有静态代码块,也没有对静态变量进行赋值的语句,那么在加载类时不会执行clinit方法。这是因为clinit方法的目的是执行类级别的初始化操作,而在这种情况下,没有需要进行的初始化工作。

案例:

public class Demo1 {
    public static void main(String[] args) {
        
    }
}

image.gif

字节码信息:


  • 如果类中有静态变量的声明,但没有对其进行赋值的语句,同样不会执行clinit方法。静态变量的初始化需要通过赋值语句来进行,如果只是声明但没有赋值,则不会被初始化。

案例:

public class Demo1 {
    public static int i;
    public static void main(String[] args) {
    }
}

image.gif

 字节码信息:


  • 如果静态变量的定义使用了final关键字,这类变量会在准备阶段直接进行初始化,因此在类加载时不会执行clinit方法。这是因为final修饰的静态变量在编译时就已经确定了值,不需要在运行时再进行初始化。

案例:

public class Demo1 {
    public static final int i = 1;
    public static void main(String[] args) {
    }
}

image.gif

 字节码信息:


4.继承关系下的初始化阶段

在继承关系下,类的生命周期中的初始化阶段变得更为复杂。子类和父类之间的初始化交互是理解这个过程的关键。

  • 直接访问父类的静态变量:在Java中,静态变量是类级别的变量,它们不属于任何一个对象实例,而是与类本身关联。当我们在子类中直接访问父类的静态变量时,只会触发父类的初始化,而不会触发子类的初始化。这是因为静态变量的初始化是类级别的操作,与子类的实例化过程无关。这种机制确保了父类静态变量的初始化在子类中使用之前已经完成。
  • 子类的初始化clinit调用之前,会先调用父类的clinit初始化方法:在Java字节码层面,类的初始化是通过执行clinit方法完成的。当一个子类被初始化时,它的clinit方法会被调用。但在子类的clinit方法执行之前,父类的clinit方法会被先调用。这种顺序确保了父类中的静态变量和静态代码块在子类使用之前已经完成初始化。这种机制确保了继承关系下的正确初始化顺序,使得子类可以依赖于父类中定义的静态变量和静态代码块。

总结

JVM是Java程序的运行环境,负责字节码解释、内存管理、安全保障、多线程支持、性能监控和跨平台运行。本文主要介绍了类的生命周期、类的初始化阶段等内容,希望对大家有所帮助。

相关文章
|
1月前
|
存储 监控 算法
美团面试:说说 G1垃圾回收 底层原理?说说你 JVM 调优的过程 ?
尼恩提示: G1垃圾回收 原理非常重要, 是面试的重点, 大家一定要好好掌握
美团面试:说说 G1垃圾回收 底层原理?说说你 JVM 调优的过程  ?
|
2月前
|
安全 Java 应用服务中间件
JVM常见面试题(三):类加载器,双亲委派模型,类装载的执行过程
什么是类加载器,类加载器有哪些;什么是双亲委派模型,JVM为什么采用双亲委派机制,打破双亲委派机制;类装载的执行过程
JVM常见面试题(三):类加载器,双亲委派模型,类装载的执行过程
|
28天前
|
监控 架构师 Java
JVM进阶调优系列(6)一文详解JVM参数与大厂实战调优模板推荐
本文详述了JVM参数的分类及使用方法,包括标准参数、非标准参数和不稳定参数的定义及其应用场景。特别介绍了JVM调优中的关键参数,如堆内存、垃圾回收器和GC日志等配置,并提供了大厂生产环境中常用的调优模板,帮助开发者优化Java应用程序的性能。
|
1月前
|
存储 监控 算法
JVM调优深度剖析:内存模型、垃圾收集、工具与实战
【10月更文挑战第9天】在Java开发领域,Java虚拟机(JVM)的性能调优是构建高性能、高并发系统不可或缺的一部分。作为一名资深架构师,深入理解JVM的内存模型、垃圾收集机制、调优工具及其实现原理,对于提升系统的整体性能和稳定性至关重要。本文将深入探讨这些内容,并提供针对单机几十万并发系统的JVM调优策略和Java代码示例。
48 2
|
1月前
|
SQL 缓存 Java
JVM知识体系学习三:class文件初始化过程、硬件层数据一致性(硬件层)、缓存行、指令乱序执行问题、如何保证不乱序(volatile等)
这篇文章详细介绍了JVM中类文件的初始化过程、硬件层面的数据一致性问题、缓存行和伪共享、指令乱序执行问题,以及如何通过`volatile`关键字和`synchronized`关键字来保证数据的有序性和可见性。
28 3
|
1月前
|
缓存 前端开发 Java
JVM知识体系学习二:ClassLoader 类加载器、类加载器层次、类过载过程之双亲委派机制、类加载范围、自定义类加载器、编译器、懒加载模式、打破双亲委派机制
这篇文章详细介绍了JVM中ClassLoader的工作原理,包括类加载器的层次结构、双亲委派机制、类加载过程、自定义类加载器的实现,以及如何打破双亲委派机制来实现热部署等功能。
42 3
|
1月前
|
小程序 Oracle Java
JVM知识体系学习一:JVM了解基础、java编译后class文件的类结构详解,class分析工具 javap 和 jclasslib 的使用
这篇文章是关于JVM基础知识的介绍,包括JVM的跨平台和跨语言特性、Class文件格式的详细解析,以及如何使用javap和jclasslib工具来分析Class文件。
41 0
JVM知识体系学习一:JVM了解基础、java编译后class文件的类结构详解,class分析工具 javap 和 jclasslib 的使用
|
1月前
|
前端开发 Java 应用服务中间件
JVM进阶调优系列(1)类加载器原理一文讲透
本文详细介绍了JVM类加载机制。首先解释了类加载器的概念及其工作原理,接着阐述了四种类型的类加载器:启动类加载器、扩展类加载器、应用类加载器及用户自定义类加载器。文中重点讲解了双亲委派机制,包括其优点和缺点,并探讨了打破这一机制的方法。最后,通过Tomcat的实际应用示例,展示了如何通过自定义类加载器打破双亲委派机制,实现应用间的隔离。
|
4月前
|
运维 监控 Java
(十)JVM成神路之线上故障排查、性能监控工具分析及各线上问题排错实战
经过前述九章的JVM知识学习后,咱们对于JVM的整体知识体系已经有了全面的认知。但前面的章节中,更多的是停留在理论上进行阐述,而本章节中则更多的会分析JVM的实战操作。
107 1
|
3月前
|
存储 监控 算法
深入解析JVM内部结构及GC机制的实战应用
深入解析JVM内部结构及GC机制的实战应用