一、类加载的过程
我们通过一个流程图来进行分析:
类加载的步骤
类加载过程有如下几步:
加载 >> 验证 >> 准备 >> 解析 >> 初始化 >> 使用 >> 卸载
- 加载:在硬盘上查找并通过IO读入字节码文件,使用到类时才会加载,例如调用类的 main()方法,new对象等等,在加载阶段会在内存中生成一个代表这个类的 java.lang.Class对象,作为方法区这个类的各种数据的访问入口
- 验证:校验字节码文件的正确性
- 准备:给类的静态变量分配内存,并赋予默认值
- 解析:将符号引用替换为直接引用,该阶段会把一些静态方法(符号引用,比如 main()方法)替换为指向数据所存内存的指针或句柄等(直接引用),这是所谓的静态链接过程(类加载期间完成),动态链接是在程序运行期间完成的将符号引用替换为直接引用
- 初始化:对类的静态变量初始化为指定的值,执行静态代码块
- 使用:JVM底层会通过C++来调用我们的类方法进行运算
- 卸载:程序运行结束,JVM通过GC机制回收原来分配给类运行的内存空间。
PS:只有使用类的时候才会去进行类加载【懒加载】
首先会加载静态代码块,然后调用构造方法进行对象创建。
PS:如A a = null; 这种并没有真正去使用这个类,并不会去加载。
二、类加载器
类加载过程主要是通过类加载器来实现的,Java里有如下几种类加载器:
- 引导类加载器:负责加载支撑JVM运行的位于JRE的lib目录下的核心类库,比如rt.jar、charsets.jar等
- 扩展类加载器:负责加载支撑JVM运行的位于JRE的lib目录下的ext扩展目录中的JAR类包
- 应用程序类加载器:负责加载ClassPath路径下的类包,主要就是加载你自己写的那些类
- 自定义加载器:负责加载用户自定义路径下的类包
PS:引导类加载器底层为C语言实现的,其他类加载器则是普通的Java对象。
下面我们通过几个例子来演示一下:
类加载器分工
类加载器层级
类加载器加载目录
三、类加载器的初始化过程【Launcher启动器分析】
- 在Launcher构造方法内部,其创建了两个类加载器,分别是sun.misc.Launcher.ExtClassLoader(扩展类加载器)和sun.misc.Launcher.AppClassLoader(应用类加载器)。
- JVM默认使用Launcher的getClassLoader()方法返回的类加载器AppClassLoader的实例加载我们的应用程序。
PS:Launcher相当于是类加载器的启动器。
下面通过一段伪码来分析Launcher的构造函数做了啥:
四、双亲委派机制解析
开局一张图,先通过这个流程图来简单了解一下双亲委派机制做了什么事:
看完流程图简单地有个了解以后,直接上源码分析~
为什么要设计双亲委派机制
- 沙箱安全机制:自己写的类似于java.lang.String.class这种核心类不会被加载,这样便可以防止核心API库被随意篡改。
- 避免类的重复加载:当父亲已经加载了该类时,就没有必要子ClassLoader再加载一次,保证被加载类的唯一性。
- 全盘负责委托机制:当一个ClassLoder装载一个类时,除非显式的使用另外一个ClassLoder,该类所依赖及引用的类也由这个ClassLoder载入,避免重复加载。
五、自定义类加载器
步骤
- 1、继承ClassLoader
- 2、重写findClass()方法
PS:自定义的类加载器,默认的父加载器是AppClassLoader【应用程序类加载器】
思路
自定义类加载器只需要继承 java.lang.ClassLoader 类,该类有两个核心方法,一个是loadClass(String, boolean),实现了双亲委派机制,还有一个方法是findClass(),默认实现是空方法,所以我们自定义类加载器主要是重写方法。
代码演示
六、小问答
Q:双亲委派机制为什么要走2次查找类加载器的路径?
从上面的图我们可以看出,在第1次加载类时,会先看当前类加载器加载目录下有没有已经加载过的类,没有则向上委托。当一直向上委托至引导类加载器时,还没能加载,又会回到应用程序加载器来加载,此时加载一个类确实经历了“一上一下”2次路径。那么这么设计的好处主要在哪里呢?为啥不直接把引导类加载器作为入口,这样就只用走一次路径就可以加载类了呀?
其实这样设计只有第一次加载需要走2次路径,当第2次去加载相同的类时,检查应用程序类加载器已经加载过了,就不会再向上委托了,而我们一个项目中可以说绝大部分需要加载的类基本都是我们自己开发的类【JDK相关的类本来也就是需要引导类/扩展类加载器加载的,这里就不再讨论】,本来也是需要用到应用程序加载类来加载的,以这种方式设计的话,只有第一次加载会稍显麻烦,后续加载的效率会快很多。
Q:双亲委派机制是继承关系吗?
双亲委派可不是继承关系喲!把双亲委派中的“引导类加载器”、“扩展类加载器”理解为“应用程序类加载器”的上级比较好,我个人把双亲委派机制理解为“双老板委派机制~😋”。
“员工”【应用程序加载器】第一次加载当前类时,没有经验,先告知“小老板”【扩展类加载器】来加载,加载不到又继续委托“大老板”【引导类加载器】来加载,等第一次加载完成以后,“员工”【应用程序加载器】已经明白这个业务的执行流程,也就自己加载了,不再去打扰“老板们”了~
Q:JSP的热加载机制原理?
其实JSP的热加载原理主要是由2部分组成,首先是每个JSP文件对应的会有自己的JSP加载器,然后tomcat之类的web容器会启动一个线程去监控文件是否已经被修改【比如启动一个定时任务,去轮询文件标识已修改的时间戳是否有变更】,如果被监控到当前JSP文件已经变更,则会把这个JSP对应的加载器卸载掉,然后重新生成一个JSP加载器来加载新的文件,这样加载出来的内容就是最新的了,即实现热加载~