注:基于《Java高并发编程详解-汪文君》、《深入理解JVM高级特性与最佳实践-周志明》,以学习为目的,加上自身理解、验证。作为笔记,为加深印象,做到不复制,不粘贴。欢迎,大家一起交流学习。
上回最后部分说到类加载阶段过程中关于类接口、字段的解析流程。那么今天就接着上回的内容,从类方法、接口方法的解析开始继续往下学习。
类方法的解析
类方法和接口方法不同,类方法可以直接使用该类进行调用,但是接口方法必须要有相应的实现类继承才能够进行调用。
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。至此,类的加载完成。