classloader简介
ClassLoader的具体作用就是将class文件加载到jvm虚拟机中去,程序就可以正确运行了。但是,jvm启动的时候,并不会一次性加载所有的class文件,而是根据需要去动态加载。
classloader 有两种装载class的方式 (时机):
- 隐式:运行过程中,碰到new方式生成对象时,隐式调用classLoader到JVM
- 显式:通过class.forname()动态加载
类加载器的树状组织结构
Java 中的类加载器大致可以分成两类,一类是系统提供的,另外一类则是由 Java 应用开发人员编写的。系统提供
的类加载器主要有下面三个:
-引导类加载器(Bootstrap Classloader):它用来加载 Java 的核心库,是用原生代码来实现的,并不继承自 java.lang.ClassLoader 。
-扩展类加载器(Extensions Classloader):它用来加载 Java 的扩展库。Java 虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载 Java 类。
-系统类加载器(System Classloader):它根据 Java 应用的类路径(CLASSPATH)来加载 Java 类。一般来说,Java 应用的类都是由它来完成加载的。可以通过 ClassLoader.getSystemClassLoader() 来获取它。
除了系统提供的类加载器以外,开发人员可以通过继承 java.lang.ClassLoader 类的方式实现自己的类加载器,以满足一些特殊的需求。
一个ClassLoader创建时如果没有指定parent,那么它的parent默认就是AppClassLoader。
每个Thread都有一个相关联的ClassLoader,默认是AppClassLoader。并且子线程默认使用父线程的ClassLoader
除非子线程特别设置。
双亲委托
BootStrap Classloder
|
Extensions Classloader
|
System Classloader
/ \
Custom Classloader Webapp Classloader
一个类加载器查找class和resource时,是通过“委托模式”进行的,它首先判断这个class是不是已经加载成功,如果没有的话它并不是自己进行查找,而是先通过父加载器,然后递归下去,直到Bootstrap ClassLoader,如果Bootstrap classloader找到了,直接返回,如果没有找到,则一级一级返回,最后到达自身去查找这些对象。这种机制就叫做双亲委托
使用这种模型来组织类加载器之间的关系的好处: 主要是为了 安全性 ,避免用户自己编写的类动态替换 Java 的一些核心类,比如 String,同时也避免了 重复加载 ,因为 JVM 中区分不同类,不仅仅是根据类名,相同的 class 文件被不同的 ClassLoader 加载就是不同的两个类,如果相互转型的话会抛java.lang.ClassCaseException.
用序列描述一下:
- 一个AppClassLoader查找资源时,先看看缓存是否有,缓存有从缓存中获取,否则委托给父加载器。
- 递归,重复第1部的操作。
- 如果ExtClassLoader也没有加载过,则由Bootstrap ClassLoader出面,它首先查找缓存,如果没有找到的
话,就去找自己的规定的路径下,也就是 sun.mic.boot.class 下面的路径。找到就返回,没有找到,让子加
载器自己去找。 - Bootstrap ClassLoader如果没有查找成功,则ExtClassLoader自己在 java.ext.dirs 路径中去查找,查找成
功就返回,查找不成功,再向下让子加载器找。 - ExtClassLoader查找不成功,AppClassLoader就自己查找,在 java.class.path 路径下查找。找到就返回。
如果没有找到就让子类找,如果没有子类会怎么样?抛出各种异常。
常见加载类错误分析
ClassNotFoundExecption
ClassNotFoundExecption 异常是平常碰到的最多的。这个异常通常发生在显示加载类的时候。
显示加载一个类通常有:
-通过类 Class 中的 forName() 方法
-通过类 ClassLoader 中的 loadClass() 方法
-通过类 ClassLoader 中的 findSystemClass() 方法
出现这种错误其实就是当 JVM 要加载指定文件的字节码到内存时,并没有找到这个文件对应的字节码,也就是这个文件并不存在。解决方法就是检查在当前的 classpath 目录下有没有指定的文件。
NoClassDefFoundError
在JavaDoc中对NoClassDefFoundError的产生可能的情况就是使用new关键字、属性引用某个类、继承了某个接口或者类,以及方法的某个参数中引用了某个类,这时就会触发JVM或者类加载器实例尝试加载类型的定义,但是该定义却没有找到,影响了执行路径。换句话说,在编译时这个类是能够被找到的,但是在执行时却没有找到。
解决这个错误的方法就是确保每个类引用的类都在当前的classpath下面。
ClassCastException
该错误通常出现强制类型转换时出现这个错误。
NoSuchMethodError
NoSuchMethodError代表这个类型确实存在,但是一个不正确的版本被加载了。
平台类加载简介
我认为有了上一节classloader的介绍后,所谓的平台的类加载原理其实就是classloader那部分内容因为经常遇到jar包冲突的问题,小部分同事还经常将jar包因为类加载不到或不对,尝试性的将jar包放各种目录,以提一下类加载是有先后顺序的,有助于大家以后先定位问题再解决问题,而不是尝试解决后,还不清楚问题原因的本质,避免将来重蹈覆辙。
平台的类加载优先级
lib/ > applib/ > app/applib/ > app/模块/applib/ > ${HWORKDIR}/ > ${HWORKDIR}/app/
这里的模块指的是${HWORKDIR}/etc/app.list中的模块,一行一个模块
至于weblib,其实它不是我们的类加载器指定的路径,它真正被使用到的是webapp的lib/下通过软连接引用的,由于webapp可能较多,所以用weblib一个公共存放管理的目录可以很好的统一管理web类jar包
平台自定义类加载器
package com.xxx.loader;
import java.net.URL;
import java.net.URLClassLoader;
public class XxStandardClassLoader extends URLClassLoader {
public XxStandardClassLoader(URL repositories[]) {
super(repositories);
}
public XxStandardClassLoader(URL repositories[], ClassLoader parent) {
super(repositories, parent);
}
}
每一个ATR服务解析成java对象时,都会创建其独有的XxStandardClassLoader,也就是每一个ATR配置文件对应的服务,都有一个与其对应的不同与其他服务的类加载器。这样在每个线程执行这些服务的Process方法之前,处理引擎负责将当前线程的classloader替换为该服务的classloader,以便处理中的classloader为当前服务的classloader,当Process结束时,处理引擎又将classloader还原为之前的。这样可以做到服务间的类隔离,再结合类加载的优先顺序,还以做到同一个容器下,不同服务可以使用不同版本的类,这种方式也可以解决类冲突带来的问题。