Class代表它的作用对象是类,Loader代表它的功能是加载,那么ClassLoader就是把一个以.class结尾的文件以JVM能识别的存储形式加载到内存中。
一、核心方法
1、loadClass方法
代码语言:javascript复制protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
//自上往下检查类是否已经被加载
Class c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
//双亲委派加载
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
//双亲加载没有找到,则继续检查其他类加载器是否加载过
//自定义类加载器加载
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
//findClass需要Override
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
一开始,先调用findLoadedClass(name)去检查该类是否已经被加载过了,加载过的类不会重复加载。如果是第一次加载(向上传递任务),分为两种:1、parent != null ;当前类加载器不是BootStrapClassLoader,直接调用父类加载器的loadClass来加载该类。2、parent == null 当前加载器是BootStrapClassLoader,则自己尝试加载该类。
如果上层的加载器都没有成功加载。(该类没有在上面任何类加载器的类命名空间下。)任务向下委托,最终回到最初接到加载任务的类加载器,调用findClass()来尝试加载该类。如果我们自定义一个ClassLoader,可以直接覆盖该方法(findClass)来进行定制加载逻辑
可以看出,只要每次优先调用parent.loadClass()来执行加载逻辑,就实现了双亲委派模型,同时发现,想破坏双亲委派模型也可以,覆盖loadClass方法,并且里面不调用parent.loadClass方法即可。
二、总结
JVM启动时产生三个类加载器:Bootstrap ClassLoader,Extension ClassLoader,App ClassLoader。 Bootstrap ClassLoader负责加载核心类库(jdk lib下的类),用C 实现,java代码中显示为null;Extension ClassLoader负责加载lib/ext下的类;App ClassLoader负责加载ClassPath下的类。 其中Bootstrap ClassLoader是Extention ClassLoader的父加载器,Extention ClassLoader是APP ClassLoader的父加载器,但不是父子类关系。 由源代码可见双亲委托机制在加载类时类似递归先回溯到Bootstrap ClassLoader,再Extension ClassLoader,再App ClassLoader…此机制一个重要原因是安全原因:防止不安全类的加载进来,比如重新实现String类Boolean类等,加载时回溯到Bootstrap ClassLoader会发现已经加载过了,在不会加载自定义的String类取代掉JDK String。判断两个类是不是同一个类,除了名字相同还要是加载器类相同才可以。
三、拓展
1、类加载过程
一个类在被使用之前,会经历class文件生成—>加载—>连接—>初始化等阶段。这些阶段组合起来为完整的类加载过程,其中加载阶段主要完成三件事:
- 通过类的全限定名来获取定义次类的二进制字节流
- 将该二进制字节流定义的静态数据结构转换成方法区的运行时数据结构
- 在内存中生成一个代表该类的Class对象,供外部通过该对象来获取类的元数据信息
上述类加载的大部分阶段是由JVM控制的,但JVM对于加载阶段有些没有做强制限制,比如从哪获取class文件,以及如何加载class都可以由用户自定义实现方案,正是由于JVM提供的这些入口,衍生出了动态字节码,applet, OSGI,类隔离(ali-Pandora)等技术。
正常情况下一个ClassLoader需要两个必要属性
1、parent:用于指明当前ClassLoader的父类加载器 2、url:类命名空间,用于指明当前ClassLoader从哪里加载class文件
2、ClassLoader分类:
1、系统自带的ClassLoader 2、用户自定义类加载ClassLoader
其中系统自带的ClassLoader分成三类: 1.1、 BootstrapClassloader: 启动类加载器, 加载JAVA_HOME/lib/目录下的所有jar包, 而该目录下的主要放系统核心类库,比如包含Object, String等类的rt.jar就是由该类加载器加载进内存的. 1.2、ExtClassloader: 扩展类加载器, 加载JAVA_HOME/jre/lib/ext/目录下的所有jar包或者由java.ext.dirs系统属性指定的jar包。放入这个目录下的jar包对所有AppClassloader都是可见的(双亲委派模型 ). 1.3、AppClassloader | SystemClassLoader: 应用类加载器, 也叫系统类加载器. 是我们平时接触最密切的类加载器, 主要负责加载classpath下的所有类. 平时我们用maven构建项目时, 添加进pom文件中的依赖, 最后都是由idea帮我们直接把依赖关联的jar包放到classPath下, 并在运行时, 由AppClassloader帮我们把这些jar包中的class加载到内存中.
总结下来,BootstrapClassloader和ExtClassLoader主要负责加载JDK相关的系统类。AppClassLoader来处理我们classpath下的所有类。
3、疑问:既然Class使用前需要ClassLoader加载到内存,那ClassLoader本身是由谁加载的?
BootStrapClassLoader并不是用java写的,而是用C ,他属于JVM的一部分,在JVM启动时就会被连带启动起来,所以在不需要被某一个ClassLoader加载。ExtClassLoader和AppClassLoader是定义在rt.jar中,所以是有BootStrapClassLoader加载进来的。
4、疑问:一个类什么时候被加载:
简单来说,程序第一次使用某个类的时候,就会调用ClassLoader去加载该类。比如:new 一个类的实例对象或者使用反射Class.forName(…)来获取Class对象(类对象)都会触发类加载。
5、疑问:ClassLoader有很多种,我们仅知道他们都有各自的加载路径,而它们是什么关系,如何协作配合的呢?(双亲委派模型是什么):
要理解双亲委派模型,首先要了解各个类加载器的层次关系。如图,从下往上看,每个类的parent是在它上方与它相邻的类加载器,比如最下面的CustomClassLoader代表用户自定义类加载器,它的parent是AppClassLoader,AppClassLoader的parent是ExtClassLoader,ExtClassLoader的parent是BootStrapClassLoader,这种递推关系到BootStrapClassLoader终止,因为它是顶层类加载器,它没有parent。
注意:这种层次关系是组合形式来实现的,并不是传统理解的继承,如:CustomClassLoader的parent是AppClassLoader,但CustomClassLoader并不是AppClassLoader的子类,仅仅是把自己类中的parent属性指向AppClassLoader
6、疑问:当需要加载一个类是,各个类加载器它们是如何协同工作的呢?
当一个类加载器想要加载一个类时,它会先把该加载任务委托给它的父类加载器(parent),而不是自己先尝试加载,以此类推,父类加载器又会委托自己的父类加载器去执行加载任务,直到最顶层的BootStrapClassLoader为止,如果BootStrapClassLoader在自己类空间(上面提到的URL)找到了该类的Class文件,就会加载该类到内存中,如果找不到,BootStrapClassLoader会把任务向下传递回ExtClassLoader,让它去尝试加载该类,如果ExtClassLoader在自己的类空间中找到该类的class文件则会加载该类到内存中,如果找不到,则ExtClassLoader会把任务继续向下传递,以此类推,直到某个类加载器在自己的类命名空间中找到该类的Class文件,并加载到内存中
可以看出,从下往上传递任务,秉着优先让上层加载器去加载的原则,而从上到下传递任务,秉着优先让自己去加载的原则
好处:可以让每个类具有优先级层次,越靠上方的类加载器加载进来的类层级就会越高,在程序中可见的范围就会越大。如Object类,因为处于rt.jar中,根据双亲委派模型的执行规律,会被最上面的BootStrapClassLoader加载进来,也就能保证该类在程序中只会被加载一次,也只会存在一份,所有的程序使用的都是这一份,不会出现自己定义一个Object类,被自己的ClassLoader给加载进来的情况,因为rt.jar中的Object会被优先加载。