上回最后部分说到类加载阶段过程中关于类接口、字段的解析流程。那么今天就接着上回的内容,从类方法、接口方法的解析开始继续往下学习。
类方法的解析
类方法和接口方法不同,类方法可以直接使用该类进行调用,但是接口方法必须要有相应的实现类继承才能够进行调用。
1.如果在类的方法表中,发现class_index中索引的Resolve是一个接口的话,而不是一个类,直接会返回错误。
2.在Resolve类中,查找是否有方法描述和目标方法完全一致的方法,如果没有,则继续向其父类中去找,否则直接返回这个方法的引用
3.如果在父类中一直没有找到,就意味着查找失败了,会抛出NoSuchMethodErrorException。如果在当前类或者父类中找到了和目标方法一致的方法,但是它是一个抽象的类,会抛出AbstractMethodErrorEcveption。
当然,在类方法查找的过程中,也存在着大量的校验和验证。
接口方法的验证
接口可以定义方法,也可以去继承其他接口。
1.在接口方法表中发现class_index中索引的Resolve是一个类而不是接口,会直接报错。理论上来说,方法接口表中和类接口表中所容纳的类型应该是不一样的。从常量池中有Contant_Methodref_info和Contant_IntefaceMethodredInfo两个不同的类型也可以看出来。
2.接口方法的查找和类方法类似,子类到父类,自上而下。
类的初始化阶段-类加载过程中最后一个阶段
在这个阶段中,最重要的一件事,执行<clinit>()(class_initialize)方法,所有的类变量都会赋予正确的值,也就是程序所制定的值。
<clinit>()方法包含在生成的class文件中,在编译阶段生成。它包含了所有类变量的赋值动作和静态块的执行代码。编译器收集的顺序是由执行语句在源文件中的出现顺序决定的。注意:该方法能够保证顺序性。另外,静态块只能对后面的静态变量进行赋值,单数不能够访问。
注:
1.如果在一个类或者中没有静态代码块,也没有静态变量,那么它就没有<clinit>()方法。
2.<clinit>()方法只能够被虚拟机所执行,类主动使用后会去调用这个方法。
3.JVM保证了该方法在多线程的执行环境下的同步语义,所以在Java的单例模式一文中,看到的Holder实现单例模式是一种比较适合的方式。
类加载过程总结
随着java的不断发展,jvm不断升级,类加载可能会变。但无论怎么变,还是会以class二进制文件加载,连接,类的初始化进行下去。最后,再看下在类加载过程中的这段代码,作为这一个阶段学习的结束。
1 public class Simple {
2
3 // 1.
4 private static int x = 0;
5 private static int y;
6
7 private static Simple instance = new Simple(); // 2.
8
9 private Simple() {
10 x++;
11 y++;
12 }
13
14 public static Simple getInstance() {
15 return instance;
16 }
17
18 public static void main(String[] args) {
19 Simple instance = Simple.getInstance();
20 System.out.println(instance.x);
21 System.out.println(instance.y);
22 }
23
24 }
说明:
- 加载二进制文件后,进入到连接阶段,类变量x, y,instance赋予对应的初始值,即 x = 0, y = 0, instance=null,进入到解析阶段。
- 解析阶段过后,到初始化阶段,为每一个类变量赋值程序文件中对应的指定值。也就是执行<clinit>方法的过程。执行过后x = 0, y = 0, instance=new Simple()。
- 接下来,非常熟悉,在new()的过程中,执行Simple类的构造方法,结果为 x = 1; y = 1。至此,类的加载完成。