类的生命周期

本次主要介绍,JVM中类的生命周期。以下内容主要还是参考《Inside JVM》

类加载、链接和初始化

java虚拟机通过加载的过程,连接和初始化使类可以在程序中运行。加载的过程就是引入一种二进制形式的java虚拟机,应该就是查找一个class文件。连接过程是二进制类型的数据在虚拟机中运行的状态。连接过程分为三步:验证,准备,解析。验证是为了确保形成的类型是正确和合适java虚拟机使用的。准备是需要分配内存所需的类型,比如内存中的任何内变量。解析是把类中的符号引用转化成常量池中的直接引用。实现可能会推迟解析步骤直到每个符号引用都被运行中的程序引用。在验证,准备,解析完成后,就准备初始化类型类。在初始化的过程中,类变量就会初始化自己的值。

Java虚拟机规范给出了实现灵活的类和接口的时间加载和链接,但严格定义初始化的时间。都必须先初始化每个类和实现接口。一个活跃使用的类:

  • 调用类的构造函数的一个新实例;
  • 创建一个数组的类作为一个元素类型;
  • 调用一个方法声明的类(而不是从超类继承);
  • 使用或转让字段声明的类(不是继承超类或超接口),除了静态和final类型,并初始化一个编译时常量表达式;
    一个活跃的使用一个接口:
  • 使用或转让字段声明的接口(而不是继承自一个超接口),除了字段初始化的一个编译时常量表达式。

除了自己初始化使用,还有一个情况会导致类的初始化:就是初始化自己的子类的时候。类的初始化前需要初始化自己的父类。
然而相同的接口是不一样的,接口初始化是通过不定的场景使用,不会因为子接口或者类实现该接口而需要初始化。因此,初始化类时需要初始化它的父类,而不是初始化它实现的父接口。初始化接口不需要初始化父接口。

加载

加载过程包含以下三步:

  • 产生一个二进制数据流来表示类型;
  • 将二进制数据流解析成内部数据结构的方法;
  • 创建java.lang.class的实例。
    二进制数据流可以支持java类文件格式,也支持其它的文件格式。

Java虚拟机规范没有说如何的二进制数据类型必须生产。一些潜在的方法产生的二进制数据类型是:

  • 从本地文件系统中加载一个java类文件;
  • 从网络中下载一个java类文件;
  • 从zip,jar,cab或者其它归档文件中提取一个java类文件;
  • 从专有数据库中提取一个java类文件;
  • 把一个动态的java源文件编译成类文件格式;
  • 动态的计算类文件中的文件数据类型;
  • 除了java类文件外使用二进制文件格式的。

鉴于二进制数据类型,java虚拟机必须很大程度上处理这些数据以便它可以创建一个java.lang.Class的实例。虚拟机解析二进制数据到具体实现相关的内部数据结构。类实例,最终产生的步骤,作为一个接口程序和内部数据结构。访问存储在内部数据结构的类型,程序需调用在类实例的方法类型。
类加载器不需要等到一个类型第一主动使用之前加载类型,类加载器允许缓存二进制类型,加载的类型会最终提前使用,或在相关的组中加载类型。如果在类加载初期类加载器遇到问题,它必须报告这个问题,只优先使用可用的类型。换句话说,在加载初期如果一个类加载器遇到一个缺失或畸形类文件,必须等到第一个主动使用的程序报告错误。如果类没有被程序使用,类加载器不会报告错误。

验证

加载类型后,就准备进入连接阶段了。连接过程的第一步是验证,确保遵循Java语言的语义类型,也没有违反虚拟机的完整性。
Java虚拟机有一些灵活性的实现是验证的另一个领域。设计者可用决定如何何时验证类型。Java虚拟机规范列出所有虚拟机能够抛出的异常,在生命情况下必须抛出异常。 无论什么样的虚拟机都有可能遇到麻烦,就应该抛出一个错误或者异常。在某些情况下,Java虚拟机规范说什么时候应该抛出异常或错误,但通常不确切地确定或发生错误时应该被检测到。
然而,某些种类的检查很有可能发生在大多数Java虚拟机实现的特定时间。例如,在加载过程中,虚拟机必须解析二进制数据表示和构建内部数据结构类型。在这一点上,某些检查必须做的只是确保解析二进制数据的初始化就不是检查虚拟机崩溃。在解析过程中,实现可能会检查二进制数据,以确保它预期的整体格式。检查java类文件格式必须检查幻数,确保每个组件都是在正确的位置和正确的长度,确认文件不是太短或太长,等等。虽然这些检查加载期间,发生在连接的验证阶段之前,它们仍是逻辑上验证阶段的一部分。
另一个可能发生在加载的检查是确保每个类对象有一个超类除外。这样做可能会在加载虚拟机加载一个类时,它还必须确保所有加载类的超类。虚拟机唯一能知道给定类的超类的名字是当前类的二进制数据。由于虚拟机在加载期间都会寻找每个类是否都是超类,这个检查也可能在加载阶段。
另一个检查可能发生在大多数实现——官方验证阶段之后的验证是象征性的引用。动态链接的过程涉及到定位类、接口、字段和方法被引用存储在常量池中,象征和符号引用替换为直接引用。当虚拟机搜索一个象征性地引用实体(类型、字段或方法),它必须首先确保实体存在。如果虚拟机发现实体存在,它必须进一步检查有权限访问的实体的引用类型。这些检查存在性和访问权限逻辑验证的一部分,第一阶段的链接,但最有可能发生在分辨率、链接的第三阶段。解析本身可以推迟到第一个使用每个符号引用的程序,所以这些检查初始化后可能发生。

准备

在Java虚拟机加载一个类和执行任何验证它,该类就进入准备阶段。在准备阶段,Java虚拟机会分配内存变量并设置它们的默认初始值。类变量没有初始化为适当的初始值,直到初始化阶段。(没有执行Java代码在准备步骤。)在制备过程中,Java虚拟机类的新分配的内存变量设置为默认值取决于变量的类型。各种类型的默认值如:

解析

通过连接的前两个阶段:验证和准备,这是准备连接的第三个和最后一个阶段:解析。解析的过程是定位类、接口、字段和方法引用从一个类型是常量池,象征性地和这些符号引用替换为直接引用。正如上面提到的,这个阶段的连接是可选的(除非)直到每个符号引用是第一个使用的程序。

实例化

最后一步需要准备好一个类或接口首次主动使用初始化,设置类变量的过程中适当的初始值。这里使用“适当的”初始值是程序员给出的一个类变量所需的起始值。在准备阶段中给类变量一个合适的初始值与默认初始值。如前所述,虚拟机分配默认值仅基于每个变量类型。相比之下,基于一些总体规划适当的初始值只有程序员知道。

原文地址:https://www.cnblogs.com/levcon/p/9794323.html