从经典面试题看java中类的加载机制
1.概述
类加载是Java程序运行的第一步,研究类的加载有助于了解JVM执行过程,并指导开发者采取更有效的措施配合程序执行,对理解java虚拟机的连接模型和java语言的动态性都有很大帮助。
由于Java的跨平台性,经过编译的Java源程序并不是一个可执行程序,而是一个或多个类文件。当Java程序需要使用某个类时,JVM会确保这个类已经被加载、连接(验证、准备和解析)和初始化。
JVM中类的装载是由类加载器(ClassLoader)和它的子类来实现的,Java中的类加载器是一个重要的Java运行时系统组件,它负责在运行时查找和装入类文件中的类。
2.类加载器
类的加载是由类加载器完成的,类加载器包括:根加载器(BootStrap)、扩展加载器(Extension)、系统加载器(System)和用户自定义类加载器(java.lang.ClassLoader的子类)。
-
Bootstrap:一般用本地代码实现,负责加载JVM基础核心类库(rt.jar);
-
Extension:从java.ext.dirs系统属性所指定的目录中加载类库,它的父加载器是Bootstrap;
-
System:又叫应用类加载器,其父类是Extension。它是应用最广泛的类加载器。它从环境变量classpath或者系统属性java.class.path所指定的目录中记载类,是用户自定义加载器的默认父加载器。
3.类加载的过程
JVM类加载机制
<1>全盘负责,当一个类加载器负责加载某个Class时,该Class所依赖的和引用的其他Class也将由该类加载器负责载入,除非显示使用另外一个类加载器来载入
<2>父类委托,先让父类加载器试图加载该类,只有在父类加载器无法加载该类时才尝试从自己的类路径中加载该类
<3>缓存机制,缓存机制将会保证所有加载过的Class都会被缓存,当程序中需要使用某个Class时,类加载器先从缓存区寻找该Class,只有缓存区不存在,系统才会读取该类对应的二进制数据,并将其转换成Class对象,存入缓存区。这就是为什么修改了Class后,必须重启JVM,程序的修改才会生效。
双亲委派模型的工作流程是:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把请求委托给父加载器去完成,依次向上,因此,所有的类加载请求最终都应该被传递到顶层的启动类加载器中,只有当父加载器在它的搜索范围中没有找到所需的类时,即无法完成该加载,子加载器才会尝试自己去加载该类。
下图是我总结的一个java类被jvm加载的过程。
4.子类和父类加载顺序
那么问题来了,如果有一个java类继承了另一个java类,这两个类的内部具体的执行过程是什么呢?
比如下面的面试题:
public class Tests{
@Test public void test(){
A a= newA(); a.aDisplay();
}
}
执行结果:
Class B1:static blocks2 Class B2:static blocks3
Class A1:static blocks1 Class A2:static blocks2
Class B:common blocksi=4,j=3 constructorB: i=5,j=4
Class A:common blocksi=3,j=2 constructorA: i=4,j=3
Class A:static void a Display(): i=5,j=3
可能这里面最迷惑人的就是两个成员变量了,一个静态的一个非静态的。
其实他们出现在这是貌似没啥太大意义,父类和子类虽然拥有着同名的成员变量,但是,这些变量都独自存在于各自的类中,并有各自的只。
言归正传,由上面的面试题,我们可以得出下面的规律: