一说Java代码的执行, 接触过Java代码的同学肯定会想到: 编写, 编译, 运行这三个阶段. 其中:
编写: 是在后缀名为.java的文件中, 根据Java语法规则编写源代码.
编译: 是将.java文件(源代码文件)编译成.class文件(字节码文件).
运行: 是通过JVM来执行.class字节码文件.
大白话解释: .Java文件是程序员能看懂, 但是计算机看不懂的文件. 需要先把它转换成.class文件, 计算机才能识别, 从而来执行. 虽然这三个阶段都可以通过IDE实现, 但是好多人容易忽略一个细节, 那就是: 在JVM执行.class字节码文件之前, 需要先通过”类加载器”将该字节码文件加载到内存中, 而这个过程, 就是我们要详聊的话题.
本文会从以下3点来介绍”Java中的”类加载器:
1. 类加载器的概述.
2. 类加载器的分类.
3. 类加载机制.
首先, 我们先来聊下类加载器的概述, 类加载器(ClassLoader)是负责加载类的对象的, 也就是将.class字节码文件加载到JVM内存中的. 那它什么时候才会去加载.class字节码文件呢? 答案是: 当Java程序第一次使用某个类中的内容, 而该类的字节码文件在内存中不存在时, 类加载器就会去加载该类的字节码文件.
俗话说”渡人先渡己”, 要想成为别人的榜样, 帮助别人. 首先要做好自己. 生活中如此, 类加载器也一样. 要想加载我们自定义的类, 类加载器必须先完成”自加载”的过程. 聊到这, 不得不提的就是”类加载器的分类”了.
Java中的类加载器主要分为以下四类:
1. 根类加载器(BootStrapClassLoader), 主要负责加载jre/lib/rt.jar相关的字节码文件的.
2. 扩展类加载器(ExtensionClassLoader), 主要负载加载 jre/lib/ext/*.jar 这些jar包的. 该类加载器在JDK1.9的时候更名为: Platform Class Loader, 其父类加载器为: null.
3. 应用程序类加载器(ApplicationClassLoader), 主要负责加载用户自定义的类以及classpath环境变量所配置的jar包的. 该类加载器在JDK1.9的时候更名为: System ClassLoader, 其父类加载器为: ExtensionClassLoader.
4. 自定义类加载器(UserClassLoader), 负责加载程序员指定的特殊目录下的字节码文件的. 大多数情况下, 自定义类加载器只需要继承ClassLoader这个抽象类, 重写findClass()和loadClass()两个方法即可.
如下图:
到这, 相信大家对类加载器已经初步有一定的认识和理解了. 接下来, 我们写代码来验证一下, 代码和打印结果如下:
到这里, 代码就已经验证完毕了. 其实我们现在一直在研究的是JVM类加载机制的"加载循序", 现在, 我们来研究下它的"检查顺序", 请你思考, 假设: D:compile, ext*.jar, rt.jar三类中都有 A.class, 那么A.class是否会被加载3次, 如果不会, 它的加载顺序是什么样的?
答案是: 不会被加载3次, 并最终会由BootStrapClassLoader来加载A.class.
原因是因为, APPClassLoader类加载器(以下简称: app)加载之前, 会先询问ExtClassLoader类加载器(以下简称: ext)是否加载. 如果ext加载, app就不加载了, 反之则app加载. 同样, ext在加载之前, 也会询问BootStrapClassLoader类加载器(以下简称: bootstrap)是否加载, 如果bootstrap加载, 则ext就不加载了, 反之, 则ext加载. 这也是: JVM类加载机制的”双亲委派机制”.
最后, 我们再来聊一聊”类加载机制”, 在JVM中类加载机制主要有3种:
1. 全盘加载. 顾名思义, 就是当某一个类加载器加载某个.class文件时, 默认也会连同该文件所依赖的.class一起加载(除非显示声明通过某个指定的类加载器加载).
2. 缓存机制. 即所有类加载器已经加载过的.class文件都会被保存到缓存中, 下次使用该.class文件时, JVM会优先从缓存中查找, 如果没有, 才会去加载指定的字节码文件, 这也是为什么当字节码文件变化后, 需要重启JVM后才能看到修改效果的原因.
3. 双亲委派. 大白话解释, 儿子(App)要星星, 他自己实现不了, 就找他老爹(Ext)要, 他老爹能实现的话就给他了, 实现不了, 就找他爷爷(BootStrap)要, 说: 你孙子要天上的星星. 他爷爷如果能实现就给了, 如果也实现不了, 就会告诉他爹(Ext), 让你儿子(App)自己实现吧. 这种情况有点极端, 属于谁都没有加载, 则程序报错, 会抛出异常.
总结: 类加载器自上而下检查(App --> Ext --> BootStrap), 自下而上加载(BootStrap --> Ext --> App).