Java Classloader机制解析

作Java开发,对于ClassLoader的机制是必需要熟悉的基础知识,本文针对Java ClassLoader的机制作一个简要的总结。由于不一样的JVM的实现不一样,本文所描述的内容均只限于Hotspot Jvm.java

本文将会从JDK默认的提供的ClassLoader,双亲委托模型,如何自定义ClassLoader以及Java中打破双亲委托机制的场景四个方面入手去讨论和总结一下。bootstrap

JDK默认ClassLoader

JDK 默认提供了以下几种ClassLoaderapi

  1. Bootstrp loader
    Bootstrp加载器是用C++语言写的,它是在Java虚拟机启动后初始化的,它主要负责加载%JAVA_HOME%/jre/lib,-Xbootclasspath参数指定的路径以及%JAVA_HOME%/jre/classes中的类。缓存

  1. ExtClassLoader  
    Bootstrp loader加载ExtClassLoader,而且将ExtClassLoader的父加载器设置为Bootstrp loader.ExtClassLoader是用Java写的,具体来讲就是 sun.misc.Launcher$ExtClassLoader,ExtClassLoader主要加载%JAVA_HOME%/jre/lib/ext,此路径下的全部classes目录以及java.ext.dirs系统变量指定的路径中类库。网络

  2. AppClassLoader
    Bootstrp loader加载完ExtClassLoader后,就会加载AppClassLoader,而且将AppClassLoader的父加载器指定为 ExtClassLoader。AppClassLoader也是用Java写成的,它的实现类是 sun.misc.Launcher$AppClassLoader,另外咱们知道ClassLoader中有个getSystemClassLoader方法,此方法返回的正是AppclassLoader.AppClassLoader主要负责加载classpath所指定的位置的类或者是jar文档,它也是Java程序默认的类加载器。app

综上所述,它们之间的关系能够经过下图形象的描述:jvm

双亲委托模型

Java中ClassLoader的加载采用了双亲委托机制,采用双亲委托机制加载类的时候采用以下的几个步骤:ide

  1. 当前ClassLoader首先从本身已经加载的类中查询是否此类已经加载,若是已经加载则直接返回原来已经加载的类。spa

    每一个类加载器都有本身的加载缓存,当一个类被加载了之后就会放入缓存,等下次加载的时候就能够直接返回了。.net

  2. 当前classLoader的缓存中没有找到被加载的类的时候,委托父类加载器去加载,父类加载器采用一样的策略,首先查看本身的缓存,而后委托父类的父类去加载,一直到bootstrp ClassLoader.

  3. 当全部的父类加载器都没有加载的时候,再由当前的类加载器加载,并将其放入它本身的缓存中,以便下次有加载请求的时候直接返回。

说到这里你们可能会想,Java为何要采用这样的委托机制?理解这个问题,咱们引入另一个关于Classloader的概念“命名空间”, 它是指要肯定某一个类,须要类的全限定名以及加载此类的ClassLoader来共同肯定。也就是说即便两个类的全限定名是相同的,可是由于不一样的 ClassLoader加载了此类,那么在JVM中它是不一样的类。明白了命名空间之后,咱们再来看看委托模型。采用了委托模型之后加大了不一样的 ClassLoader的交互能力,好比上面说的,咱们JDK本生提供的类库,好比hashmap,linkedlist等等,这些类由bootstrp 类加载器加载了之后,不管你程序中有多少个类加载器,那么这些类其实都是能够共享的,这样就避免了不一样的类加载器加载了一样名字的不一样类之后形成混乱。

如何自定义ClassLoader

Java除了上面所说的默认提供的classloader之外,它还允许应用程序能够自定义classloader,那么要想自定义classloader咱们须要经过继承java.lang.ClassLoader来实现,接下来咱们就来看看再自定义Classloader的时候,咱们须要注意的几个重要的方法:

1.loadClass 方法

loadClass method declare


public Class<?> loadClass(String name)  throws ClassNotFoundException

上面是loadClass方法的原型声明,上面所说的双亲委托机制的实现其实就实在此方法中实现的。下面咱们就来看看此方法的代码来看看它到底如何实现双亲委托的。

loadClass method implement


public Class<?> loadClass(String name) throws ClassNotFoundException
 {  
return loadClass(name, false);
}

从上面能够看出loadClass方法调用了loadcClass(name,false)方法,那么接下来咱们再来看看另一个loadClass方法的实现。

Class loadClass(String name, boolean resolve)


protected synchronized Class<?> loadClass(String name, boolean resolve)  throws ClassNotFoundException   
 {  // First, check if the class has already been loaded  Class c = findLoadedClass(name);
//检查class是否已经被加载过了  if (c == null)
 {     
 try {      
if (parent != null) {         
 c = parent.loadClass(name, false); //若是没有被加载,且指定了父类加载器,则委托父加载器加载。    
  } else {        
  c = findBootstrapClass0(name);//若是没有父类加载器,则委托bootstrap加载器加载      } 
     } catch (ClassNotFoundException e) {         
 // If still not found, then invoke findClass in order          
// to find the class.         
 c = findClass(name);//若是父类加载没有加载到,则经过本身的findClass来加载。      } 
 } 
 if (resolve) 
{     
 resolveClass(c); 
 }  
return c;
}

上面的代码,我加了注释经过注释能够清晰看出loadClass的双亲委托机制是如何工做的。 这里咱们须要注意一点就是public Class<?> loadClass(String name) throws ClassNotFoundException没有被标记为final,也就意味着咱们是能够override这个方法的,也就是说双亲委托机制是能够打破的。另外上面注意到有个findClass方法,接下来咱们就来讲说这个方法究竟是搞末子的。

2.findClass

咱们查看java.lang.ClassLoader的源代码,咱们发现findClass的实现以下:


 protected Class<?> findClass(String name) throws ClassNotFoundException
 {  
throw new ClassNotFoundException(name);
}

咱们能够看出此方法默认的实现是直接抛出异常,其实这个方法就是留给咱们应用程序来override的。那么具体的实现就看你的实现逻辑了,你能够从磁盘读取,也能够从网络上获取class文件的字节流,获取class二进制了之后就能够交给defineClass来实现进一步的加载。defineClass咱们再下面再来描述。 ok,经过上面的分析,咱们能够得出以下结论:

咱们在写本身的ClassLoader的时候,若是想遵循双亲委托机制,则只须要override findClass.

3.defineClass

咱们首先仍是来看看defineClass的源码:

defineClass


protected final Class<?> defineClass(String name, byte[] b, int off, int len)  
throws ClassFormatError
{     
 return defineClass(name, b, off, len, null);
}

从上面的代码咱们看出此方法被定义为了final,这也就意味着此方法不能被Override,其实这也是jvm留给咱们的惟一的入口,经过这个惟 一的入口,jvm保证了类文件必须符合Java虚拟机规范规定的类的定义。此方法最后会调用native的方法来实现真正的类的加载工做。

Ok,经过上面的描述,咱们来思考下面一个问题:
假如咱们本身写了一个java.lang.String的类,咱们是否能够替换调JDK自己的类?

答案是否认的。咱们不能实现。为何呢?我看不少网上解释是说双亲委托机制解决这个问题,其实不是很是的准确。由于双亲委托机制是能够打破的,你彻底能够本身写一个classLoader来加载本身写的java.lang.String类,可是你会发现也不会加载成功,具体就是由于针对java.*开头的类,jvm的实现中已经保证了必须由bootstrp来加载。

不遵循“双亲委托机制”的场景

上面说了双亲委托机制主要是为了实现不一样的ClassLoader之间加载的类的交互问题,被你们公用的类就交由父加载器去加载,可是Java中确实也存在父类加载器加载的类须要用到子加载器加载的类的状况。下面咱们就来讲说这种状况的发生。

Java中有一个SPI(Service Provider Interface)标准,使用了SPI的库,好比JDBC,JNDI等,咱们都知道JDBC须要第三方提供的驱动才能够,而驱动的jar包是放在咱们应 用程序自己的classpath的,而jdbc 自己的api是jdk提供的一部分,它已经被bootstrp加载了,那第三方厂商提供的实现类怎么加载呢?这里面JAVA引入了线程上下文类加载的概 念,线程类加载器默认会从父线程继承,若是没有指定的话,默认就是系统类加载器(AppClassLoader),这样的话当加载第三方驱动的时候,就可 以经过线程的上下文类加载器来加载。另外为了实现更灵活的类加载器OSGI以及一些Java app server也打破了双亲委托机制。

相关文章
相关标签/搜索