下面我将详细讲解Java类加载初始化的过程及顺序。
Java类加载初始化的过程
Java的类加载过程一般分为三个部分:类加载、链接和初始化。其中类的加载是指将类的.class文件读入内存,并将其转化成方法区中的运行时数据结构;链接是将类的常量池中的符号引用转化成直接引用的过程,然后进行内存地址的检验,最后完成方法表的预备工作;初始化则是对类的静态变量进行初始化的操作,JVM提供了五种初始化方式。
具体来说,Java类加载初始化的过程如下:
- 加载:这是类加载的第一个阶段,在该阶段,类加载器会从本地文件系统或者网络中查找类的字节码文件,并将其读入到JVM中的内存中。当然,在加载的过程中可能会出现各种各样的问题,如类找不到、类格式不正确、访问权限等。
- 链接:
- 校验:在这个阶段,JVM会对类进行校验,来确保它的字节码是合法的、符合Java虚拟机规范的。该阶段主要是检查字节码文件是否符合规范的内容。如果字节码文件存在错误,那么会抛出
java.lang.VerifyError
错误。 - 准备:在该阶段,JVM会为类信息在内存中分配空间,并且设置类变量的默认值(0、false、null等)。
-
解析:在此阶段,JVM会将符号引用替换为直接应用,也就是将常量池中的Class符号转为对应的直接引用。这个阶段可能会归并(合并)一些类或者接口,也可能导致一些类或接口没能被加载,从而抛出
java.lang.NoClassDefFoundError
或java.lang.ClassNotFoundException
。 -
初始化:这是类加载的最后一个阶段,在此阶段JVM必须保证所有静态变量在进行首次访问之前都已经被正确地初始化。JVM定义了五种初始化方式,包含:静态变量赋值、静态代码块赋值、延迟加载、粒度控制、progressive。
类初始化过程中,并不是所有的类都会被初始化。只有在以下情况下类才会被初始化:
- 创建类的实例
- 调用类的静态方法
- 访问类的静态变量
- 使用反射方式强制创建某个类或接口对应的Class对象,且该类或接口还没有被初始化
- 初始化某个类或接口的子类。
接下来,我举两个例子来说明Java类加载初始化的过程和顺序。
示例一
我们来看一个简单的Java类SimpleClass
,其代码如下:
public class SimpleClass {
static {
System.out.println("Static initialization block");
}
public static void main(String[] args) {
System.out.println("Main method");
}
}
在执行SimpleClass.main
方法之前,这个类会被加载、链接和初始化,我们来逐步分析运行结果:
- 加载:在运行Java程序时,JVM会查找符号引用,当发现需要加载类时,会调用类加载器来进行加载。在该例中,类的加载通过系统类加载器(AppClassLoader)完成。
- 链接
- 校验:在该阶段,JVM会校验字节码文件,是否符合规范、正确性等。该阶段的输出为:
Static initialization block
。 - 准备:在该阶段,会为类变量在内存中分配空间,并设置默认值。在该例中,在符号表中发现了类的静态变量,被初始化为0或null。
-
解析:在该阶段,JVM会将符号引用转换为直接引用。这里并没有符号引用需要转换,所以跳过这个步骤。
-
初始化:在该阶段,执行类的静态初始化。在
SimpleClass
中,静态代码块将被执行,并打印Static initialization block
。
这样上述SimpleClass
类的类加载初始化过程便完整地走完了。
示例二
下面我们再来看一下更复杂一些的类的初始化顺序。下面先给出Parent
父类和Child
子类的定义:
class Parent {
public static int A = 1;
static {
A = 2;
}
}
class Child extends Parent {
public static int B = A;
public static void main(String[] args) {
System.out.println(B);
}
}
那么我们再分析Child
类的初始化过程:
- 加载:
Child
类的加载器是系统类加载器(AppClassLoader),因为该类是通过main方法启动的。 - 链接:
- 校验:此阶段完成后执行
Parent
类的初始化。Parent
的初始化之后的特征是子类可以使用父类的静态属性,在这个例子中执行后A的值修改为2。 - 准备:在该阶段,会为类变量在内存中分配空间,并设置默认值。准备阶段结束后,静态变量的值符合程序员的设定,即
A=2
和B=0
。 -
解析:在该阶段,JVM会将符号引用转为直接引用。在这个例子中,没有需要转化的引用需要解析。
-
初始化。
- 类初始化时会初始化类中的父类
- 父类的静态代码块(static block)先于子类执行(源码中
Parent
的静态部分先于Child
),且只会执行一次。 - 接着执行子类中的静态代码块(此处没有子类实现)
- 最后执行子类自身的main方法
- 整个过程中只执行了一次输出,最终的输出结果为:
2
。
所以,通过上面这两个例子,我们可以看出Java类加载初始化的过程及顺序,即先加载,再链接,最后初始化。在初始化期间,首先执行父类初始化,接着执行子类的静态初始化块和静态变量初始化,最后执行子类的主方法。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:Java类加载初始化的过程及顺序 - Python技术站