一、classLoader加载机制
JVM类加载器加载机制并不是继承关系,而是委派关系,之前意识中一直当作是继承关系。自定义classloader的时候,通常会传一个parent classLoader,看见parent就想当然的理解成继承关系,直到在写code时,发现不同classLoader的用各自的parent加载的class,都是用的相同的classLoader实例,然后才把这个潜意识改变过来。
1.默认
JVM默认类加载机制是自下而上的委派方式,即加载某个class时,先从parent classLoader加载,如果该parent还有parent,则依次向上递归,若还加载不到,此时自己再加载。例如,我们通过命令行+classpath参数启动的程序,就是该流程,下面引用java.lang.ClassLoader的loadClass方法代码:
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// 查看是否被加载过
Class c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {//若parent loader不空,在委托parent加载
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) {
// 若还没有加载到,此时自己再加载
long t1 = System.nanoTime();
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;
}
}
2.定制
某些场合下,优先从本地加载class,若加载不到,再委托父类加载。 JavaEE规范则推荐每个类加载模块先加载本类加载的内容,若果加载不到,才尝试从parent中加载。比如tomcat,jetty就是采用这种加载机制。下面贴出jetty的webAppClassLoader的加载class片段:
protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException
{
Class<?> c= findLoadedClass(name);
ClassNotFoundException ex= null;
boolean tried_parent= false;
//判断是否是系统类,即AppClassLoader加载的
boolean system_class=_context.isSystemClass(name);
//判断是否是jetty本身类,即jetty代码
boolean server_class=_context.isServerClass(name);
if (system_class && server_class)
{
return null;
}
//isParentLoaderPriority标示是否先委托parent加载,默认false
if (c == null && _parent!=null && (_context.isParentLoaderPriority() || system_class) && !server_class)
{
tried_parent= true;
try
{
c= _parent.loadClass(name);
if (LOG.isDebugEnabled())
LOG.debug("loaded " + c);
}
catch (ClassNotFoundException e)
{
ex= e;
}
}
if (c == null)
{
try
{
//自己加载各个war包下的类
c= this.findClass(name);
}
catch (ClassNotFoundException e)
{
ex= e;
}
}
if (c == null && _parent!=null && !tried_parent && !server_class )
c= _parent.loadClass(name);
if (c == null)
if (resolve)
resolveClass(c);
if (LOG.isDebugEnabled())
LOG.debug("loaded " + c+ " from "+c.getClassLoader());
return c;
}
二、classLoader加载体系
第一节中提到2种类加载机制,一个种是自下而上的委派模式,另外一种则是定制,优先从本地类加载模块加载。下面就介绍几种类加载器:
1.bootstrap classLoader
bootstrap加载器是用C++语言写的,它是在Java虚拟机启动后初始化的,它主要负责加载%JAVA_HOME%/jre/lib,-Xbootclasspath参数指定的路径以及%JAVA_HOME%/jre/classes中的类。
2.extension classLoader
Bootstrap loader加载ExtClassLoader,并且将ExtClassLoader的父加载器设置为Bootstrap loader。ExtClassLoader是用Java写的,具体来说就是 sun.misc.Launcher$ExtClassLoader,ExtClassLoader主要加载%JAVA_HOME%/jre/lib/ext,此路径下的所有classes目录以及java.ext.dirs系统变量指定的路径中类库。
3.system app classLoader
AppClassLoader也是用Java写成的,它的实现类是 sun.misc.Launcher$AppClassLoader,另外我们知道ClassLoader中有个getSystemClassLoader方法,此方法返回的正是AppclassLoader.AppClassLoader主要负责加载classpath所指定的位置的类或者是jar文档,它也是Java程序默认的类加载器。
4.customer classLoader
就是开发自定义的classLoader,通常它的parent classLoader一般是AppClassLoader。
三、classLoader实践与样例
在开发中常见的几种classLoader错误
1.NoClassDefFoundError
classloader没有找到该类的定义,一般是该类的jar包没有加载到classpath路径下。
2.NoSuchMethodError
NoSuchMethodError情况在开发过程中也会经常遇到。在项目中该类所在的jar包有多个版本,比如maven工程中,jar包版本的间接依赖导致版本仲裁的时候,选择的版本可能并不是我们所需要的版本,就可能会出现这个错误,通过maven tree把jar包依赖树打印出来排除掉即可。
3.ClassCastException
在JVM中,确定一个类的实例类型是:类全名+类加载器。那么出现ClassCastException一般是类型转换的时候遇到问题,即class实例类型不对。在双亲委派模式下一般不会出现,classLoader都交给系统处理。当遇到我们自定义的classLoader的时候,就可能会出现这种问题。
4.LinkageError
通常这种错误比较难排查,其实和ClassCastException类型本质一样,下面是我摘自他人的一个样例:
public class HandleUtils {
public void m(Param param) {
param.generate();
}
public void print(){
System.out.println("hello world!");
}
}
public class Param {
private String name;
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public Param(){
System.out.println("Param: "+Param.class.getClassLoader());
}
public Param2 generate() {
System.out.println("Param2: "+Param2.class.getClassLoader());
return new Param2();
}
// public Param generate() {
// return new Param();
// }
}
public class Param2 extends Param {
public Param2 generate() {
System.out.println("i am param2.");
return new Param2();
}
}
public class TestLinkError {
public static void main(String[] args) {
try {
TestLinkError.test();
} catch (Exception e) {
e.printStackTrace();
// System.out.println(e);
}
}
public static void test() throws Exception {
// cl1在加载HandleUtils和Param时将会使用AppClassLoader
URLClassLoader cl1 = new URLClassLoader(new URL[] { new File("target/test-classes").toURI().toURL() }, null) {
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
if ("com.murdock.classloader.linkageerror.HandleUtils".equals(name)) {
return ClassLoader.getSystemClassLoader().loadClass(name);
}
if ("com.murdock.classloader.linkageerror.Param".equals(name)) {
return ClassLoader.getSystemClassLoader().loadClass(name);
}
return super.loadClass(name);
}
};
ClassLoader.getSystemClassLoader().loadClass("com.murdock.classloader.linkageerror.Param2");
HandleUtils hu = (HandleUtils) cl1.loadClass("com.murdock.classloader.linkageerror.HandleUtils").newInstance();
hu.m((Param) cl1.loadClass("com.murdock.classloader.linkageerror.Param2").newInstance());
}
四、其他