jvm classLoader architecture:

  1. Bootstrap ClassLoader/启动类加载器 
    主要负责jdk_home/lib目录下的核心 api 或 -Xbootclasspath 选项指定的jar包装入工作。
  2. Extension ClassLoader/扩展类加载器 
    主要负责jdk_home/lib/ext目录下的jar包或 -Djava.ext.dirs 指定目录下的jar包装入工作。
  3. System ClassLoader/系统类加载器 
    主要负责java -classpath/-Djava.class.path所指的目录下的类与jar包装入工作。
  4. User Custom ClassLoader/用户自定义类加载器(java.lang.ClassLoader的子类) 
    在程序运行期间, 通过java.lang.ClassLoader的子类动态加载class文件, 体现java动态实时类装入特性。

类加载器特性:

  1. 每个ClassLoader都维护了一份自己的名称空间, 同一个名称空间里不能出现两个同名的类。
  2. 为了实现java安全沙箱模型顶层的类加载器安全机制, java默认采用了 " 双亲委派的加载链 " 结构。

为什么要使用这种双亲委托模式呢?

  1. 因为这样可以避免重复加载,当父亲已经加载了该类的时候,就没有必要子ClassLoader再加载一次。
  2. 考虑到安全因素,我们试想一下,如果不使用这种委托模式,那我们就可以随时使用自定义的String来动态替代java核心api中定义类型,这 样会存在非常大的安全隐患,而双亲委托的方式,就可以避免这种情况,因为String已经在启动时被加载,所以用户自定义类是无法加载一个自定义的 ClassLoader。

java动态载入class的两种方式:

  1. implicit隐式,即利用实例化才载入的特性来动态载入class
  2. explicit显式方式,又分两种方式:
    1. java.lang.Class的forName()方法
    2. java.lang.ClassLoader的loadClass()方法
附注:从其他文章上摘取的资料(重温java之classloader体系结构(含hotswap)http://www.iteye.com/topic/136427)

java默认采用了 ” 双亲委派的加载链 ” 结构.

如下图:

Class Diagram:

类图中, BootstrapClassLoader是一个单独的java类, 其实在这里, 不应该叫他是一个java类。
因为, 它已经完全不用java实现了。

它是在jvm启动时, 就被构造起来的, 负责java平台核心库。(具体上面已经有介绍)

启动类加载实现 (其实我们不用关心这块, 但是有兴趣的, 可以研究一下 ):
bootstrap classLoader 类加载原理探索

自定义类加载器加载一个类的步骤 :

ClassLoader 类加载逻辑分析, 以下逻辑是除 BootstrapClassLoader 外的类加载器加载流程:

Java代码  
  1. // 检查类是否已被装载过
  2. Class c = findLoadedClass(name);
  3. if (c == null ) {
  4. // 指定类未被装载过
  5. try {
  6. if (parent != null ) {
  7. // 如果父类加载器不为空, 则委派给父类加载
  8. c = parent.loadClass(name, false );
  9. } else {
  10. // 如果父类加载器为空, 则委派给启动类加载加载
  11. c = findBootstrapClass0(name);
  12. }
  13. } catch (ClassNotFoundException e) {
  14. // 启动类加载器或父类加载器抛出异常后, 当前类加载器将其
  15. // 捕获, 并通过findClass方法, 由自身加载
  16. c = findClass(name);
  17. }
  18. }

用Class.forName加载类
Class.forName使用的是被调用者的类加载器来加载类的.
这种特性, 证明了java类加载器中的名称空间是唯一的, 不会相互干扰.

即在一般情况下, 保证同一个类中所关联的其他类都是由当前类的类加载器所加载的.

Java代码  
  1. public static Class forName(String className)
  2. throws ClassNotFoundException {
  3. return forName0(className, true , ClassLoader.getCallerClassLoader());
  4. }
  5. /** Called after security checks have been made. */
  6. private static native Class forName0(String name, boolean initialize,
  7. ClassLoader loader)
  8. throws ClassNotFoundException;

上图中 ClassLoader.getCallerClassLoader 就是得到调用当前forName方法的类的类加载器

线程上下文类加载器
java默认的线程上下文类加载器是 系统类加载器(AppClassLoader).

Java代码  
  1. // Now create the class loader to use to launch the application
  2. try {
  3. loader = AppClassLoader.getAppClassLoader(extcl);
  4. } catch (IOException e) {
  5. throw new InternalError(
  6. "Could not create application class loader" );
  7. }
  8. // Also set the context class loader for the primordial thread.
  9. Thread.currentThread().setContextClassLoader(loader);

以上代码摘自sun.misc.Launch的无参构造函数Launch()。

使用线程上下文类加载器, 可以在执行线程中, 抛弃双亲委派加载链模式, 使用线程上下文里的类加载器加载类.
典型的例子有, 通过线程上下文来加载第三方库jndi实现, 而不依赖于双亲委派.

大部分java app服务器(jboss, tomcat..)也是采用contextClassLoader来处理web服务。
还有一些采用 hotswap 特性的框架, 也使用了线程上下文类加载器, 比如 seasar (full stack framework in japenese).

线程上下文从根本解决了一般应用不能违背双亲委派模式的问题.

使java类加载体系显得更灵活.

随着多核时代的来临, 相信多线程开发将会越来越多地进入程序员的实际编码过程中. 因此,
在编写基础设施时, 通过使用线程上下文来加载类, 应该是一个很好的选择.

当然, 好东西都有利弊. 使用线程上下文加载类, 也要注意, 保证多根需要通信的线程间的类加载器应该是同一个,
防止因为不同的类加载器, 导致类型转换异常(ClassCastException).

自定义的类加载器实现
defineClass(String name, byte[] b, int off, int len,ProtectionDomain protectionDomain)
是java.lang.Classloader提供给开发人员, 用来自定义加载class的接口.

使用该接口, 可以动态的加载class文件.

例如,
在jdk中, URLClassLoader是配合findClass方法来使用defineClass, 可以从网络或硬盘上加载class.

而使用类加载接口, 并加上自己的实现逻辑, 还可以定制出更多的高级特性.

比如,

一个简单的hot swap 类加载器实现:

Java代码  
  1. import java.io.File;
  2. import java.io.FileInputStream;
  3. import java.lang.reflect.Method;
  4. import java.net.URL;
  5. import java.net.URLClassLoader;
  6. /**
  7. * 可以重新载入同名类的类加载器实现
  8. *
  9. * 放弃了双亲委派的加载链模式.
  10. * 需要外部维护重载后的类的成员变量状态.
  11. *
  12. * @author ken.wu
  13. * @mail ken.wug@gmail.com
  14. * 2007-9-28 下午01:37:43
  15. */
  16. public class HotSwapClassLoader extends URLClassLoader {
  17. public HotSwapClassLoader(URL[] urls) {
  18. super (urls);
  19. }
  20. public HotSwapClassLoader(URL[] urls, ClassLoader parent) {
  21. super (urls, parent);
  22. }
  23. public Class load(String name)
  24. throws ClassNotFoundException {
  25. return load(name, false );
  26. }
  27. public Class load(String name, boolean resolve)
  28. throws ClassNotFoundException {
  29. if ( null != super .findLoadedClass(name))
  30. return reload(name, resolve);
  31. Class clazz = super .findClass(name);
  32. if (resolve)
  33. super .resolveClass(clazz);
  34. return clazz;
  35. }
  36. public Class reload(String name, boolean resolve)
  37. throws ClassNotFoundException {
  38. return new HotSwapClassLoader( super .getURLs(), super .getParent()).load(
  39. name, resolve);
  40. }
  41. }
  42. public class A {
  43. private B b;
  44. public void setB(B b) {
  45. this .b = b;
  46. }
  47. public B getB() {
  48. return b;
  49. }
  50. }
  51. public class B {}

这个类的作用是可以重新载入同名的类, 但是, 为了实现hotswap, 老的对象状态
需要通过其他方式拷贝到重载过的类生成的全新实例中来。(A类中的b实例)

而新实例所依赖的B类如果与老对象不是同一个类加载器加载的, 将会抛出类型转换异常(ClassCastException).

为了解决这种问题, HotSwapClassLoader自定义了load方法. 即当前类是由自身classLoader加载的, 而内部依赖的类

还是老对象的classLoader加载的.

Java代码  
  1. public class TestHotSwap {
  2. public static void main(String args[]) {
  3. A a = new A();
  4. B b = new B();
  5. a.setB(b);
  6. System.out.printf("A classLoader is %s n" , a.getClass().getClassLoader());
  7. System.out.printf("B classLoader is %s n" , b.getClass().getClassLoader());
  8. System.out.printf("A.b classLoader is %s n" ,   a.getB().getClass().getClassLoader());
  9. HotSwapClassLoader c1 = new HotSwapClassLoader( new URL[]{ new URL( "file:\e:\test\")} , a.getClass().getClassLoader());
  10. Class clazz = c1.load(" test.hotswap.A ");
  11. Object aInstance = clazz.newInstance();
  12. Method method1 = clazz.getMethod(" setB ", B.class);
  13. method1.invoke(aInstance, b);
  14. Method method2 = clazz.getMethod(" getB ", null);
  15. Object bInstance = method2.invoke(aInstance, null);
  16. System.out.printf(" reloaded A.b classLoader is %s n", bInstance.getClass().getClassLoader());
  17. }
  18. }

输出

A classLoader is sun.misc.Launcher$AppClassLoader@19821f
B classLoader is sun.misc.Launcher$AppClassLoader@19821f
A.b classLoader is sun.misc.Launcher$AppClassLoader@19821f
reloaded A.b classLoader is sun.misc.Launcher$AppClassLoader@19821f

==================================================================================================

  作者:欧阳鹏  欢迎转载,与人分享是进步的源泉!

  转载请保留原文地址:http://blog.csdn.net/ouyang_peng

==================================================================================================


转载于:https://www.cnblogs.com/ouyangpeng/archive/2013/03/28/8538449.html

我的Java开发学习之旅------Java ClassLoader解析一(转)相关推荐

  1. 我的Java开发学习之旅------JAVA 笔记ClassLoader.getResourceAsStream() 与 Class.getResourceAsStream()的区别...

    今天,一读者在我的 Android通过调用Webservice实现手机号码归属地查询 文章中给我回复了一个问题,由于没有具体说明我的sop12.xml文件是放在src目录下,不是和具体操作类Addre ...

  2. 我的Java开发学习之旅------Java资源的国际化详解

    internationalization (国际化)简称 i18n,因为在i和n之间还有18个字符,localization(本地化 ),简称L10n. 国际化相关的Java类 Java国际化主要通过 ...

  3. 我的Java开发学习之旅------Java经典排序算法之希尔排序

    一.希尔排序(Shell Sort) 希尔排序(Shell Sort)是一种插入排序算法,因D.L.Shell于1959年提出而得名. Shell排序又称作缩小增量排序. 二.希尔排序的基本思想 希尔 ...

  4. 我的Java开发学习之旅------Java经典排序算法之二分插入排序

    一.折半插入排序(二分插入排序) 将直接插入排序中寻找A[i]的插入位置的方法改为采用折半比较,即可得到折半插入排序算法.在处理A[i]时,A[0]--A[i-1]已经按关键码值排好序.所谓折半比较, ...

  5. JAVA开发学习——欢迎来到JAVA开发世界!

    java概述 Java是第一门完全面向对象编程的计算机语言 作者:詹姆斯·高斯林(James Gosling) 三大平台版本 J2SE(Java 2 Platform Standard Edition ...

  6. 我的Java开发学习之旅------gt;Base64的编码思想以及Java实现

    Base64是一种用64个字符来表示任意二进制数据的方法. 用记事本打开exe.jpg.pdf这些文件时,我们都会看到一大堆乱码,因为二进制文件包含很多无法显示和打印的字符,所以,如果要让记事本这样的 ...

  7. 我的Java开发学习之旅------Base64的编码思想以及Java实现

    Base64是一种用64个字符来表示任意二进制数据的方法. 用记事本打开exe.jpg.pdf这些文件时,我们都会看到一大堆乱码,因为二进制文件包含很多无法显示和打印的字符,所以,如果要让记事本这样的 ...

  8. 我的Java开发学习之旅------求字符串中出现次数最多的字符串以及出现的次数...

    金山公司面试题:一个字符串中可能包含a~z中的多个字符,如有重复,如String data="aavzcadfdsfsdhshgWasdfasdf",求出现次数最多的那个字母及次数 ...

  9. 我的Java开发学习之旅------心得总结:Java性能优化技巧集锦

    一.通用篇 "通用篇"讨论的问题适合于大多数Java应用. 1.1 不用new关键词创建类的实例 用new关键词创建类的实例时,构造函数链中的所有构造函数都会被自动调用.但如果一个 ...

  10. 【我的Java开发学习之旅】Windows系统下如何运行.jar文件?

    一.需求描述 今天查询大数据平台,看到刚刚发布的APP版本有crash,然后平台上的crash都是混淆的样子,类似如下所示: 所以我得使用proguardgui.jar工具来进行反混淆,查看出现问题的 ...

最新文章

  1. Google Chrome 悄悄升级 WebGL 2.0 标准
  2. ​Python 3 新特性:类型注解——类似注释吧,反正解释器又不做校验
  3. win10下输入法突然变成繁体了怎么设置回来?(繁體)(快捷鍵:ctrl + shift +f)
  4. wavecn 2.0.0.5 正式版_微信7.0.5怎么更新?微信7.0.5安卓版下载与更新升级教程
  5. 简单实现x的n次方pta_学会这四招,原来平均值计算也可以这么简单
  6. 线段树 区间加 gcd 差分
  7. jQuery中操作元素节点appendTo()与prependTo()的区别
  8. kibana java_kibana安装
  9. [剑指offer]面试题第[53-2]题[JAVA][0-n-1中缺失的数字][二分法][暴力法]
  10. 【MFC】遍历文件夹及其子文件夹
  11. 16 张图解带你掌握一致性哈希算法
  12. 甜品果汁饮品拍摄设计海报,美如蓬莱仙境!
  13. 微课|玩转Python轻松过二级:第3章课后习题解答6
  14. graphpad prism显著性差异分析_【市场表现】2020年第三季度企业债利差分析
  15. 什么是Prettier?
  16. 【hdu3501】求[1,n-1]与n不互质的所有数之和(单个欧拉函数求法+[1,n]和n互质的数之和公式----模版题)
  17. GPU机器无法使用GPU
  18. php开源代码推荐,非常好用的源代码网站推荐,用这些开源代码就够了
  19. 【LDO带载能力和两端压差有关】
  20. databanding 替换 findviewbyid

热门文章

  1. 鸿蒙os系统使用技巧,鸿蒙OS系统的四大技术特性介绍
  2. android 本地广播原理,本地广播原理
  3. python单链表实现荷兰国旗问题_[算法题] 荷兰国旗问题
  4. linux添加ctrl c,Linux ctrl-c explained(ctrl-c 详解)
  5. 帆软统计函数之统计有效性
  6. 帆软报表嵌套在iframe中,HTML的fieldset 定义的为自定义导出按钮
  7. 三星a9s参数_三星A9s配置怎么样 三星A9s参数配置介绍
  8. Java重入函数_重入函数
  9. sql server数据库中 smallint, int ,bigint ,tinyint的区别与长度
  10. 取代SharedPreferences的多进程解决方案