`
weiwu83
  • 浏览: 191365 次
  • 来自: ...
社区版块
存档分类
最新评论

Java类加载原理解析第一篇(上)

    博客分类:
  • JAVA
阅读更多
1       基本信息
摘要:
每个java开发人员对java.lang.ClassNotFoundExcetpion这个异常肯定都不陌生,这背后就涉及到 了java技术体系中的类加载。Java的类加载机制是java技术体系中比较核心的部分,虽然和大部分开发人 员直接打交道不多,但是对其背后的机理有一定理解有助于排查程序中出现的类加载失败等技术问题,对 理解java虚拟机的连接模型和java语言的动态性都有很大帮助。
由于关于java类加载的内容较多,所以打算分三篇文章简述一下:
第一篇:类加载,类加载器,双亲委派,自定义类加载器
第二篇:插件环境 Bundle类加载器
第三篇:线程上下文类加载器
分类:开发技术->J2EE
标签:Java类加载 类加载器 双亲委派机制 自定义类加载器
作者:朱兴  
2       虚拟机类加载器结构简述Java
2.1    JVM三种预定义类 型类加载器
我们首先看一下JVM预定义的三种类型类加载器,当一个 JVM 启动的时候,Java 缺省开始使用如下 三种类型类装入器:
启动(Bootstrap)类加载器:引导类装入器是用本地代码实 现的类装入器,它负责将 <Java_Runtime_Home>/lib 下面的类库加载到内存中。由于引导类加载 器涉及到虚拟机本地实现细节,开发者无法直接获取到启动类加载器的引用,所以不允许直接通过引用进 行操作。
标准扩展(Extension)类加载器:扩展类加载器是由 Sun 的 ExtClassLoader(sun.misc.Launcher$ExtClassLoader) 实现的。它负责将 < Java_Runtime_Home >/lib/ext 或者由系统变量 java.ext.dir 指定位置中的类库加载到内存中。开 发者可以直接使用标准扩展类加载器。
系统(System)类加载器:系统类加载器是由 Sun 的 AppClassLoader(sun.misc.Launcher$AppClassLoader)实现的。它负责将系统类路径(CLASSPATH)中 指定的类库加载到内存中。开发者可以直接使用系统类加载器。
除了以上列举的三种类加载器,还有一种比较特殊的类型就是线程上下文类加载器 ,这个将在后面单独介绍。
2.2    类加载双亲委派机 制介绍和分析
       在这里,需要着重说明的是,JVM在加载类 时默认采用的是双亲委派机制。通俗的讲,就是某个特定的类加载器在接到加载类的 请求时,首先将加载任务委托给父类加载器,依次递归,如果父类加载器可以完成类加载任务,就成功返 回;只有父类加载器无法完成此加载任务时,才自己去加载。关于虚拟机默认的双亲委派机制,我们可以 从系统类加载器和标准扩展类加载器为例作简单分析。
             图一   标准扩展类加载器继承层次图
             图二  系统类加载器继承层次图
       通过图一和图二我们可以看出,类加载器 均是继承自java.lang.ClassLoader抽象类。我们下面我们就看简要介绍一下java.lang.ClassLoader中几 个最重要的方法:
//加载指定名称(包括包名)的二进制类型,供用户调用的接口
public Class<?> loadClass(String name) throws ClassNotFoundException{//…}
//加载指定名称(包括包名)的二进制类型,同时指定是否解析(但是,这里的resolve参数不一定 真正能达到解析的效果~_~),供继承用
protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException{//…}
//findClass方法一般被loadClass方法调用去加载指定名称类,供继承用
protected Class<?> findClass(String name) throws ClassNotFoundException {//…}
//定义类型,一般在findClass方法中读取到对应字节码后调用,可以看出不可继承(说明:JVM已 经实现了对应的具体功能,解析对应的字节码,产生对应的内部数据结构放置到方法区,所以无需覆写, 直接调用就可以了)
protected final Class<?> defineClass(String name, byte[] b, int off, int len)
throws ClassFormatError{//…}
 
      通过进一步分析标准扩展类加载器 (sun.misc.Launcher$ExtClassLoader)和系统类加载器(sun.misc.Launcher$AppClassLoader)的代码 以及其公共父类(java.net.URLClassLoader和java.security.SecureClassLoader)的代码可以看出,都 没有覆写java.lang.ClassLoader中默认的加载委派规则---loadClass(…)方法。既然这样,我 们就可以通过分析java.lang.ClassLoader中的loadClass(String name)方法的代码就可以分析出虚拟 机默认采用的双亲委派机制到底是什么模样:

  1. public Class<?> loadClass(String name)throws ClassNotFoundException {
  2.             return loadClass(name, false);
  3. }
  4. protected synchronized Class<?> loadClass(String name, boolean resolve)
  5.              &nbs p;    throws ClassNotFoundException {
  6.             // 首 先判断该类型是否已经被加载
  7.             Class c = findLoadedClass(name);
  8.             if (c == null) {
  9.              &nbs p;    //如果没有被加载,就委托给父类加载或者委派给启动类加载器加载
  10.              &nbs p;    try {
  11.              &nbs p;          if (parent != null) {
  12. //如果存在父类加载器,就委派给父类加载器加载
  13.              &nbs p;                 c = parent.loadClass(name, false);
  14.              &nbs p;          } else {
  15. //如果不存在父类加载器,就检查是否是由启动类加载器加载的类,通过调用本地方法 native Class findBootstrapClass(String name)
  16.              &nbs p;                 c = findBootstrapClass0(name);
  17.              &nbs p;          }
  18.              &nbs p;    } catch (ClassNotFoundException e) {
  19.             // 如 果父类加载器和启动类加载器都不能完成加载任务,才调用自身的加载功能
  20.              &nbs p;          c = findClass(name);
  21.              &nbs p;    }
  22.             }
  23.             if (resolve) {
  24.              &nbs p;    resolveClass(c);
  25.             }
  26.             return c;
  27.       }


    通过上面的代码分析,我们可以对JVM采用的双 亲委派类加载机制有了更感性的认识,下面我们就接着分析一下启动类加载器、标准扩展类加载器和系统 类加载器三者之间的关系。可能大家已经从各种资料上面看到了如下类似的一幅图片:
 
    图三  类加载器默认委派关 系图
上面图片给人的直观印象是系统类加载器的父类加载器是标准扩展类加载器,标准扩 展类加载器的父类加载器是启动类加载器,下面我们就用代码具体测试一下:

  1. 示例代码:
  2. public static void main(String[] args) {
  3. try {
  4. System.out.println(ClassLoader.getSystemClassLoader());
  5. System.out.println(ClassLoader.getSystemClassLoader().getParent();
  6. System.out.println(ClassLoader.getSystemClassLoader().getParent().getParent ());
  7. } catch (Exception e) {
  8. e.printStackTrace();
  9. }
  10. }

说明:通过java.lang.ClassLoader.getSystemClassLoader()可以直接获取到系统类加载器。

  1. 代码输出如下:
  2. sun.misc.Launcher$AppClassLoader@197d257
  3. sun.misc.Launcher$ExtClassLoader@7259da
  4. null

    通过以上的代码输出,我们可以判定系统类加载器的父加载 器是标准扩展类加载器,但是我们试图获取标准扩展类加载器的父类加载器时确得到了null,就是说标准 扩展类加载器本身强制设定父类加载器为null。我们还是借助于代码分析一下:
     我们首先看一下java.lang.ClassLoader抽象类中默认实 现的两个构造函数:

  1. protected ClassLoader() {
  2. SecurityManager security = System.getSecurityManager();
  3. if (security != null) {
  4. security.checkCreateClassLoader();
  5. }
  6. //默认将父类加载器设置为系统类加载器,getSystemClassLoader()获取系统类加载器
  7. this.parent = getSystemClassLoader();
  8. initialized = true;
  9. }
  10. protected ClassLoader(ClassLoader parent) {
  11. SecurityManager security = System.getSecurityManager();
  12. if (security != null) {
  13. security.checkCreateClassLoader();
  14. }
  15. //强制设置父类加载器
  16. this.parent = parent;
  17. initialized = true;
  18. }

    我们再看一下ClassLoader抽象类中parent成员的声明:

  1. // The parent class loader for delegation
  2. private ClassLoader parent;

声明为私有变量的同时并没有对外提供可供派生类访问的public或者protected设置器接口(对应的 setter方法),结合前面的测试代码的输出,我们可以推断出:
1.               系统类加载器(AppClassLoader)调用ClassLoader(ClassLoader parent)构造函数将父 类加载器设置为标准扩展类加载器(ExtClassLoader)。(因为如果不强制设置,默认会通过调用 getSystemClassLoader()方法获取并设置成系统类加载器,这显然和测试输出结果不符。)
2.                扩展类加载器(ExtClassLoader)调用ClassLoader(ClassLoader parent)构造函数将父 类加载器设置为null。(因为如果不强制设置,默认会通过调用getSystemClassLoader()方法获取并设置 成系统类加载器,这显然和测试输出结果不符。)
     现在我们可能会有这样的疑问:扩展类加载器 (ExtClassLoader)的父类加载器被强制设置为null了,那么扩展类加载器为什么还能将加载任务委派给 启动类加载器呢?
 
  
             图四 标准扩展类加载器和系统类加载器成员大纲视图
 
  
            图 五 扩展类加载器和系统类加载器公共父类成员大纲视图
    通过图四和图五可以看出,标准扩展类加载器和系统类加载器 及其父类(java.net.URLClassLoader和java.security.SecureClassLoader)都没有覆写 java.lang.ClassLoader中默认的加载委派规则---loadClass(…)方法。有关 java.lang.ClassLoader中默认的加载委派规则前面已经分析过,如果父加载器为null,则会调用本地方 法进行启动类加载尝试。所以,图三中,启动类加载器、标准扩展类加载器和系统类加载器之间的委派关 系事实上是仍就成立的。(在后面的用户自定义类加载器部分,还会做更深入的分析)。
2.3    类加载双亲委派示
以上已经简要介绍了虚拟机默认使用的启动类加载器、标准扩展类加载器和系统类加载器,并以三 者为例结合JDK代码对JVM默认使用的双亲委派类加载机制做了分析。下面我们就来看一个综合的例子。首 先在eclipse中建立一个简单的java应用工程,然后写一个简单的JavaBean如下:

  1. package classloader.test.bean;
  2. public class TestBean {
  3. public TestBean() {}
  4. }

在现有当前工程中另外建立一测试类(ClassLoaderTest.java)内容如下:
测试一:

  1. public class ClassLoaderTest {
  2. public static void main(String[] args) {
  3. try {
  4. //查看当前系统类路径中包含的路径条目
  5. System.out.println(System.getProperty("java.class.path"));
  6. //调用加载当前类的类加载器(这里即为系统类加载器)加载TestBean
  7. Class typeLoaded = Class.forName("classloader.test.bean.TestBean");
  8. //查看被加载的TestBean类型是被那个类加载器加载的
  9. System.out.println(typeLoaded.getClassLoader());
  10. } catch (Exception e) {
  11. e.printStackTrace();
  12. }
  13. }
  14. }

对应的输出如下:
D:\EOS4HW2\dev\Study\ClassLoaderTest\bin
(说明:当前类路径默认的含有的一个条目就是工程的输出目录)
测试二:
将当前工程输出目录下的…/classloader/test/bean/TestBean.class打包进 test.jar剪贴到< Java_Runtime_Home >/lib/ext目录下(现在工程输出目录下 和JRE扩展目录下都有待加载类型的class文件)。再运行测试一测试代码,结果如下:
D:\EOS4HW2\dev\Study\ClassLoaderTest\bin
对比测试一和测试二,我们明显可以验证前面说的双亲委派机制,系统类加载器在接到加载 classloader.test.bean.TestBean类型的请求时,首先将请求委派给父类加载器(标准扩展类加载器), 标准扩展类加载器抢先完成了加载请求。
    测试三:
test.jar拷贝一份到< Java_Runtime_Home >/lib下,运行测试代码, 输出如下:
D:\EOS4HW2\dev\Study\ClassLoaderTest\bin
   测试三和测试二输出结果一致。那就是说,放置到< Java_Runtime_Home >/lib目录下的TestBean对应的class字节码并没有被加载,这其实和前面讲的双 亲委派机制并不矛盾。虚拟机出于安全等因素考虑,不会加载< Java_Runtime_Home >/lib存在的陌生类开发者通过将要加载 的非JDK自身的类放置到此目录下期待启动类加载器加载是不可能 的。做个进一步验证,删除< Java_Runtime_Home >/lib/ext目录下和工程输出目录下的 TestBean对应的class文件,然后再运行测试代码,则将会有ClassNotFoundException异常抛出。有关这 个问题,大家可以在java.lang.ClassLoader中的loadClass(String name, boolean resolve)方法中设置 相应断点运行测试三进行调试,会发现findBootstrapClass0()会抛出异常,然后在下面的findClass方法 中被加载,当前运行的类加载器正是扩展类加载器(sun.misc.Launcher$ExtClassLoader),这一点可以 通过JDT中变量视图查看验证。
分享到:
评论

相关推荐

    Java类加载器.pdf

    其中,`Class.forName()`方法有两种重载形式,第一个只接受类名作为参数,第二个允许控制是否初始化类以及指定类加载器。如果只使用一个参数的`Class.forName()`方法,那么它会默认使用调用者的类加载器并初始化类。...

    Java类加载内幕详细讲解

    这意味着多个线程同时加载同一个类时,只会有一个线程成功加载该类,其他线程会等待第一个线程加载完成后再继续执行。 #### 八、总结 深入了解Java类加载机制对于开发者来说至关重要。通过掌握类加载的过程、类...

    Java类加载原理浅析

    1. **加载**:这是类加载的第一步,类加载器根据类的全限定名(包括包名和类名)找到对应的.class文件。这个过程可能涉及到从硬盘、网络或其他数据源读取字节流。在Java中,文件系统和JAR文件是默认的类查找路径。 ...

    深入研究Java类加载机制 深入研究Java类加载机制

    Java类加载机制是Java程序运行的第一步,它对于理解Java虚拟机(JVM)的行为至关重要。类加载过程涉及到类的加载、链接(验证、准备、解析)、初始化等阶段,并且这一过程是由类加载器系统完成的。 #### 二、类加载...

    Java的类加载器

    Bootstrap ClassLoader是JVM启动时的第一个类加载器,负责加载JDK的`&lt;JAVA_HOME&gt;/jre/lib`目录下的核心类库。Extension ClassLoader加载`&lt;JAVA_HOME&gt;/jre/lib/ext`目录下的扩展类库。AppClassLoader(也叫System ...

    Java类加载及SPI机制.pdf

    即如果一个类加载器收到了类加载的请求,它首先不会自己尝试去加载这个类,而是将加载请求委托给父类加载器去完成,每一层都是如此,只有当父类加载器在它的搜索范围中没有找到所需的类时,子类加载器才会尝试自己去...

    Java类加载器的详解

    1. **引导类加载器(Bootstrap ClassLoader)**:这是JVM自带的第一类加载器,负责加载JRE核心库,如rt.jar、charsets.jar等,这些位于JDK安装目录下的jre/lib/下的核心类库。 2. **扩展类加载器(Extension ...

    Java类加载器学习总结.pdf

    1. Bootstrap ClassLoader(启动类加载器):它是用C++实现的,是Java类加载层次中最顶层的类加载器,主要负责加载Java运行时环境的核心类库,例如`java.*`包下的类。由于是用C++实现的,它不是Java类,因此在Java中...

    揭示Java类加载内幕(code)

    在本篇文章中,我们将深入探讨Java类加载器的工作原理,以及其在代码执行中的作用。 首先,我们从基础开始。Java类加载器分为三个主要类型:Bootstrap ClassLoader、Extension ClassLoader和AppClassLoader。...

    java深度历险,介绍java类的加载

    1. **加载**:这是类加载的第一步,JVM通过类加载器找到对应的.class文件。类加载器分为引导类加载器、扩展类加载器和应用程序类加载器,它们按照双亲委派模型工作,保证了类的唯一性。 2. **验证**:验证是确保....

    Java类加载及SPI机制.zip

    Java类加载机制是Java运行时环境的核心组成部分,它负责将.class文件从磁盘加载到内存中,转换为可执行的Java对象。理解类加载机制对于优化应用性能、深入理解JVM工作原理至关重要。SPI(Service Provider Interface...

    通过面试题解析 Java 类加载机制

    加载阶段是类加载过程的第一个阶段。在这个阶段,JVM 的主要目的是将字节码从各个位置(网络、磁盘等)转化为二进制字节流加载到内存中,接着会为这个类在 JVM 的方法区创建一个对应的 Class 对象,这个 Class 对象...

    java class reload

    1. **Java ClassLoader原理** - Java程序在运行时,类的加载是由ClassLoader负责的。默认情况下,当类被加载后,除非应用停止,否则ClassLoader不会重新加载该类。 - Java的双亲委托模型(Parent Delegation Model...

    JAVA提高第七篇 类加载器解析

    1. **Bootstrap ClassLoader**:引导类加载器,它是JVM的基石,由C++实现,不遵循Java的类加载机制。它负责加载JDK的核心库,如rt.jar,这些库位于JRE的lib目录下的bootstrap类路径中。 2. **Extension ClassLoader...

    Java ClassLoader原理

    Java中的类加载器是第一类对象,这意味着它们可以像其他对象一样被创建和使用。这赋予了开发人员对类加载过程的完全控制权。例如,开发人员可以创建自定义类加载器来指定类文件的远程位置、为从特定源加载的类分配...

    java class文件解析

    4. **类加载机制**:在Java中,类的加载、验证、准备、解析和初始化都是由类加载器完成的。双亲委派模型是类加载的一种策略,确保了类的唯一性,防止不同类加载器加载相同的类。 5. **反编译工具**:如`javap`命令...

    Java动态加载类小实验.pdf

    ### Java动态加载类知识点总结 Java动态加载类的概念,是指在运行时通过反射机制加载类的...通过以上知识点的梳理和示例分析,我们可以了解到Java动态加载类的基本原理和应用场景,以及如何在实际编程中应用这一技术。

    java文件的加载

    无论是基于Java本身的类加载机制还是通过Spring等框架提供的便捷方式,都能够有效地实现资源文件的加载与管理。在实际开发中,理解这些机制的工作原理是非常重要的,它能够帮助我们更高效地进行开发工作。

    java虚拟机的详细原理

    1. **类加载子系统**(Class Loader Subsystem):负责将Java类加载到内存中,并对它们进行验证、解析和初始化。每个Java应用程序都有一个类加载器子系统,该子系统包括: - **引导类加载器**(Bootstrap Class ...

Global site tag (gtag.js) - Google Analytics