java类加载器(java.lang.ClassLoader) 与 Class.forName()


引文
http://www.ibm.com/developerworks/cn/java/j-lo-classloader/

相关引文:深入剖析 Eclipse 类装入器



熟悉此过程,有助于理解和排查  
  java.lang.ClassNotFoundException 异常( 方法  loadClass() 抛出 );
java.lang.NoClassDefFoundError 异常( 方法  defineClass() 抛出
    java.lang.ClassCastException 异常

下面首先介绍 java中类装载器,然后再简单分析 类装载过程

类加载器的树状组织结构

Java 中的类加载器大致可以分成两类,一类是系统提供的,另外一类则是由 Java 应用开发人员编写的。系统提供的类加载器主要有下面三个:

  • 引导类加载器(bootstrap class loader):它用来加载 Java 的核心库,是用原生代码来
  • 实现的,并不继承自 java.lang.ClassLoader
  • 扩展类加载器(extensions class loader):它用来加载 Java 的扩展库。Java 虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载 Java 类。
  • 系统类加载器(system class loader):它根据 Java 应用的类路径(CLASSPATH)来加载 Java 类。一般来说,Java 应用的类都是由它来完成加载的。可以通过 
  • ClassLoader.getSystemClassLoader()来获取它。

除了系统提供的类加载器以外,开发人员可以通过继承 java.lang.ClassLoader类的方式实现自己的类加载器,以满足一些特殊的需求。

除了引导类加载器之外,所有的类加载器都有一个父类加载器。通过 表 1中给出的 getParent()方法可以得到。对于系统提供的类加载器来说,系统类加载器的父类加载器是扩展类加载器,而扩展类加载器的父类加载器是引导类加载器;对于开发人员编写的类加载器来说,其父类加载器是加载此类加载器 Java 类的类加载器。因为类加载器 Java 类如同其它的 Java 类一样,也是要由类加载器来加载的。一般来说,开发人员编写的类加载器的父类加载器是系统类加载器。类加载器通过这种方式组织起来,形成树状结构。树的根节点就是引导类加载器。图 1中给出了一个典型的类加载器树状组织结构示意图,其中的箭头指向的是父类加载器。

图 1. 类加载器树状组织结构示意图


类装入器首先判断要求它装入的类是否与过去装入的类相同。如果相同,就返回上次返回的类(即保存在缓存中的类)。如果不是,就把装入类的机会交给父类。这两步递归地以深度优先的方式重复。如果父类返回 null(或抛出 ClassNotFoundException),那么类装入器会在自己的路径中寻找类的源。

因为父类类装入器总是先得到装入类的机会,所以类装入器装入的类最靠近根。这意味着所有核心引导类都是由引导装入器装入的,这就保证装入了类(例如 java.lang.Object)的正确版本。这也可以让类装入器看到自己或父类或祖先装入的类,但是不能看到子女装入的类。

图 1 显示了三个标准的类装入器及装载路径:

图 1. 类装入器委托模型


类加载器的代理模式


类加载器在尝试自己去查找某个类的字节代码并定义它时,会先代理给其父类加载器,由父类加载器先去尝试加载这个类,依次类推。在介绍代理模式背后的动机之前,首先需要说明一下 Java 虚拟机是如何判定两个 Java 类是相同的。Java 虚拟机不仅要看类的全名是否相同,还要看加载此类的类加载器是否一样。只有两者都相同的情况,才认为两个类是相同的。即便是同样的字节代码,被不同的类加载器加载之后所得到的类,也是不同的






类加载器与 Web 容器()

Tomcat类加载机制理解参考文章:
对于运行在 Java EE™容器中的 Web 应用来说,类加载器的实现方式与一般的 Java 应用有所不同。不同的 Web 容器的实现方式也会有所不同。以 Apache Tomcat 来说,每个 Web 应用都有一个对应的类加载器实例。 该类加载器也使用代理模式,所不同的是它是首先尝试去加载某个类,如果找不到再代理给父类加载器。这与一般类加载器的顺序是相反的。这是 Java Servlet 规范中的推荐做法,其目的是使得 Web 应用自己的类的优先级高于 Web 容器提供的类。这种代理模式的一个例外是:Java 核心库的类是不在查找范围之内的。这也是为了保证 Java 核心库的类型安全。

绝大多数情况下,Web 应用的开发人员不需要考虑与类加载器相关的细节。下面给出几条简单的原则:

  • 每个 Web 应用自己的 Java 类文件和使用的库的 jar 包,分别放在 WEB-INF/classes和 WEB-INF/lib目录下面。

  • 多个应用共享的 Java 类文件和 jar 包,分别放在 Web 容器指定的由所有 Web 应用共享的目录下面
  • 当出现找不到类的错误时,检查当前类的类加载器和当前线程的上下文类加载器是否正确。




下面介绍一种加载类的方法:Class.forName

Class.forName

Class.forName是一个静态方法,同样可以用来加载类。该方法有两种形式:Class.forName(String name, boolean initialize, ClassLoader loader)和 Class.forName(String className)。第一种形式的参数 name表示的是类的全名;initialize表示是否初始化类loader表示加载时使用的类加载器。第二种形式则相当于设置了参数 initialize的值为 trueloader的值为当前类的类加载器。Class.forName的一个很常见的用法是在加载数据库驱动的时候。如Class.forName("org.apache.derby.jdbc.EmbeddedDriver").newInstance()用来加载 Apache Derby 数据库的驱动。

案例1:
  1. Class.forName("com.mysql.jdbc.Driver");   
  2. String url = "jdbc:mysql://127.0.0.1/test?useUnicode=true&characterEncoding=utf-8";   
  3. String user = "";   
  4. String psw = "";   
  5. Connection con = DriverManager.getConnection(url,user,psw);

其实这句话:Class.forName("com.mysql.jdbc.Driver");可以暂用一下代替:
com.mysql.jdbc.Driver driver = new com.mysql.jdbc.Driver();//这样会创建一个这个类的实例

其目的主要是执行Driver的初始化,运行期静态块儿:
com.mysql.jdbc.Driver的源码:



案例2、另一种方法



,第一个输出的是  Test7 类的类加载器,即系统类加载器。它是  sun.misc.Launcher$AppClassLoader 类的实例;第二个输出的是扩展类加载器,是  sun.misc.Launcher$ExtClassLoader 类的实例。需要注意的是这里并没有输出引导类加载器,这是由于有些 JDK 的实现对于父类加载器是引导类加载器的情况, getParent() 方法返回  null

类装入的阶段

类的装入实际上可以分成三个阶段:装入、链接和初始化。

虽然不是所有的问题,但至少大多数与类装入有关的问题都可以追溯到在这三个阶段中发生的某个问题。所以,对于每一阶段的深入理解有助于对类装入问题的诊断。图 2 显示了这三个阶段:

图 2. 类装入的阶段

装入 阶段包括:找到必要的类(通过查找每个类路径)并装入字节码。在 JVM 中,装入阶段为类对象提供了非常基本的内存结构。在这一阶段不处理方法、字段和引用的其他类。所以,类还不能使用。

链接 是三个阶段中最复杂的一个。可以把它分成三个主要阶段:

  • 字节码验证。 类装入器对于类的字节码要做许多检测,以确保格式正确、行为正确。
  • 类准备。 这个阶段准备代表每个类中定义的字段、方法和实现接口所必需的数据结构。
  • 解析。 在这个阶段,类装入器装入类所引用的其他所有类。可以用许多方式引用类:
    • 超类
    • 接口
    • 字段
    • 方法签名
    • 方法中使用的本地变量

初始化 阶段,类中包含的静态初始化器都被执行。在这一阶段末尾,静态字段被初始化成默认值。

在这三个阶段末尾,类被完整地装入,可以使用了。请注意可以用惰性方式执行类装入,所以类装入过程的某些部分可能在第一次使用类的时候才执行,而不是在装入时执行。

显式装入与隐式装入

类装入的方式有两种 —— 显式 或 隐式,两者之间有些细微差异。

显式 类装入发生在使用以下方法调用装入的类的时候:

  • cl.loadClass()cl 是 java.lang.ClassLoader 的实例)
  • Class.forName()(启动的类装入器是当前类定义的类装入器)

当调用其中一个方法的时候,指定的类(以类名为参数)由类装入器装入。如果类已经装入,那么只是返回一个引用;否则,装入器会通过委托模型装入类。

隐式 类装入发生在由于引用、实例化或继承导致装入类的时候(不是通过显式方法调用)。在每种情况下,装入都是在幕后启动的,JVM 会解析必要的引用并装入类。与显式类装入一样,如果类已经装入了,那么只是返回一个引用;否则,装入器会通过委托模型装入类。

类的装入通常组合了显式和隐式类装入。例如,类装入器可能先显式地装入一个类,然后再隐式地装入它引用的所有类











  • 7
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值