类加载器ClassLoader的含义
不论多么简单的java程序,都是由一个或者多个java文件组成,java内部实现了程序所需要的功能逻辑,类之间可能还存在着依赖关系。当程序运行的时候,类加载器会把一部分类编译为class后加载到内存中,这样程序才能够调用里面的方法并运行。
类之间如果存在依赖关系,那么类加载会去帮你加载相关的类到内存中,这样才能够完成调用。如果找不到相关的类,那么他就会抛出我们在开发经常见到的异常:ClassNotFoundException
Java中的所有类,必须被装载到jvm中才能运行,这个装载工作是由jvm中的类装载器完成的,类装载器所做的工作实质是把类文件从硬盘读取到内存中,JVM在加载类的时候,都是通过ClassLoader的loadClass()方法来加载class的,与此同时在loadClass中存在着三种加载策略,loadClass使用双亲委派模式。
所以Classloader就是用来动态加载Class文件到内存当中用的。
Java默认提供的三个ClassLoader
1.Bootstrap ClassLoader
称为启动类加载器,是Java类加载层次中最顶层的类加载器,负责加载JDK中的核心类库,预设上它负责搜寻JRE所在目录的classes或lib目录下(实际上是由系统参数sun.boot.class.path指定)。如:rt.jar、resources.jar、charsets.jar等,可通过如下程序获得该类加载器从哪些地方加载了相关的jar或class文件:
|
输出
|
2.Extension ClassLoader
称为扩展类加载器,负责加载Java的扩展类库,默认加载JAVA_HOME/jre/lib/ext/目下的所有jar(实际上是由系统参数java.ext.dirs指定)
3.App ClassLoader
称为系统类加载器,负责加载应用程序classpath目录下的所有jar和class文件(由系统参数java.class.path指定)
总结
Extension ClassLoader和App ClassLoader 这两个类加载器实际上都是继承了ClassLoader类,但是Bootstrap ClassLoader不继承自ClassLoader,因为它不是一个普通的Java类,底层由C++编写,已嵌入到了JVM内核当中,当JVM启动后,Bootstrap ClassLoader也随着启动,负责加载完核心类库后,并构造Extension ClassLoader和App ClassLoader类加载器,也就是说:
Bootstrap Loader会在JVM启动之后载入,之后它会载入ExtClassLoader并将ExtClassLoader的parent设为Bootstrap Loader,然后BootstrapLoader再加载AppClassLoader,并将AppClassLoader的parent设定为 ExtClassLoader。
ClassLoader加载类的原理
双亲委托加载模式
我们知道除了顶级的 Bootstrap Loader他的parent属性为null之外,其他的两个或者自定义的类加载器都是存在parent 的。
当一个ClassLoader实例需要加载某个类时,它会试图亲自搜索某个类之前,先把这个任务委托给它的父类加载器,这个过程是由上至下依次检查的,首先由最顶层的类加载器Bootstrap ClassLoader试图加载,如果没加载到,则把任务转交给Extension ClassLoader试图加载,如果也没加载到,则转交给App ClassLoader 进行加载,如果它也没有加载得到的话,则返回给委托的发起者,由它到指定的文件系统或网络等URL中加载该类。如果它们都没有加载到这个类时,则抛出ClassNotFoundException异常。否则将这个找到的类生成一个类的定义,并将它加载到内存当中,最后返回这个类在内存中的Class实例对象
为什么要使用双亲委托这种模型呢
java是一门具有很高的安全性的语言,使用这种加载策略的目的是为了:防止重载,父类如果已经找到了需要的类并加载到了内存,那么子类加载器就不需要再去加载该类。安全性问题。两个原因
JVM在搜索类的时候,又是如何判定两个class是相同的
- 类名是否相同
- 否由同一个类加载器实例加载
看一个例子
|
输出
|
结论
第一行结果说明:ClassLoaderTest的类加载器是AppClassLoader。
第二行结果说明:AppClassLoader的类加器是ExtClassLoader,即parent=ExtClassLoader。
第三行结果说明:ExtClassLoader的类加器是Bootstrap ClassLoader,因为Bootstrap ClassLoader不是一个普通的Java类,所以ExtClassLoader的parent=null,所以第三行的打印结果为null就是这个原因。
测试2
将ClassLoaderTest.class打包成ClassLoaderTest.jar,放到Extension ClassLoader的加载目录下(JAVA_HOME/jre/lib/ext),然后重新运行这个程序,得到的结果会是什么样呢?
输出
|
结果分析:
为什么第一行的结果是ExtClassLoader呢?
因为ClassLoader的委托模型机制,当我们要用ClassLoaderTest.class这个类的时候,AppClassLoader在试图加载之前,先委托给Bootstrcp ClassLoader,Bootstracp ClassLoader发现自己没找到,它就告诉ExtClassLoader,兄弟,我这里没有这个类,你去加载看看,然后Extension ClassLoader拿着这个类去它指定的类路径(JAVA_HOME/jre/lib/ext)试图加载,唉,它发现在ClassLoaderTest.jar这样一个文件中包含ClassLoaderTest.class这样的一个文件,然后它把找到的这个类加载到内存当中,并生成这个类的Class实例对象,最后把这个实例返回。所以ClassLoaderTest.class的类加载器是ExtClassLoader。
第二行的结果为null,是因为ExtClassLoader的父类加载器是Bootstrap ClassLoader。
测试3:
|
自定义ClassLoader
前言
实现自定义类加载的目的是,假设我们的类他不是存在特定的位置,可能是某个磁盘或者某个远程服务器上面,那么我们就需要自定义类加载器去加载这些类。
继承继承java.lang.ClassLoader
重写父类的findClass方法
在findClass()方法中调用defineClass()。
这个方法在编写自定义classloader的时候非常重要,它能将class二进制内容转换成Class对象,如果不符合要求的会抛出各种异常
注意: 一个ClassLoader创建时如果没有指定parent,那么它的parent默认就是AppClassLoader。
为什么不去重定义loadClass方法呢?其实也可以,但是loadClass方法内部已经实现了搜索类的策略。除非你是非常熟悉否则还是不建议这样去做。这里建议重载findClass方法,因为在loadClass中最后会去调用findClass方法去加载类。而且这个方法内部默认是空的。
分析loadClass方法
源码
|
大致意思如下:
|
自定义类加载器
|
测试类:
|
输出:
com.kingge.com.PersonalClassLoader@65b54208
其中HelloWorld.class文件的位置在于:
其实很多服务器都自定义了类加载器
用于加载web应用指定目录下的类库(jar或class),如:Weblogic、Jboss、tomcat等,下面我以Tomcat为例,展示该web容器都定义了哪些个类加载器:
下面以tomcat为例子
- 1、新建一个web工程httpweb
- 2、新建一个ClassLoaderServletTest,用于打印web容器中的ClassLoader层次结构
一下代码来自网上:
- 3、配置Servlet,并启动服务
|
运行截图:
总结
这种自定义的方式目的就是为了,能够控制类的加载流程,那么这种远程加载类的方式类似于我们常用的Hessian 来访问多个系统获取类