静态链接
那么,首先,咱们先来聊聊静态链接。
如上面的概念所述,在C/C++中静态链接就是在编译期将所有类加载并找到他们的直接引用,不论是否使用到。而在Java中我们知道,编译Java程序之后,会得到程序中每一个类或者接口的独立的class文件。虽然独立看上去毫无关联,但是他们之间通过接口(harbor)符号互相联系,或者与Java API的class文件相联系。
我们之前也讲述了类加载机制中的一个过程—解析,并在其中提到了解析就是将class文件中的一部分符号引用直接解析为直接引用的过程,但是当时我们并没有详细说明这种解析所发生的条件,现在我给大家进行补充:
方法在程序真正运行之前就有一个可确定的调用版本,并且这个方法的调用版本在运行期是不可改变的。可以概括为:编译期可知、运行期不可变。
符合上述条件的方法主要包括静态方法和私有方法两大类。前者与类型直接关联,后者在外部不可被访问,这两种方法的特点决定了它们都不可能通过继承或别的方式重写其他版本,因此它们适合在类加载阶段进行解析。
额外补充一点:
在Java虚拟机中提供了5条方法调用字节码指令,其中invokestatic和invokespecial指令调用的方法,都可以在解析阶段中确定唯一的调用版本,符合这个条件的有静态方法、私有方法、实例构造器、父类方法(不知道这是个什么玩意、不重要,先放下)4类。它们在类加载的时候就会把符号引用解析为该方法的直接引用,因此这些方法也被称为非虚方法(包括final方法),与之相反的称为虚方法。
解析调用一定是个静态过程,在编译期间就完全确定,在类加载的解析阶段就会把涉及的符号引用转化为可确定的直接引用,不会延迟到运行期再去完成,这也就是Java中的静态链接。
在Java方法调用的过程中,JVM是如何知道调用的是哪个类的方法源代码?这里面到底有什么内幕呢?
这篇文章我们就将揭露JVM方法调用的静态(static binding)和动态绑定机制(auto binding)。
静态绑定机制:
//被调用的类
package hr.test;
class Father {
public static void f1() {
System.out.println("Father");
}
}
//调用静态方法 import hr.test.Father;
public class StaticCall {
public static void main() {
Father.f1(); //调用静态方法
}
}
上面的源代码中执行方法调用的语句(Father.f1())被编译器编译成了一条指令:
invokestatic #13。我们看看JVM是如何处理这条指令的
(1)指令中的#13指的是StaticCall类的常量池中第13个常量表的索引项(关于常量池详见《Class文件内容及常量池 》)。
这个常量表(CONSTATN_Methodref_info)记录的是方法f1信息的符号引用(包括f1所在的类名,方法名和返回类型)。
JVM会首先根据这个符号引用找到方法f1所在的类的全限定名:hr.test.Father;
(2)紧接着JVM会加载、链接和初始化Father类;
(3)然后在Father类所在的方法区中找到f1()方法的直接地址,并将这个直接地址记录到StaticCall类的常量池索引为13的常量表中。
这个过程叫常量池解析 ,以后再次调用Father.f1()时,将直接找到f1方法的字节码;
(4)完成了StaticCall类常量池索引项13的常量表的解析之后,JVM就可以调用f1()方法,并开始解释执行f1()方法中的指令了。
通过上面的过程,我们发现经过常量池解析之后,JVM就能够确定要调用的f1()方法具体在内存的什么位置上了。实际上,
这个信息在编译阶段就已经在StaticCall类的常量池中记录了下来。这种在编译阶段就能够确定调用哪个方法的方式,
我们叫做静态绑定机制 。
除了被static 修饰的静态方法,所有被private 修饰的私有方法、被final 修饰的禁止子类覆盖的方法都会被编译成invokestatic指令。
另外所有类的初始化方法和会被编译成invokespecial指令。JVM会采用静态绑定机制来顺利的调用这些方法。
动态链接
上面大概说完了静态链接,那么什么是动态链接、它有什么用?
如上所述,在Class文件中的常量持中存有大量的符号引用。字节码中的方法调用指令就以常量池中指向方法的符号引用作为参数。这些符号引用一部分在类的加载阶段(解析)或第一次使用的时候就转化为了直接引用(指向数据所存地址的指针或句柄等),这种转化称为静态链接。而相反的,另一部分在运行期间转化为直接引用,就称为动态链接。
与那些在编译时进行链接的语言不同,Java类型的加载和链接过程都是在运行的时候进行的,这样虽然在类加载的时候稍微增加一些性能开销,但是却能为Java应用程序提供高度的灵活性,Java中天生可以动态扩展的语言特性就是依赖动态加载和动态链接这个特点实现的。