Java 基础 类加载器和双亲委派机制 学习笔记
转自博客:https://blog.csdn.net/weixin_38118016/article/details/79579657
文章不是我写的,但是感觉写的挺通俗易懂的,然后防止以后丢失,就转载到我的个人博客上了,留作学习笔记。
零、Java的编译执行过程
编译 -----> 验证 -----> 准备 ----->解析 ----->初始化 ----->使用------>卸载
其中验证、准备、解析统称为连接
1.编译: 将 .java 文件 通过 javac 文件名.java 的命令 编译成 文件名 .class 的过程
2.验证: 确保Class文件的字节流中包含的信息符合当前虚拟机的要求。
3.准备: 正式为类变量(被static修饰的变量,不包括实例变量,实例变量在对象实例化时随对象分配到java堆中)分配内存并设置类变量初始值,且初始值是指数据类型的零值。
4.解析: 将常量池内的符号引用替换为直接引用
5.初始化: 执行类构造器<clinit>()方法的过程,<clinit>()方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块中的语句合并产生的。
附:静态语句块中只能访问到定义在静态语句块之前的变量,定义在它之后的变量,在前面的静态语句块中可以赋值但不能访问
1 public class Test { 2 3 static { 4 i = 0; //给变量赋值可以正常通过 5 System.out.println(i); //错。编译器提示“非法向前引用” 6 } 7 static int i =1; 8 }
有五种情况必须初始化(加载、连接自然要在这之前进行)
1.遇到new、getstatic、putstatic或invokestatic这四条字节码指令时
2.使用java.lang.reflect包的方法对类进行反射调用的时候
3.当初始化一个类时,如果父类还没有进行初始化,需要先触发其父类的初始化
4.当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的类),虚拟机会先初始化这个类
5.当使用jdk1.7的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果为REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,需要先初始化
这五种称为对一个类的主动引用,除此之外的都不会触发初始化,称为被动引用。
例:
1.通过子类引用父类的静态字段,不会导致子类的初始化
2.通过数组定义来引用类
3.常量在编译阶段会存入调用类的常量池中,本质上并没有直接引用到定义常量的类,因此不会初始化
一、java类加载器分类详解
1、Bootstrap ClassLoader:
启动类加载器,也叫根类加载器,负责加载java的核心类库,例如(%JAVA_HOME%/lib)目录下的rt.jar(包含System,String这样的核心类),根类加载器非常特殊,它不是java.lang.ClassLoader的子类,它是JVM自身内部由C/C++实现的,并不是java实现的
2、Extension ClassLoader:
扩展类加载器,负责加载扩展目录(%JAVA_HOME%/jre/lib/ext)下的jar包,用户可以把自己开发的类打包成jar包放在这个目录下即可扩展核心类以外的功能
3、System ClassLoader\APP ClassLoader:
系统类加载器,又称为应用程序类加载器,是加载CLASSPATH环境变量下所指定的jar包与类路径,一般来说,用户自定义的就是由APP ClassLoader加载的
各类加载器之间的关系 以组合关系复用父类加载器的父子关系,注意,这里的父子关系并不是以继承关系实现的
二、类加载器的双亲委派加载机制(重点)
当一个类收到了类加载请求,他首先不会尝试自己去加载这个类,而是把这个请求委派给父类去完成,每一个层次类加载都是如此,因此所有的加载请求都应该传送到启动类加载器中,只有当父类加载器反馈自己无法完成这个请求的时候(在它的加载路径里找不到这个所需要加载的类),子类加载器才会尝试自己去加载
过程如下图所示:
三、双亲委派模型的源码实现
主要体现在ClassLoader的loadClass()方法,思路很简单:
先检查是否已经被加载,若没有被加载则调用父类的LoadClass()方法,若父类加载器为空,则默认使用启动类加载器作为父类加载器,如果父类加载器加载失败,抛出ClassNotFoundException异常后,调用自己的findClass()方法进行加载