JVM 类的初始化

作者: 撸代码的大白 | 来源:发表于2020-03-19 20:14 被阅读0次

按理解描述,欢迎指正。

一、类的生命周期

编译阶段:java源文件经过编译(javac)编译为符合jvm规范的二进制字节码。

加载阶段:classLoader将.class文件加载到内存。有几种方式:从本地记载、从网络加载、从zip、jar包加载、从数据库加载、动态编译(动态代理、jsp)

连接阶段:分三个阶段,分别为:

                校验:类文件的结构检查、语义检查、字节码验证、二进制兼容性验证

                准备:为静态变量分配内存,并赋初始化值

                解析:将符号引用转换成直接引用

初始化阶段:假如类还没有加载和连接,那先加载和连接。如果类存在直接父类,直接父类没有初始化,先初始化直接父类。如果类存在初始化语句,按顺序执行初始化语句。但是接口例外,类初始化时不会初始化接口,接口初始化时不会初始化父接口。使用classLoader的loadClass方法不初始化类,但是forName会初始化类。注意,静态代码只会执行一次,因为只会初始化一次。

使用阶段:待补充

卸载阶段:待补充

二、什么时候初始化。

规则:类或者接口,只在被首次主动使用时,才被初始化。

主动使用有几种情况:创建类的实例、访问或者修改类或者接口的静态变量、访问类的静态方法、Class.forName()、虚拟机启动时的启动类、java.lang.invoke.MethodHandle实例的解析结果REF_getStatic、REF_putStatic、REF_invokeStatic句柄对应的没有初始化的类。

下面举几个特殊的例子,加深一下印象。

1、类只有被首次主动使用才初始化,后续的使用不会再一次初始化

示例1:



这段代码的输出为: TestParent init ,不会输出两次

2、final修饰的变量,如果在编译期间能确定,则会将常量值存放到使用类的常量池内

示例2:


  这个代码非常简单。运行结果只输出了5。原因是:TestParent类没有初始化,所以静态代码块不会执行,a是确定是常量,编译阶段已经在Dabai这个类的常量池内了。甚至于jvm都没有把TestParent加载到jvm内。因为编译结束后,TestParent.a已经变成了5。所以Dabai并没有任何TestParent的相关信息,也没有主动使用TestParent。

将上面代码内的final去掉,那么就会打印TestParent init 和 5。原因是类被首次主动使用、需要初始化。

如果将a的值变为动态的,那么也会打印TestParent init 和 5。原因为:编译期间确定不了常量的值,推迟到初始化阶段。如下:

3、直接使用才会初始化

实例3:

这段代码最后会输出:TestParent init 和 1。原因是虽然是用子类的引用访问父类的变量,但是对字类没有直接使用。所以对TestChild没有初始化。但是会对TestChild进行加载。原因是虽然没被主动使用,JVM预测TestChild可能会被使用,那么就会加载这个类。如果TestChild.class文件缺失、或者内部存在错误,那么会在首次主动使用这个类时报错。如果这个类一直没被主动使用,类加载器一直不会报错。

4、数组不会对其内元素的类型进行主动使用

示例4:

这段代码会输出:class [Lcom.inspur.eap.datasource.rest.TestParent;

原因是:数组没有对TestParent主动使用,所以TestParent没有初始化,而是动态生成了一个 [Lcom.inspur.eap.datasource.rest.TestParent类。父类为Object。

5、连接阶段中准备阶段给静态变量分配空间并赋予初始值。初始化阶段,按顺序执行初始化代码。

实例5:


这段代码的输出为:1  0。 原因是,Dabai对TestParent进行了首次主动使用,所以TestParent被加载、连接、初始化。连接中准备阶段,num1被赋值0,num2被赋值0。然后初始化阶段,按顺序,第一行执行完之后num1仍然为0,第二行执行完之后num1=1,num2=1,第七行执行完,num2=0。所以输出为0。另外num2在构造方法后声明不会导致初始化报错,因为连接阶段num2已经存在。

6、类和接口初始化的差异。因为接口内的变量默认时 public static final 的,所以必须动态赋值才能体现初始化。

示例6-1

输出结果为:TestParent init          TestChild init           1。原因是,主动使用了TestChild,所以需要初始化TestChild,但他的直接父类TestParent还没初始化,所以先初始化TestParent。

示例6-2:


输出结果为: TestChild init        1。原因是:类初始化,但不会初始化接口。

示例6-3:


输出结果:1。原因:接口内的变量默认是 public static final 的,那么a就是个常量,在编译期就被放在常量池里了,所以都没初始化,且都没加载。

示例6-4:


输出结果: TestChild init   4(10以内的数字)。原因,首先父接口没有初始化,其次子接口初始化了。

7、loadClass 和 forName

示例7-1:


这段代码没有任何输出。原因是loadClass不算主动使用TestParent类。

示例7-2:


输出结果:TestParent init 。原因:forName是对类的主动使用(具体细节以后补充)

先分享这么多,后面再分享类加载器的一些内容。

相关文章

  • Java类加载机制与反射

    1. 类的加载、连接和初始化 1.1 JVM和类 当调用java程序时,会启动一个JVM进程 JVM中止的情形: ...

  • 我的秋招之路-面经篇

    Java基础 类加载的时机和类初始化的时机(引出tomcat类加载器)JVM和绝大多数用户自定义的类在JVM启动的...

  • JVM类的初始化顺序

    当new一个类对象时,如果该类还未被JVM所加载,则JVM首先加载该类,加载类流程中会有类的初始化操作,也就是类中...

  • 2020最新JAVA核心面试知识整理283页(带详解)

    部分目录预览 部分内容预览 JVM 类加载机制 JVM 类加载机制分为五个部分:加载,验证,准备,解析,初始化,下...

  • [JVM系列]对于JVM最生动的总结,一个类的一生

    一个故事,表达了一个类,一个对象,从出生,到分配,到结束的过程。涉及到JVM初始化→JVM类加载→JVM实例化→J...

  • JVM中的类加载与双亲委派机制

    一个类想要在JVM中使用,势必要经过加载、验证、准备、解析、初始化等步骤。而JVM的所有类加载,都是通过一个叫类加...

  • JVM类加载-小探

    类加载 jvm把描述类的数据从class文件加载到内存,并对数据进行校验、转换解析及初始化,最终形成可以被jvm直...

  • 类加载原理分析&动态加载Jar/Dex

    简书 编程之乐转载请注明原创出处! JVM类加载机制 JVM类加载机制分为五个部分:加载,验证,准备,解析,初始化...

  • JVM类生命周期概述:加载时机与加载过程

    写在前面 本文概述了JVM加载类的时机和生命周期,并结合典型案例重点介绍了类的初始化过程,揭开了JVM类加载机制的...

  • JVM 类的初始化

    按理解描述,欢迎指正。 一、类的生命周期 编译阶段:java源文件经过编译(javac)编译为符合jvm规范的二进...

网友评论

    本文标题:JVM 类的初始化

    本文链接:https://www.haomeiwen.com/subject/ynplyhtx.html