来自https://www.kancloud.cn/book/hx78/java/edit
Java类加载器的作用就是在运行时加载类。Java类加载器基于三个机制:委托、可见性和单一性。委托机制是指将加载一个类的请求交给父类加载器,如果这个父类加载器不能够找到或者加载这个类,那么再加载它。可见性的原理是子类的加载器可以看见所有的父类加载器加载的类,而父类加载器看不到子类加载器加载的类。单一性原理是指仅加载一个类一次,这是由委托机制确保子类加载器不会再次加载父类加载器加载过的类。正确理解类加载器能够帮你解决NoClassDefFoundError和java.lang.ClassNotFoundException,因为它们和类的加载相关。类加载器通常也是比较高级的Java面试中的重要考题,Java类加载器和工作原理以及classpath如何运作的经常被问到。Java面试题中也经常出现“一个类是否能被两个不同类加载器加载”这样的问题。这篇教程中,我们将学到类加载器是什么,它的工作原理以及一些关于类加载器的知识点。
### [](https://github.com/stephanietang/ImportNew/blob/master/Java/How%20ClassLoader%20Works%20in%20Java.md#%E4%BB%80%E4%B9%88%E6%98%AF%E7%B1%BB%E5%8A%A0%E8%BD%BD%E5%99%A8)什么是类加载器
类加载器是一个用来加载类文件的类。Java源代码通过javac编译器编译成类文件。然后JVM来执行类文件中的字节码来执行程序。类加载器负责加载文件系统、网络或其他来源的类文件。有三种默认使用的类加载器:Bootstrap类加载器、Extension类加载器和System类加载器(或者叫作Application类加载器)。每种类加载器都有设定好从哪里加载类。
* Bootstrap类加载器负责加载rt.jar中的JDK类文件,它是所有类加载器的父加载器。Bootstrap类加载器没有任何父类加载器,如果你调用String.class.getClassLoader(),会返回null,任何基于此的代码会抛出NUllPointerException异常。Bootstrap加载器被称为初始类加载器。
* 而Extension将加载类的请求先委托给它的父加载器,也就是Bootstrap,如果没有成功加载的话,再从jre/lib/ext目录下或者java.ext.dirs系统属性定义的目录下加载类。Extension加载器由sun.misc.Launcher$ExtClassLoader实现。
* 第三种默认的加载器就是System类加载器(又叫作Application类加载器)了。它负责从classpath环境变量中加载某些应用相关的类,classpath环境变量通常由-classpath或-cp命令行选项来定义,或者是JAR中的Manifest的classpath属性。Application类加载器是Extension类加载器的子加载器。通过sun.misc.Launcher$AppClassLoader实现。
除了Bootstrap类加载器是大部分由C来写的,其他的类加载器都是通过java.lang.ClassLoader来实现的。
总结一下,下面是三种类加载器加载类文件的地方:
1) Bootstrap类加载器 – JRE/lib/rt.jar
2) Extension类加载器 – JRE/lib/ext或者java.ext.dirs指向的目录
3) Application类加载器 – CLASSPATH环境变量, 由-classpath或-cp选项定义,或者是JAR中的Manifest的classpath属性定义.
[![](http://www.importnew.com/wp-content/uploads/2013/10/java_classloader_hierarchy.png "java_classloader_hierarchy")](http://www.importnew.com/?attachment_id=6582)
### [](https://github.com/stephanietang/ImportNew/blob/master/Java/How%20ClassLoader%20Works%20in%20Java.md#%E7%B1%BB%E5%8A%A0%E8%BD%BD%E5%99%A8%E7%9A%84%E5%B7%A5%E4%BD%9C%E5%8E%9F%E7%90%86-1)类加载器的工作原理
我之前已经提到过了,类加载器的工作原理基于三个机制:委托、可见性和单一性。这一节,我们来详细看看这些规则,并用一个实例来理解工作原理。下面显示的是类加载器使用委托机制的工作原理。
#### [](https://github.com/stephanietang/ImportNew/blob/master/Java/How%20ClassLoader%20Works%20in%20Java.md#%E5%A7%94%E6%89%98%E6%9C%BA%E5%88%B6)委托机制
当一个类加载和初始化的时候,类仅在有需要加载的时候被加载。假设你有一个应用需要的类叫作Abc.class,首先加载这个类的请求由Application类加载器委托给它的父类加载器Extension类加载器,然后再委托给Bootstrap类加载器。Bootstrap类加载器会先看看rt.jar中有没有这个类,因为并没有这个类,所以这个请求由回到Extension类加载器,它会查看jre/lib/ext目录下有没有这个类,如果这个类被Extension类加载器找到了,那么它将被加载,而Application类加载器不会加载这个类;而如果这个类没有被Extension类加载器找到,那么再由Application类加载器从classpath中寻找。记住classpath定义的是类文件的加载目录,而PATH是定义的是可执行程序如javac,java等的执行路径。
[![](http://www.importnew.com/wp-content/uploads/2013/10/Java-classloader-working.png "Java classloader working")](http://www.importnew.com/6581.html/java-classloader-working)
#### [](https://github.com/stephanietang/ImportNew/blob/master/Java/How%20ClassLoader%20Works%20in%20Java.md#%E5%8F%AF%E8%A7%81%E6%80%A7%E6%9C%BA%E5%88%B6)可见性机制
根据可见性机制,子类加载器可以看到父类加载器加载的类,而反之则不行。所以下面的例子中,当Abc.class已经被Application类加载器加载过了,然后如果想要使用Extension类加载器加载这个类,将会抛出java.lang.ClassNotFoundException异常。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
`package` `test;`
`import` `java.util.logging.Level;`
`import` `java.util.logging.Logger;`
`/**`
`* Java program to demonstrate How ClassLoader works in Java,`
`* in particular about visibility principle of ClassLoader.`
`*`
`* @author Javin Paul`
`*/`
`public` `class` `ClassLoaderTest {`
`public` `static` `void` `main(String args[]) {`
`try` `{ `
`//printing ClassLoader of this class`
`System.out.println(``"ClassLoaderTest.getClass().getClassLoader() : "`
`+ ClassLoaderTest.``class``.getClassLoader());`
`//trying to explicitly load this class again using Extension class loader`
`Class.forName(``"test.ClassLoaderTest"``,` `true`
`, ClassLoaderTest.``class``.getClassLoader().getParent());`
`}` `catch` `(ClassNotFoundException ex) {`
`Logger.getLogger(ClassLoaderTest.``class``.getName()).log(Level.SEVERE,` `null``, ex);`
`}`
`}`
`}`
|
输出:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
|
`ClassLoaderTest.getClass().getClassLoader() : sun.misc.Launcher$AppClassLoader@601bb1`
`16/08/2012 2:43:48 AM test.ClassLoaderTest main`
`SEVERE: null`
`java.lang.ClassNotFoundException: test.ClassLoaderTest`
`at java.net.URLClassLoader$1.run(URLClassLoader.java:202)`
`at java.security.AccessController.doPrivileged(Native Method)`
`at java.net.URLClassLoader.findClass(URLClassLoader.java:190)`
`at sun.misc.Launcher$ExtClassLoader.findClass(Launcher.java:229)`
`at java.lang.ClassLoader.loadClass(ClassLoader.java:306)`
`at java.lang.ClassLoader.loadClass(ClassLoader.java:247)`
`at java.lang.Class.forName0(Native Method)`
`at java.lang.Class.forName(Class.java:247)`
`at test.ClassLoaderTest.main(ClassLoaderTest.java:29)`
|
#### [](https://github.com/stephanietang/ImportNew/blob/master/Java/How%20ClassLoader%20Works%20in%20Java.md#%E5%8D%95%E4%B8%80%E6%80%A7%E6%9C%BA%E5%88%B6)单一性机制
根据这个机制,父加载器加载过的类不能被子加载器加载第二次。虽然重写违反委托和单一性机制的类加载器是可能的,但这样做并不可取。你写自己的类加载器的时候应该严格遵守这三条机制。
### [](https://github.com/stephanietang/ImportNew/blob/master/Java/How%20ClassLoader%20Works%20in%20Java.md#%E5%A6%82%E4%BD%95%E6%98%BE%E5%BC%8F%E7%9A%84%E5%8A%A0%E8%BD%BD%E7%B1%BB)如何显式的加载类
Java提供了显式加载类的API:Class.forName(classname)和Class.forName(classname, initialized, classloader)。就像上面的例子中,你可以指定类加载器的名称以及要加载的类的名称。类的加载是通过调用java.lang.ClassLoader的loadClass()方法,而loadClass()方法则调用了findClass()方法来定位相应类的字节码。在这个例子中Extension类加载器使用了java.net.URLClassLoader,它从JAR和目录中进行查找类文件,所有以”/”结尾的查找路径被认为是目录。如果findClass()没有找到那么它会抛出java.lang.ClassNotFoundException异常,而如果找到的话则会调用defineClass()将字节码转化成类实例,然后返回。
### [](https://github.com/stephanietang/ImportNew/blob/master/Java/How%20ClassLoader%20Works%20in%20Java.md#%E4%BB%80%E4%B9%88%E5%9C%B0%E6%96%B9%E4%BD%BF%E7%94%A8%E7%B1%BB%E5%8A%A0%E8%BD%BD%E5%99%A8)什么地方使用类加载器
类加载器是个很强大的概念,很多地方被运用。最经典的例子就是AppletClassLoader,它被用来加载Applet使用的类,而Applets大部分是在网上使用,而非本地的操作系统使用。使用不同的类加载器,你可以从不同的源地址加载同一个类,它们被视为不同的类。J2EE使用多个类加载器加载不同地方的类,例如WAR文件由Web-app类加载器加载,而EJB-JAR中的类由另外的类加载器加载。有些服务器也支持热部署,这也由类加载器实现。你也可以使用类加载器来加载数据库或者其他持久层的数据。
以上是关于类加载器的工作原理。我们已经知道了委托、可见性以及单一性原理,这些对于调试类加载器相关问题时至关重要。这些对于Java程序员和架构师来说都是必不可少的知识。
原文链接: [Javarevisited](http://javarevisited.blogspot.hk/2012/12/how-classloader-works-in-java.html) 翻译: [ImportNew.com ](http://www.importnew.com/)- [唐小娟](http://www.importnew.com/author/tangxiaojuan)
译文链接: [http://www.importnew.com/6581.html](http://www.importnew.com/6581.html)
[ **转载请保留原文出处、译者和译文链接。**]
- JVM
- 深入理解Java内存模型
- 深入理解Java内存模型(一)——基础
- 深入理解Java内存模型(二)——重排序
- 深入理解Java内存模型(三)——顺序一致性
- 深入理解Java内存模型(四)——volatile
- 深入理解Java内存模型(五)——锁
- 深入理解Java内存模型(六)——final
- 深入理解Java内存模型(七)——总结
- Java内存模型
- Java内存模型2
- 堆内内存还是堆外内存?
- JVM内存配置详解
- Java内存分配全面浅析
- 深入Java核心 Java内存分配原理精讲
- jvm常量池
- JVM调优总结
- JVM调优总结(一)-- 一些概念
- JVM调优总结(二)-一些概念
- VM调优总结(三)-基本垃圾回收算法
- JVM调优总结(四)-垃圾回收面临的问题
- JVM调优总结(五)-分代垃圾回收详述1
- JVM调优总结(六)-分代垃圾回收详述2
- JVM调优总结(七)-典型配置举例1
- JVM调优总结(八)-典型配置举例2
- JVM调优总结(九)-新一代的垃圾回收算法
- JVM调优总结(十)-调优方法
- 基础
- Java 征途:行者的地图
- Java程序员应该知道的10个面向对象理论
- Java泛型总结
- 序列化与反序列化
- 通过反编译深入理解Java String及intern
- android 加固防止反编译-重新打包
- volatile
- 正确使用 Volatile 变量
- 异常
- 深入理解java异常处理机制
- Java异常处理的10个最佳实践
- Java异常处理手册和最佳实践
- Java提高篇——对象克隆(复制)
- Java中如何克隆集合——ArrayList和HashSet深拷贝
- Java中hashCode的作用
- Java提高篇之hashCode
- 常见正则表达式
- 类
- 理解java类加载器以及ClassLoader类
- 深入探讨 Java 类加载器
- 类加载器的工作原理
- java反射
- 集合
- HashMap的工作原理
- ConcurrentHashMap之实现细节
- java.util.concurrent 之ConcurrentHashMap 源码分析
- HashMap的实现原理和底层数据结构
- 线程
- 关于Java并发编程的总结和思考
- 40个Java多线程问题总结
- Java中的多线程你只要看这一篇就够了
- Java多线程干货系列(1):Java多线程基础
- Java非阻塞算法简介
- Java并发的四种风味:Thread、Executor、ForkJoin和Actor
- Java中不同的并发实现的性能比较
- JAVA CAS原理深度分析
- 多个线程之间共享数据的方式
- Java并发编程
- Java并发编程(1):可重入内置锁
- Java并发编程(2):线程中断(含代码)
- Java并发编程(3):线程挂起、恢复与终止的正确方法(含代码)
- Java并发编程(4):守护线程与线程阻塞的四种情况
- Java并发编程(5):volatile变量修饰符—意料之外的问题(含代码)
- Java并发编程(6):Runnable和Thread实现多线程的区别(含代码)
- Java并发编程(7):使用synchronized获取互斥锁的几点说明
- Java并发编程(8):多线程环境中安全使用集合API(含代码)
- Java并发编程(9):死锁(含代码)
- Java并发编程(10):使用wait/notify/notifyAll实现线程间通信的几点重要说明
- java并发编程-II
- Java多线程基础:进程和线程之由来
- Java并发编程:如何创建线程?
- Java并发编程:Thread类的使用
- Java并发编程:synchronized
- Java并发编程:Lock
- Java并发编程:volatile关键字解析
- Java并发编程:深入剖析ThreadLocal
- Java并发编程:CountDownLatch、CyclicBarrier和Semaphore
- Java并发编程:线程间协作的两种方式:wait、notify、notifyAll和Condition
- Synchronized与Lock
- JVM底层又是如何实现synchronized的
- Java synchronized详解
- synchronized 与 Lock 的那点事
- 深入研究 Java Synchronize 和 Lock 的区别与用法
- JAVA编程中的锁机制详解
- Java中的锁
- TreadLocal
- 深入JDK源码之ThreadLocal类
- 聊一聊ThreadLocal
- ThreadLocal
- ThreadLocal的内存泄露
- 多线程设计模式
- Java多线程编程中Future模式的详解
- 原子操作(CAS)
- [译]Java中Wait、Sleep和Yield方法的区别
- 线程池
- 如何合理地估算线程池大小?
- JAVA线程池中队列与池大小的关系
- Java四种线程池的使用
- 深入理解Java之线程池
- java并发编程III
- Java 8并发工具包漫游指南
- 聊聊并发
- 聊聊并发(一)——深入分析Volatile的实现原理
- 聊聊并发(二)——Java SE1.6中的Synchronized
- 文件
- 网络
- index
- 内存文章索引
- 基础文章索引
- 线程文章索引
- 网络文章索引
- IOC
- 设计模式文章索引
- 面试
- Java常量池详解之一道比较蛋疼的面试题
- 近5年133个Java面试问题列表
- Java工程师成神之路
- Java字符串问题Top10
- 设计模式
- Java:单例模式的七种写法
- Java 利用枚举实现单例模式
- 常用jar
- HttpClient和HtmlUnit的比较总结
- IO
- NIO
- NIO入门
- 注解
- Java Annotation认知(包括框架图、详细介绍、示例说明)