从class文件到内存中的类,按先后顺序需要经过加载、链接以及初始化三个步骤
一、加载
加载就是查找字节流,并且据此创建类的过程。
除了启动类加载器(所有类加载器的祖师爷,由C++实现,没有对应的Java对象)之外,其他的类加载器都是 java.lang.ClassLoader 的子类。这些类加载器需要先由另一个类加载器,比如说启动类加载器加载至JVM中,才能进行类加载。
双亲委派模型:每当一个类加载器接收到加载请求时,它会先将请求转发给父类加载器,在父类加载器没有找到所请求的类的情况下,该类加载器才会尝试去加载。
Java9之前:
启动类加载器 :加载最基础和最重要的类(JRE的lib目录下jar包中的类)。
扩展类加载器 :其父类加载器是启动类加载器,负责加载相对次要,但通用的类(JRE的lib/ext目录下jar包中的类)。
应用类加载器:其父类加载器是扩展类加载器,负责加载应用程序路径下的类(这里的应用程序路径,是指虚拟机参数 -cp/-classpath、系统变量Java.class.path或环境变量CLASSPATH所指定的路径)默认情况下,应用程序中包含的类便是由应用类加载器加载的。
Java9之后:引入模块系统
扩展类加载器 rename为 平台类加载器
除了由 Java 核心类库提供的类加载器外,我们还可以加入自定义的类加载器,实现特殊的加载方式,eg 可以对class文件进行加密,加载时再利用自定义的类加载器对其解密。
在JVM中,类的唯一性是由类加载器实例以及类的全名一同确认。即使是同一串字节类,经由不同类加载器加载,也会得到两个不同的类。
二、链接
链接,是指将创建成的类合并至JVM中,使之能够执行的过程,分为三个如下阶段
注意:在class文件被加载至JVM之前,该类无法知道其他类及方法、字段对应的具体地址,甚至不知道自己方法、字段的地址,因此,每当需要引用这些成员时,Java编译器会生成一个符号引用(java编译器会暂时使用符号引用表表示目标方法
),在运行阶段,该符号引用一般能无歧义的定位到具体目标,解析的目的就是将这些符号引用解析成为实际引用。
当然,这些符号引用有可能指向一个未被加载的类或类的字段,那么解析也将触发这个类的加载(但未必触发这个类的链接及初始化)
1.非接口符号引用
假定该符号引用指向C类
2.接口符号引用
假定该符号引用指向接口I
经过上述解析步骤后,符号引用会被解析成实际引用。
对于静态绑定的方法调用而言,实际引用是一个指向方法的指针;对于动态绑定的方法调用而言,实际引用是一个方法表的索引
三、初始化
只有当初始化完成以后,类才正式成为可执行的状态。
JVM进行类的初始化之前,先简单了解下Java编译器都做了什么工作。
对于Java中初始化,静态字段的初始化有点特殊,
两种方式初始化一个静态字段,
(1)声明时直接赋值(若直接赋值的静态字段被final所修饰,且它的类型是基本类型或字符串时,该字段便会被编译器标记成常量,其初始化直接由JVM完成)
(2)在静态代码块中对其赋值。
所以除了对于常量是直接由JVM完成,其余的初始化(直接赋值操作及所有静态代码块中的代码),会被Java编译器置于同一个方法中,并把它命名为***< clinit >***。
总结为一句话就是,类加载的最后一步初始化,便是为标记为常量 的字段赋值以及执行***< clinit >***方法的过程。
类的初始化仅会被执行一次,这个特性被用来实现单例的延迟初始化。如下demo
public class Singleton {private Singleton() {}private static class LazyHolder {static final Singleton INSTANCE = new Singleton();}public static Singleton getInstance() {return LazyHolder.INSTANCE;}
}
这段代码是著名的单例延迟初始化例子
,只有当调用Singleton.getInstance 时,程序才会访问LazyHolder.INSTANCE,才会触发对 LazyHolder 的初始化(对应下图第 4 种情况),继而新建一个 Singleton 的实例
个人思考
public class Singleton {private Singleton() {}private static class LazyHolder {static final Singleton INSTANCE = new Singleton();static {System.out.println("LazyHolder.<clinit>");}}public static Object getInstance(boolean flag) {if (flag) return new LazyHolder[2];return LazyHolder.INSTANCE;}public static void main(String[] args) {getInstance(true);System.out.println("----");getInstance(false);}
}
1.新建数据(10行)会导致LazyHolder的加在吗?会初始化吗?
2.新建数组会导致LazyHolder的链接吗?
答案:
1.虚拟机必须知道(加载)有这个类,才能创建这个类的数组(容器),但是这个类并没有被使用到(没有达到初始化的条件),所以不会初始化。所以新建数组会加载元素类LazyHolder;不会初始化元素类
2.新建数组的时候并不是要使用这个类(只是定义了放这个类的容器),所以不会被链接,调用getInstance(false)的时候约等于告诉虚拟机,我要使用这个类了,你把这个类造好(链接),然后把static修饰的字符赋予变量(初始化)。