JVM

类的生命周期

2021-07-11  本文已影响0人  程序员阿奇

Java 虚拟机为java程序提供运行时环境,其中一项重要的任务就是管理类和对象的生命周期。类的生命周期从类被加载、连接、和初始化开始,到类被卸载结束。当类处于生命周期时,它的二进制数据位于 方法区内,在堆区内还会有一个相应的描述这个类的Class对象。只有当类处于生命周期时,java程序才能使用它,比如调用了类的静态属性和方法,或者创建类的对象。

Java虚拟机及程序的生命周期

当通过java命令运行一个java程序时,就启动了一个java进程。java虚拟机进程从启动到终止的过程,称为java虚拟机的生命周期。在一下情况下,java虚拟机将结束生命周期。

  1. 程序正常执行结束。
  2. 程序在执行中因出现异常或错误而导致异常终止。
  3. 执行了System.exit()方法。

当Java虚拟机处于生命周期时,它的总任务就是运行java程序。Java程序从开始运行到终止的过程称为程序的生命周期,它和Java虚拟机的生命周期的一致的。

类的加载、连接和初始化

当Java程序使用某个类时,java虚拟机会确保这个类已经被加载、连接和初始化。过程如下:

  1. 加载:查找并加载类的二进制数据。
  2. 连接:包括验证、准备和解析类的二进制数据。
    验证:确保被加载类的正确性。
    准备:为类的静态变量分配内存,并将其初始化为默认值。
    解析:把类中的符号引用转换为直接引用。
  3. 初始化 : 给类的静态变量赋予正确的初始值。
    时机:所有的java虚拟机实现必须在每个类或接口被java程序 ”首次使用注定使用“ 时才初始化它们。
    java 程序对类的使用方式可以分为两种:主动使用 和 被被动使用。
类的加载:

类的加载是指把类的.class 文件中的二进制数据读入到内存中,把它存放在运行时数据区的方法区内,然后在堆中创建一个java.lang.Class对象,用来封装类在方法区内的数据结构。

Java虚拟机能够从多种来源加载二进制数据,包括:

  1. 从本地文件系统加载类的class文件。
  2. 通过网络下载类的.class文件。
  3. 从ZIP JAR 或其他类型的归档文件中提取.class文件。
  4. 把一个java源文件动态编译为.class文件。

类的加载最终产品是位于运行数据区的堆区的Class对象。Class对象封装了类在方法区内的数据结构,并且向java程序提供了访问类在方法区内的数据结构接口 : Class 对象。

类加载器并不需要等到某个类被 ”首次主动使用“ 时再加载它,java 虚拟机规范允许类加载器在预料某个类将要被加载使用时就预先加载它,如果在预先加载过程中遇到.class文件缺失或者存在错误,类加载器必须等到程序首次主动使用该类时才会报告错误(抛出LInkageError)。如果该类一直未被使用,那么类加载器将不会报告错误。

类的验证:

当类被加载后,就进入连接阶段。连接就是把已经读入到内存的类的二进制数据合并到虚拟姐的运行时环境中去。连接的第一步是类的验证,确保被加载的类有正确的内部结构,并且与其它类协调一致。如果Javaa虚拟机检查到错误,那么就会抛出相应的Error对象。

类的验证主要包括:

  1. 文件结构的检查:确保文件遵循java;类文件的固定格式。
  2. 语义检查:确保类本身符合java语言的语法规定,比如验证final类型的类没有子类,以及final类型的方法没有被覆盖。
  3. 字节码验证:确保字节码流可以被java虚拟机安全的执行。字节码流代表Java方法(包括静态方法和实例方法),它是由被称作操作字节码的单字节指令组成的序列,每一个操作码后边都跟着一个或多个操作数。字节码验证步骤会检查每个操作码是否合法,即是否有着合法的操作数。
  4. 二进制兼容的验证:确保相互引用的类之间协调一致。
类的准备:

在准备阶段,java虚拟机为类的静态变量分配内存,并设置默认的初始值。

类的解析:

在解析阶段,Java虚拟机会把类的二进制数据中的符号引用替换成直接引用。

类的初始化:

在初始化阶段,Java虚拟机执行类的初始化语句,为类的静态变量赋予初始值。
在程序中,静态变量初始化的途径有两个:

  1. 在静态变量的声明处进行初始化。
  2. 在静态代码块中进行初始化。
java虚拟机初始化一个类包含以下步骤:
  1. 假如这类还没有被加载和链接,那就先进行加载和连接。
  2. 假如类存在直接父类,并且这个父类还没有被初始化,那就先初始化直接的父类。
  3. 假如类中存在初始化语句,那就依次执行这些初始化语句。
类的初始化时机:

java虚拟机只有在程序首次主动使用一个类或接口时才会初始化。只有6种活动被看作是程序对类或接口的主动使用:

  1. 创建类的实例:创建类的实例包括:用new语句床就按实例,或者通过反射,克隆以及反序列化手段来创建实例。
  2. 调用类的静态方法。
  3. 访问某个类或接口变量,或者对该静态变量赋值。
  4. 调用 JAVA API 某些反射方法,比如 Class.forName("Test")方法,假如Test类还没初始化,那么forName()方法就会初始化Test类,然后返回代表这个Test类的Class实例。forName()方法是java.lang.Class类的静态方法。
  5. 初始化一个类的子类可以看作是对它父类的主动使用,因此会先初始化父类。
  6. Java虚拟机启动时被标明启动类的类,如 ”java Test“ 命令,Tes类就是启动类,java虚拟机会先初始化它。
除了以上6种情形,其它使用Java类的方式都被看作是被动使用,都不会导致类的初始化。
  1. 对于final 类型的静态变量,如果在编译时就能计算出变量的值,那么这种变量被看作编译时常量。java程序中对类的编译时常量的使用,被看作是对类的被动调用,不会导致类的初始化。
  2. 对于 final 类型的静态变量,如果不能在编译时计算出变量的取值,那么程序对类的这种变量的使用,被看作是对类的主动使用,会导致类的初始化。
  3. 当Java虚拟机初始化一个类时,要求它的所有父类都已经被初始化,接口除外。
    (1) 在初始一个类时,并不会先初始化它所实现的接口。
    (2) 在初始化一个接口时,并不会先初始化它的父接口。
    因此,一个接口并不会因为它的子接口或者实现类的初始化而初始化。只有当程序首次使用特定接口的静态变量时,才会导致类的初始化。
  4. 只有当程序访问的静态变量或者静态方法的确在当前类或者接口定义时,才可以看作是对类或接口的主动使用。
  5. 调用ClassLoader类的loadClass()方法加载一个类,并不是对类的主动使用,不会导致类的初始化。
上一篇下一篇

猜你喜欢

热点阅读