转 Java Classloader机制解析

发表于11个月前(2014-05-09 11:36)   阅读(693) | 评论(0) 9人收藏此文章, 我要收藏
赞1

慕课网,程序员升职加薪神器,点击免费学习

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

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

JDK默认ClassLoader

JDK 默认提供了如下几种ClassLoader

  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程序默认的类加载器。

综上所述,它们之间的关系可以通过下图形象的描述:

双亲委托模型

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

  1. 当前ClassLoader首先从自己已经加载的类中查询是否此类已经加载,如果已经加载则直接返回原来已经加载的类。

    每个类加载器都有自己的加载缓存,当一个类被加载了以后就会放入缓存,等下次加载的时候就可以直接返回了。

  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

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

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

loadClass method implement

 
  1. public Class<?> loadClass(String name) throws ClassNotFoundException
  2.  {  
  3. return loadClass(name, false);
  4. }

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

Class loadClass(String name, boolean resolve)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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的实现如下:

  1.  protected Class<?> findClass(String name) throws ClassNotFoundException
  2.  {  
  3. throw new ClassNotFoundException(name);
  4. }

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

我们在写自己的ClassLoader的时候,如果想遵循双亲委托机制,则只需要override findClass.

3.defineClass

我们首先还是来看看defineClass的源码:

defineClass

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

从上面的代码我们看出此方法被定义为了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也打破了双亲委托机制。

转 Java Classloader机制解析的更多相关文章

  1. Java Classloader机制解析(转)

    做Java开发,对于ClassLoader的机制是必须要熟悉的基础知识,本文针对Java ClassLoader的机制做一个简要的总结.因为不同的JVM的实现不同,本文所描述的内容均只限于Hotspo ...

  2. Java Classloader机制解析

    做Java开发,对于ClassLoader的机制是必须要熟悉的基础知识,本文针对Java ClassLoader的机制做一个简要的总结.因为不同的JVM的实现不同,本文所描述的内容均只限于Hotspo ...

  3. 理解Java ClassLoader机制

    当JVM(Java虚拟机)启动时,会形成由三个类加载器组成的初始类加载器层次结构: bootstrap classloader                |       extension cla ...

  4. JAVA回调机制解析

    一.回调机制概述     回调机制在JAVA代码中一直遇到,但之前不懂其原理,几乎都是绕着走.俗话说做不愿意做的事情叫做突破,故诞生了该文章,算是新年的新气象,新突破!     回调机制是什么?其实回 ...

  5. 【JVM】深度分析Java的ClassLoader机制(源码级别)

    原文:深度分析Java的ClassLoader机制(源码级别) 为了更好的理解类的加载机制,我们来深入研究一下ClassLoader和他的loadClass()方法. 源码分析 public abst ...

  6. JDK源码解析之Java SPI机制

    1. spi 是什么 SPI全称Service Provider Interface,是Java提供的一套用来被第三方实现或者扩展的API,它可以用来启用框架扩展和替换组件. 系统设计的各个抽象,往往 ...

  7. Java ClassLoader加载机制理解

    今天看到了一篇介绍Java ClassLoader加载机器的文章, 才发觉一直来自己的肤浅, 好好地给补了一课, 不得不存档! 原文地址: http://www.blogjava.net/lhulcn ...

  8. ClassLoader类加载器 & Java类加载机制 & 破坏双亲委托机制

    ClassLoader类加载器 Java 中的类加载器大致可以分成两类: 一类是系统提供的: 引导类加载器(Bootstrap classloader):它用来加载 Java 的核心库(如rt.jar ...

  9. Java 类加载机制 ClassLoader Class.forName 内存管理 垃圾回收GC

    [转载] :http://my.oschina.net/rouchongzi/blog/171046 Java之类加载机制 类加载是Java程序运行的第一步,研究类的加载有助于了解JVM执行过程,并指 ...

随机推荐

  1. android开发游记:meterial design 5.0 开源控件整套合集 及使用demo

    android 的5.0公布不光google官方给出了一些新控件,同一时候还给出了一套符合material design风格的设计标准,这套标准将未来将覆盖google全部产品包括pc端,站点,移动端 ...

  2. 使用mescroll实现上拉加载与下拉刷新

    现在上拉加载与下拉刷新几乎已经是移动端必备功能之一了,自己实现一个太麻烦,但是好用的插件又非常少.之前看到网上很多人都在用iScroll,于是也尝试用它做了几个DEMO,但或多或少都有一些问题,比如这 ...

  3. Oracle的substr函数简单用法与substring区别

    substr(字符串,截取开始位置,截取长度) //返回截取的字 substr('Hello World',0,1) //返回结果为 'H'  *从字符串第一个字符开始截取长度为1的字符串 subst ...

  4. HUAWEI HiAI亮相Droidcon柏林2018开发者峰会 开启HiAI海外生态

    柏林时间6月25日到27日,华为HiAI亮相Droidcon柏林2018开发者峰会,有1200多位海外开发者参加了此次峰会,来自HUAWEI HiAI领域的多名专家携手Prisma和金山WPS,以“E ...

  5. php使用imagick模块实现图片缩放、裁剪、压缩示例

    PHP 使用Imagick模块 缩放,裁剪,压缩图片 包括gif图片 缩放 裁剪 复制代码代码如下: /**  * 图片裁剪  * 裁剪规则:  *   1. 高度为空或为零   按宽度缩放 高度自适 ...

  6. java多线程实现复制大文件

    有些开发的时候我们经常遇到这样一个问题,对大文件的处理.比如:日志文件.那么十几G的大文件.我们应该如何复制呢? 还有就是希望从本地和远程复制文件,文件都很大,10G级的如何办呢? 在这里我告诉你们, ...

  7. horizontalDragMaxWidth:0;就没有水平滚动条了

    jquery.jscrollpane.css JScrollPane工作所必须的基本的CSS样式.jquery.min.jsjQuery作为javascript库必须提前引入.jquery.mouse ...

  8. 自学宝典:10个学习Android开发的网站推荐

    1. Android Developers 作为一个Android开发者,官网的资料当然不可错过,从设计,培训,指南,文档,都不应该错过,在以后的学习过程中慢慢理解体会. 2. Android Gui ...

  9. j2EE的web.xml详解

    https://blog.csdn.net/changqing5818/article/details/49928231 https://www.cnblogs.com/ClassNotFoundEx ...

  10. 线性同余方程模板( A+C*x=B(mod D) )

    void extendgcd(long long a,long long b,long long &d,long long &x,long long &y) { ){d=a;x ...