java虚拟机为java程序提供运行时环境,其中一项重要的任务就是管理类和对象的生命周期,类的生命周期从类被加载、连接和初始化开始,到类被卸装结束。当类处于生命周期中时,它的二进制数据位于运行时数据区的方法区,同时在运行时数据区还会实例化java.lang.Class类的对象(并没有明确规定是在java堆中),只有当类处于生命周期中时,java程序才能使用它,比如调用类的静态属性和方法,或者创建类的实例。

 

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

 

类的加载、连接和初始化

当java程序需要使用某个类的时,java虚拟机会确保这个类已经被加载、连接和初始化。其中连接又包括验证、准备和解析这3个子步骤:

1、加载:查找并加载类的二进制数据

2、连接:包括验证、准备和解析类的二进制数据

    a)、验证:确保被加载类的正确性

    b)、准备:为类的静态变量分配内存,并将其初始化为其类型对应的默认值

    c)、解析:把类中的符号引用转换为直接引用

3、初始化:为类的静态变量赋予正确的初始值

 

在类或接口被加载和连接的时机上,java虚拟机规范给实现提供了一定的灵活性,但是它严格定义了初始化的时机,所有的java虚拟机实现在每个类或接口被java程序“首次主动使用”时才初始化它们。java程序对类的使用方式可分为俩种:主动使用和被动使用。

 

类的加载

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

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

 

类的加载是由类加载器完成的,类的加载器分为两种:

java虚拟机自带的加载器:

    根类加载器(Bootstarp):使用C++编写,程序员无法在java代码中获得该类

    扩展类加载器(Etension):使用java代码实现

    系统加载器(System):应用加载器,使用java代码实现

用户自定义的类加载器:

    java.lang.ClassLoader的子类

    用户可以定制类的加载方式 

 

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

 

类的验证

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

 

类的准备

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

 

类的解析

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

 

类的初始化

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

1、在静态变量的声明处进行初始化

2、在静态代码块中进行初始化

java虚拟机会按照初始化语句在类文件中的先后顺序来依次执行它们。

 

java虚拟机初始化一个类包含以下步骤:

1、假如这个类还没有被加载和连接,那就先进行加载和连接

2、假如类存在直接的父类,并且这个父类还没有被初始化,那就先初始化直接父类

3、假如类中存在初始化语句,那就依次执行这些初始化语句

当初始化一个类的直接父类时,也需要重复以上步骤,这会确保当程序主动使用一个类时,这个类以及所有父类(包括直接父类和间接父类)都已经初始化,程序中第一个被初始化的类是Object类。

 

类的初始化时机

java虚拟机只有在程序首次主动使用一个类或接口时才会初始化类。

只有6种活动被看作是程序对类或接口的主动使用。

a)、创建类的实例。包括用new关键字创建实例或者通过反射、克隆以及反序列化手段来创建

b)、调用类的静态方法

c)、访问某个类或接口的静态变量,或者对静态变量赋值

d)、调用java api中某些反射方法,Class.forName()是java.lang.Class的静态方法

e)、初始化一个类的子类

f)、java虚拟机启动时被标明为启动类的类

除了以上6中情形,其他使用java类的方式都被看做是被动使用,都不会初始化。下面结合具体的例子类解释类的初始化时机

1、对与final类型的静态变量,如果在编译时就能计算出变量的值,这种变量被看做是编译时常量。    java程序中对类的编译时常量的使用,被看做是对类方法的被动使用,不会导致类的初始化

2、对于final类型的静态变量,如果在编译时不恩能够计算出变量的值,那么程序对类的这种变量的使用,被看做是对类的主动使用,会导致类的初始化。

3、当java虚拟机初始化一个类时,要求它的所有父类都已经被初始化,但是这条规则不适用于接口

    在初始化一个类时,并不会先初始化它所实现的接口

    在初始化一个接口时,并不会先初始化它的父接口

因此,一个父接口并不会因为它的子接口或者实现类的初始化而初始化。只有当程序首次使用特定接口的静态变量时,才会导致该接口的初始化。

4、只有当程序访问的静态变量或静态方法的确在当前类或接口中定义时,才可看做是对类或接口的主动使用。

5、调用ClassLoader类的loadClass()方法加载一个类,并不是对类的主动使用,不会导致类的初始化。但当程序调用Class类的静态方法------forName(“ClassA”)方法显示初始化ClassA时,才是ClassA的主动使用,将导致ClassA被初始化。