在真正的战争到来之前,尽可能地变得强大吧~
文章目录
- 前言
- 1. 类加载
- 1.1 类加载的过程
- 1.2 类加载的时机
- 1.3 双亲委派模型
前言
这个问题是面试经典题,让我们来求甚解吧~
1. 类加载
1.1 类加载的过程
如下图
- 加载,找到.class文件,读取文件内容
- 验证,验证.class文件的格式是否符合JVM虚拟机规范,确保加载了该 class 文件不会导致 JVM 出现任何异常,不会危害JVM 的自身安全。
- 准备,给类对象分配内存空间,这里是在方法区为类对象开辟空间,并将对象值初始化成零值.即int型初始化为0,引用型初始化为null.
- 解析,将常量池的符号引用转为直接引用.在这之前,这个引用记录的并不是字符串常量的真正地址,而是记录在文件中的相对偏移量,相当于先占个位置.类加载之后,才真正把字符串常量的地址放进去.这个引用才被真正的赋值成内存地址.
- 初始化,类加载的最后一步,执行Java代码,这时,才真正的对类对象进行初始化.此阶段会根据代码进行类变量和其他资源的初始化.编译器收集的顺序是由语句在源文件中出现的顺序决定的.
观察下图的执行结果
改变顺序,结果改变
1.2 类加载的时机
遇到 new、getstatic、putstatic、invokestatic 字节码指令,例如:使用 new 实例化对象;
读取或设置一个类的 static 字段(被 final 修饰的除外);
调用类的静态方法。
对类进行反射调用;
初始化一个类时,其父类还没初始化(需先初始化父类);
这点类与接口具有不同的表现,接口初始化时,不要求其父接口完成初始化,只有真正使用父接口时才初始化,如引用父接口中定义的常量。
虚拟机启动,先初始化包含 main() 函数的主类;
1.3 双亲委派模型
如下图,是JVM默认提供的三种类加载器.它们存在父子关系.
加载一个类的时候,从ApplicationClassLoader开始,但ApplicasionClassLoader会先把加载任务交给父亲ExtensionClassLoader去完成.
ExtensionClassLoader要加载了,他也把这个任务交给他的父亲BootstrapClassLoader去完成.
接着,BootstrapClassLoader也想交给自己的父亲,但他发现自己父亲为空,就只能去搜索自己负责的标准库目录的相关的类,找到的就加载,没找到的,就交给子类加载器进行加载.
之后,ExtensionClassLoader去搜索与扩展库相关的目录,如果找到就加载,没找到的,交给子类加载器进行加载
之后,Applicaton开始真正搜索与用户项目有关的目录,找到了,就加载,找不到,就会报错了.
那么,为什么要这么麻烦,不是直接从上到下的呢?由于JVM使用递归来实现的,所以,才导致了这个顺序.另外,先加载底层的类,能保证,当用户代码出错时,只需修改用户代码即可,不会出现太严重的bug.如果用户写了与标准库重名的一些类,由于执行顺序,JVM加载的还是标准库中的类,不会用用户自己写的类.并且,用户可以自定义类加载器,将自己的类加载器加入上述流程中,与自带的类加载器配合使用.