类加载过程
类加载可以分为加载、连接、初始化3个部分
加载
加载过程是指查找并加载类的二进制数据,加载class文件的方式可以有以下几种
- 本地class文件
- 网络下载的class文件
- jar或zip等归档文件中加载class文件
- 将java源文件动态编译的class文件(动态代理)
- 特殊的数据库中提取class文件
类加载的最终产品就是位于内存中的Class对象。Class对象封装了类在方法区内的数据结构并提供了能够访问方法区内这些数据结构的接口。
Java提供了两种类加载器
Java虚拟机自带的类加载器
- Bootstrap ClassLoader(由C++编写)
- Extension ClassLoader(纯Java的加载器)
- App/System ClassLoader(纯Java的加载器)
用户自定义的类加载器
1.java.lang.ClassLoader的子类
连接
连接就是对已经读入内存的类的二进制数据合并到虚拟机的运行时环境中去,连接又分为3个步骤,分别是
-
验证
验证是对二进制数据的合法性进行校验,具体是指如下几点
- 类文件结构检查
- 语义检查
- 字节码验证
- 兼容性验证
-
准备
为类的静态变量申请内存,并为其附上其类型的默认值。(0,false,null等)
-
解析
在类型的常量池中寻找类、接口、字段和方法的符号应用转换为直接应用
初始化
初始化是指为类的静态变量设置正确的初始值,即在代码中真正设置的值。
类初始化步骤:
- 假如该类还未加载和连接,则先进行加载和连接
- 加入该类存在父类,且父类还未被初始化时则先初始化父类
- 初始化是按照代码编写顺序依次执行 的代码。
类的初始化时机
类的初始化时机与类的使用方式密切相关
Java程序对类的两种使用方式
- 主动使用
- 被动使用
其中主动使用会引发上面的初始化操作。Java虚拟机要求每个类或接口在其被首次主动使用时才初始化类或接口。
主动使用某个类或接口的七种情况
- 创建实例(new Object)
- 调用类的静态方法
- 对类的静态属性get或set
- 反射(Class.forName,需要注意的是在Class.forName时有一个initialize参数可以控制是否需要初始化,默认为true。)
- 初始化一个类的子类
- Java虚拟机启动时被标记为启动类的类
- JDK1.7提供的对动态语言支持的三个句柄对应的类没初始化时
除了以上7种情况外的都是对类的被动使用,被动使用是不会导致对类的初始化的。
另外访问类的静态属性时有一种特殊情况不会造成类的主动使用,示例代码如下
public class Sample{
public static final int a = 1;
public static final int b = new Random(3).nextInt(3);
}
public class Main {
public static void main(String args[]){
// 这里直接访问Sample的静态属性a,但并不会初始化Sample类,因为属性a是final的,因此在编译期编译器会将a的值放入Main类的常量池中,在运行时并不会去引用Sample类
System.out.println(Sample.a);
// 这里对Sample的静态属性b的直接访问会造成Sample的初始化,因为虽然b也是被final修饰的,但b的值需要在运行时才能确定,因此还是需要对Sample类进行初始化操作
System.out.println(Sample.b);
}
}
调用ClassLoader的loadClass并不是对类的主动使用,因此不会进行初始化。
接口的初始化时机
上文已经说过,当Java虚拟机初始化一个类时,要求其所有父类都已经被初始化,但此规则并不适用于接口
针对接口来说遵循如下两种规则
- 在初始化一个类时并不会初始化其实现的接口
- 在初始化一个字接口时并不会对其父接口进行初始化
因此,一个父接口并不会因为其字接口或实现类的初始化而初始化,只有当程序首次主动使用特定接口的静态变量时才会对其进行初始化。
网友评论