https://www.ibm.com/developerworks/cn/java/j-lo-classloader/

类加载器(class loader)

用来加载 Java 类到 Java 虚拟机中

  • 一般来说,Java 虚拟机使用 Java 类的方式如下:

  Java 源程序(.java 文件)在经过 Java 编译器编译之后就被转换成 Java 字节代码(.class 文件)。类加载器负责读取 Java 字节代码,并转换成 java.lang.Class类的一个实例;每个这样的实例用来表示一个 Java 类。通过此实例的 newInstance()方法就可以创建出该类的一个对象。

  • 实际的情况可能更加复杂,比如 Java 字节代码可能是通过工具动态生成的,也可能是通过网络下载的

  基本上所有的类加载器都是 java.lang.ClassLoader类的一个实例。

java.lang.ClassLoader

  • 根据一个指定的类的名称,找到或者生成其对应的字节代码,然后从这些字节代码中定义出一个 Java 类,即 java.lang.Class类的一个实例;
方法 说明
getParent() 返回该类加载器的父类加载器。
loadClass(String name) 加载名称为 name的类,返回的结果是 java.lang.Class类的实例。
findClass(String name) 查找名称为 name的类,返回的结果是 java.lang.Class类的实例。
findLoadedClass(String name) 查找名称为 name的已经被加载过的类,返回的结果是 java.lang.Class类的实例。
defineClass(String name, byte[] b, int off, int len) 把字节数组 b中的内容转换成 Java 类,返回的结果是 java.lang.Class类的实例。这个方法被声明为 final的。
resolveClass(Class<?> c) 链接指定的 Java 类。

表示类名称的 name参数的值是类的二进制名称。需要注意的是内部类的表示,如 com.example.Sample$1和 com.example.Sample$Inner等表示方式。

  • 负责加载 Java 应用所需的资源,如图像文件和配置文件等。

类加载器的树状组织结构

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

  • 系统提供的类加载器主要有下面3个:

  引导类加载器(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类的方式实现自己的类加载器,以满足一些特殊的需求。

  除了引导类加载器之外,所有的类加载器都有一个父类加载器。通过 getParent()方法可以得到。

  对于系统提供的类加载器来说,系统类加载器的父类加载器是扩展类加载器,而扩展类加载器的父类加载器是引导类加载器;

  对于开发人员编写的类加载器来说,其父类加载器是加载此类加载器 Java 类的类加载器。

  因为类加载器 Java 类如同其它的 Java 类一样,也是要由类加载器来加载的。一般来说,开发人员编写的类加载器的父类加载器是系统类加载器。

public class ClassLoaderTree { 

   public static void main(String[] args) {
ClassLoader loader = ClassLoaderTree.class.getClassLoader();
//有些 JDK 的实现对于父类加载器是引导类加载器的情况,getParent()方法返回 null
while (loader != null) {
System.out.println(loader.toString());
loader = loader.getParent();
}
}
} //输出
sun.misc.Launcher$AppClassLoader@9304b1 //系统类加载器实例
sun.misc.Launcher$ExtClassLoader@190d11 //扩展类加载器实例

类加载器的代理模式

类加载器在尝试自己去查找某个类的字节代码并定义它时,会先代理给其父类加载器,由父类加载器先去尝试加载这个类,依次类推。

  • Java 虚拟机是如何判定两个 Java 类是相同的

Java 虚拟机不仅要看类的全名是否相同,还要看加载此类的类加载器(定义加载器)是否一样。只有两者都相同的情况,才认为两个类是相同的

  • 代理模式是为了保证 Java 核心库的类型安全

通过代理模式,对于 Java 核心库的类的加载工作由引导类加载器来统一完成,保证了 Java 应用所使用的都是同一个版本的 Java 核心库的类,是互相兼容的。

  • 相同名称的类可以并存在 Java 虚拟机中,只需要用不同的类加载器来加载它们即可。

不同类加载器加载的类之间是不兼容的,这就相当于在 Java 虚拟机内部创建了一个个相互隔离的 Java 类空间。

加载类的过程

加载 + 启动加载

  • 真正完成类的加载工作是通过调用加载器的 defineClass来实现的;而启动类的加载工过程是通过调用加载器的 loadClass来实现的。

  前者称为一个类的定义加载器(defining loader),后者称为初始加载器(initiating loader)。

  在 Java 虚拟机判断两个类是否相同的时候,使用的是类的定义加载器。

  • 对于一个类加载器实例来说,相同全名的类只加载一次

线程上下文类加载器

  类 java.lang.Thread中的方法 getContextClassLoader()和 setContextClassLoader(ClassLoader cl)用来获取和设置线程的上下文类加载器。

  如果没有通过 setContextClassLoader(ClassLoader cl)方法进行设置的话,线程将继承其父线程的上下文类加载器。

  Java 应用运行的初始线程的上下文类加载器是系统类加载器。在线程中运行的代码可以通过此类加载器来加载类和资源。

  • 在 SPI 接口的代码中使用线程上下文类加载器,就可以成功的加载到 SPI 实现的类。

一种加载类的方法 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 数据库的驱动。

开发自己的类加载器

  • 文件系统类加载器

  加载存储在文件系统上的 Java 字节代码,通过 类 FileSystemClassLoader 的 defineClass()方法来把这些字节代码转换成 java.lang.Class类的实例。

  • 网络类加载器

  类 NetworkClassLoader负责通过网络下载 Java 类字节代码并定义出 Java 类。

类加载器与 Web 容器

  • 运行在 Java EE™容器中的 Web 应用来说,类加载器的实现方式与一般的 Java 应用有所不同

  每个 Web 应用都有一个对应的类加载器实例。

  该类加载器也使用代理模式,所不同的是它是首先尝试去加载某个类,如果找不到再代理给父类加载器。这与一般类加载器的顺序是相反的。这是 Java Servlet 规范中的推荐做法,其目的是使得 Web 应用自己的类的优先级高于 Web 容器提供的类。

  这种代理模式的一个例外是:Java 核心库的类是不在查找范围之内的。这也是为了保证 Java 核心库的类型安全。

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

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

多个应用共享的 Java 类文件和 jar 包,分别放在 Web 容器指定的由所有 Web 应用共享的目录下面。

当出现找不到类的错误时,检查当前类的类加载器和当前线程的上下文类加载器是否正确。

类加载器与 OSGi

OSGi™是 Java 上的动态模块系统。它为开发人员提供了面向服务和基于组件的运行环境,并提供标准的方式用来管理软件的生命周期。

  • OSGi 中的每个模块(bundle)都包含 Java 包和类

OSGi 中的每个模块都有对应的一个类加载器。它负责加载模块自己包含的 Java 包和类。当它需要加载 Java 核心库的类时(以 java开头的包和类),它会代理给父类加载器(通常是启动类加载器)来完成。当它需要加载所导入的 Java 类时,它会代理给导出此 Java 类的模块来完成加载。

  • 面提供几条比较好的建议

如果一个类库只有一个模块使用,把该类库的 jar 包放在模块中,在 Bundle-ClassPath中指明即可。

如果一个类库被多个模块共用,可以为这个类库单独的创建一个模块,把其它模块需要用到的 Java 包声明为导出的。其它模块声明导入这些类。

如果类库提供了 SPI 接口,并且利用线程上下文类加载器来加载 SPI 实现的 Java 类,有可能会找不到 Java 类。如果出现了 NoClassDefFoundError异常,首先检查当前线程的上下文类加载器是否正确。通过 Thread.currentThread().getContextClassLoader()就可以得到该类加载器。该类加载器应该是该模块对应的类加载器。如果不是的话,可以首先通过 class.getClassLoader()来得到模块对应的类加载器,再通过 Thread.currentThread().setContextClassLoader()来设置当前线程的上下文类加载器。

018.Java类加载器的更多相关文章

  1. java笔记--理解java类加载器以及ClassLoader类

    类加载器概述: java类的加载是由虚拟机来完成的,虚拟机把描述类的Class文件加载到内存,并对数据进行校验,解析和初始化,最终形成能被java虚拟机直接使用的java类型,这就是虚拟机的类加载机制 ...

  2. java类加载器深入研究

    看了下面几篇关于类的加载器的文章,豁然开朗.猛击下面的地址开始看吧. Java类加载原理解析      深入探讨 Java 类加载器 分析BootstrapClassLoader/ExtClassLo ...

  3. 深入探讨 Java 类加载器

    转自:http://www.ibm.com/developerworks/cn/java/j-lo-classloader/ 类加载器(class loader)是 Java™中的一个很重要的概念.类 ...

  4. JAVA 类加载器 第14节

    JAVA 类加载器 第14节 今天我们将类加载机制5个阶段中的第一个阶段,加载,又叫做装载.为了阅读好区分,以下都叫做装载. 装载的第一步就是要获得二进制的字节流,它可以从读.class文件获得,也可 ...

  5. 深入探讨 Java 类加载器[转]

    原文地址:http://www.ibm.com/developerworks/cn/java/j-lo-classloader/index.html 类加载器(class loader)是 Java™ ...

  6. 转载:深入探讨 Java 类加载器

    转载地址 : http://www.ibm.com/developerworks/cn/java/j-lo-classloader/ 深入探讨 Java 类加载器 类加载器(class loader) ...

  7. Java类加载器详解

    title: Java类加载器详解date: 2015-10-20 18:16:52tags: JVM--- ## JVM三种类型的类加载器- 我们首先看一下JVM预定义的三种类型类加载器,当一个 J ...

  8. 一篇文章读懂Java类加载器

    Java类加载器算是一个老生常谈的问题,大多Java工程师也都对其中的知识点倒背如流,最近在看源码的时候发现有一些细节的地方理解还是比较模糊,正好写一篇文章梳理一下. 关于Java类加载器的知识,网上 ...

  9. Java类加载器的工作原理

    Java类加载器的作用就是在运行时加载类.Java类加载器基于三个机制:委托.可见性和单一性.委托机制是指将加载一个类的请求交给父类加载 器,如果这个父类加载器不能够找到或者加载这个类,那么再加载它. ...

随机推荐

  1. union联合体学习

    union,中文名“联合体.共用体”,在某种程度上类似结构体struct的一种数据结构,共用体(union)和结构体(struct)同样可以包含很多种数据类型和变量. 不过区别也挺明显: 结构体(st ...

  2. 关于SCANF接受的一些总结

    当C语言中SCANF函数接受到非指定类型的参数时,如:定义为整形输入为字符串,函数会返回一个失败的BOOL类型,以此可以判断是否输入的为指定类型. 当发生一次输入错误的时候,如果想再次进行输入则需要使 ...

  3. mysql更改时区

    set global time_zone = '+8:00'; ##修改mysql全局时区为北京时间,即我们所在的东8区 set time_zone = '+8:00'; ##修改当前会话时区 flu ...

  4. Hadoop-2.3.0-cdh5.0.1完全分布式环境搭建(NameNode,ResourceManager HA)

    编写不易,转载请注明(http://shihlei.iteye.com/blog/2084711)! 说明 本文搭建Hadoop CDH5.0.1 分布式系统,包括NameNode ,Resource ...

  5. Linux ifconfig-etho文件参数详解

    1.ifcfg-eth0文件参数详解 DEVICE 网卡名称/网络接口的名称BOOTPROTO 系统启动地址协议 常用参数: none:不使用启动地址协议,none禁止DHCP bootp:BOOTP ...

  6. Windows日志为什么要把它转成Syslog呢?

    有的朋友会问,好好的Windows日志为什么要把它转成Syslog呢?呵呵,当Windows服务器比较少的时候,我们是不需要这样做的.但试想如果你管理着成千上百台的Windows机器,你会一台一台的登 ...

  7. Unity---UNet学习(2)----简单mmo游戏实现

    1.实现步骤 新建空物体Controller,添加Network Manager.HUD组件. 创建Player模型,添加Inentity组件. Player添加脚本控制移动,只有当为本地用户才执行. ...

  8. Spring Boot整合实战Spring Security JWT权限鉴权系统

    目前流行的前后端分离让Java程序员可以更加专注的做好后台业务逻辑的功能实现,提供如返回Json格式的数据接口就可以.像以前做项目的安全认证基于 session 的登录拦截,属于后端全栈式的开发的模式 ...

  9. web版聊天框

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  10. RABC(Role-Based Access Control) 基于角色的权限访问控制

    基于角色的权限访问控制(Role-Based Access Control),通过角色绑定权限,然后给用户划分角色.在web应用中,可以将权限理解为url,一个权限对应一个url. 使用thinkph ...