Java类的装载(Loading)、链接(Linking)和初始化(Initialization)


Loading 加载

按如下三步执行

1.通过类的全名产生对应类的二进制数据流。(注意,根据early load的原理,如果没找到对应类文件,只有在类实际使用时才抛出错误.)

2.分析并将这些二进制数据流转换为 方法区(JVM的架构:方法区、堆,栈,本地方法栈,pc寄存器)特定的数据结构(这些数据结构是实现有关的,不同JVM有不同实现)。

这里处理了部分verification(对正确性的检验),比如 .class文件的magic number , 文件是否过长或者过短。确定是否有父类(除了Obecjt类)。

3.创建对应类的 java.lang.Class 实例。(有了对应的Class实例,并不意味着这个类已经完成了加载链接!!)

Linking 链接

链接的过程比加载过成复杂不少,这是实现java的动态性的重要一步!分为三部分:verification (检测), preparation(准备) 和 resolution(解析)

1.verification:(注意到有一些verification已经在loading的过程中执行)

linking的resolve会把类中成员方法、成员变量、类和接口的符号引用替换为直接引用,而在这之前,需要检测被引用的类型正确性和接入属性是否正确(就是public ,private的的问题)诸如,检查final class 没有被继承,检查静态变量的正确性等等。

2.preparation:

对类的成员变量分配空间。

虽然有初始值,但这个时候不会对他们进行初始化(因为这里不会执行任何java代码)。。具体如下:

所有primitive type都为 0 值。如float : 0f , int 0 , boolean 0 (注意boolean底层实现大多使用int),

引用类型则为 null

值得注意的是,JVM可能会在这个时期给一些有助于程序运行效率提高的数据结构分配空间。比如method table(类似与C++中的虚函数表,参见另一篇《Java:方法的虚分派(virtual dispatch)和方法表(method table)》 )。

3.Resolution

为类、接口、方法、成员变量的符号引用定位直接引用(符号引用先到constant pool中寻找符号,再找先应的类型,无疑会耗费更多时间),完成内存结构的布局。

注意,这一步也是可选的。可以在符号应用第一次被使用时完成,即所谓的 late resolution .

但是,对用户而言,这一步永远是late resolution的:即即使运行时会early resolution , 但程序不会显示的在第一次判断出错误时抛出错误,而会在对应的类第一次active use的时候抛出错误!

另外,这一步与之后的类初始化是不冲突的,并非一定要所有的resolution结束以后才执行类的初始化。不同的JVM实现不同。

详情见另一篇《Java类初始化的时机(主动调用和被动使用的区别) 》

Initialization 初始化类

我们接触最多的是对对象的初始化,但类也是有初始化的。

相比对象初始化(参见《Java类的实例化探究》),类的初始化机制略简略。

类的初始化也是延迟的,直到类第一次被主动使用(active use),JVM才会初始化类。(参见《Java动态性: 类加载时的延迟初始化》)

类的初始化分两步:

1.如果基类没有被初始化,初始化基类

2.有类构造函数,则执行类构造函数   这是由java编译器完成的,将会把类成员变量的初始化和static区间的代码提取出,放到一个叫<clinit>的方法中。

这个方法不能被一般的method访问(static final 成员变量不会在此执行初始化,它一般被编译器生成constant值)。

如果你细心,应该会注意到,<clinit>中是不会显示的调用基类的<clinit>的,因为1中已经执行了基类的初始化。

类的初始化还必须注意线程安全的问题。

另外,注意,接口是的初始化比较不同。

这就解释了为什么接口不能定义成员变量,只能定义static final变量:

1.接口不可实例化,即其中的信息都应该是类层次的,而不是对象层次。很容易理解为static了。

2.如果能修改,一旦一个类实现了这个接口,并修改了接口中的非final变量,则对于其他在之后实现同样接口的类,他们实现的就不是同一个接口了,接口中的变量都变化了。

综上述,static final更适合于接口。

再另外

注意class和interface的初始化策略是不一样的。

一个class的初始化,会首先初始化superclass,而interface的初始化,只是因为其中的non constant变量(比如final static i = random())被使用,而不管super interface。

相关内容