https://blog.csdn.net/zhangjg_blog/article/details/16102131

从java的动态性到类加载机制

我们知道,java是一种动态语言。那么怎样理解这个“动态”呢?或者说一门语言具备了什么特性,才能称之为动态语言呢?对于java,我是这样理解的。

我们都知道JVM(java虚拟机)执行的不是本地机器码指令,而是执行一种称之为字节码的指令(存在于class文件中)。这就要求虚拟机在真正执行字节码之前,先把相关的class文件加载到内存中。虚拟机不是一次性加载所有需要的class文件,因为它在执行的时候根本不会知道以后会用到哪些class文件。它是每用到一个类,就会在运行时“动态地”加载和这个类相关的class文件。这就是java被称之为动态性语言的根本原因。除了动态加载类之外,还会动态的初始化类,对类进行动态链接。动态初始化和动态链接放在其他文章中进行介绍。本文中只关心类的加载。

在JVM中负责对类进行加载的正是本文要介绍的类加载器(ClassLoader),所以,类加载器是JVM不可或缺的重要组件。

java中的类加载器及类加载器工作原理

java中(指的是javase)有三种类加载器。每个类加载器在创建的时候已经指定他们对应的目录, 也就是说每个类加载器去哪里加载类是确定的,我认为在ClassLoader类中应该会有getTargetPath()之类的方法, 得到他们对应的路径,找了找jdk的文档,发现是没有的。以下是这三种类加载器和他们对应的路径:

* AppClassLoader  --   加载classpath指定的路径中的类

* ExtClassLoader   --   加载jre/lib/ext目录下或者java.ext.dirs系统属性定义的目录下的类

* BootStrap           --   加载JRE/lib/rt.jar中的类

那么类加载器是如何工作的呢?可以参看jdk中ClassLoader类的源码。这个类的实现使用了模板方法模式,首先是loadClass方法来加载类,loadClass方法又调用了findClass方法,该方法读取并返回类文件的数据,findClass方法返回后,loadClass方法继续调用defineClass方法,将返回的数据加工成虚拟机运行时可识别的类型信息。所以,我们如果开发自己的类加载器,只需要继承jdk中的ClassLoader类,并覆盖findClass方法就可以了,剩下的而工作,父类会完成。其他java平台有的根据自己的需求,实现了自己特定的类加载器,例如javaee平台中的tomcat服务器,android平台中的dalvik虚拟机也定义了自己的类加载器。

虚拟机加载类有两种方式,一种方式就是上面提到的ClassLoader.loadClass()方法,另一种是使用反射API,Class.forName()方法,其实Class.forName()方法内部也是使用的ClassLoader。Class类中forName方法的实现如下:

  1. public static Class<?> forName(String name, boolean initialize,
  2. ClassLoader loader)
  3. throws ClassNotFoundException
  4. {
  5. if (loader == null) {
  6. SecurityManager sm = System.getSecurityManager();
  7. if (sm != null) {
  8. ClassLoader ccl = ClassLoader.getCallerClassLoader();
  9. if (ccl != null) {
  10. sm.checkPermission(
  11. SecurityConstants.GET_CLASSLOADER_PERMISSION);
  12. }
  13. }
  14. }
  15. return forName0(name, initialize, loader);
  16. }
  17. /** Called after security checks have been made. */
  18. private static native Class forName0(String name, boolean initialize,
  19. ClassLoader loader)
  20. throws ClassNotFoundException;

类加载器的三个特性

类加载器有三个特性,分别为委派,可见性和单一性,其他文章上对这三个特性的介绍如下:

* 委托机制是指将加载一个类的请求交给父类加载器,如果这个父类加载器不能够找到或者加载这个类,那么再加载它。

* 可见性的原理是子类的加载器可以看见所有的父类加载器加载的类,而父类加载器看不到子类加载器加载的类。

* 单一性原理是指仅加载一个类一次,这是由委托机制确保子类加载器不会再次加载父类加载器加载过的类。

其中,委派机制是基础,在其他资料中也把这种机制叫做类加载器的双亲委派模型,其实说的是同一个意思。可加性和单一性是依赖于委派机制的。

以下代码测试类加载器的委派机制:

  1. ClassLoader appClassLoader = ClassLoaderTest.class.getClassLoader();
  2. System.out.println(appClassLoader); //sun.misc.Launcher$AppClassLoader@19821f
  3. ClassLoader extClassLoader = appClassLoader.getParent();
  4. System.out.println(extClassLoader);  //sun.misc.Launcher$ExtClassLoader@addbf1
  5. //AppClassLoader的父加载器是ExtClassLoader
  6. System.out.println(extClassLoader.getParent()); //null
  7. //ExtClassLoader的父加载器是null, 也就是BootStrap,这是由c语言实现的

由打印结果可知,加载我们自己编写的类的加载器是AppClassLoader,AppClassLoader的父加载器是ExtClassLoader,在而ExtClassLoader的父加载器返回结果为null,这说明他的附加载器是BootStrap,这个加载器是和虚拟机紧密联系在一起的,在虚拟机启动时,就会加载jdk中的类。它是由C实现的,没有对应的java对象,所以返回null。但是在逻辑上,BootStrap仍是ExtClassLoader的父加载器。也就是说每当ExtClassLoader加载一个类时,总会委托给BootStrap加载。

系统类加载器和线程上下文类加载器

在java中,还存在两个概念,分别是系统类加载器和线程上下文类加载器。

其实系统类加载器就是AppClassLoader应用程序类加载器,它两个值得是同一个加载器,以下代码可以验证:

  1. ClassLoader appClassLoader = ClassLoaderTest.class.getClassLoader();
  2. System.out.println(appClassLoader); //sun.misc.Launcher$AppClassLoader@19821f
  3. ClassLoader sysClassLoader = ClassLoader.getSystemClassLoader();
  4. System.out.println(sysClassLoader);  //sun.misc.Launcher$AppClassLoader@19821f
  5. //由上面的验证可知, 应用程序类加载器和系统类加载器是相同的, 因为地址是一样的

这两个类加载器对应的输出,不仅类名相同,连对象的哈希值都是一样的,这充分说明系统类加载器和应用程序类加载器不仅是同一个类,更是同一个类的同一个对象。

每个线程都会有一个上下文类加载器,由于在线程执行时加载用到的类,默认情况下是父线程的上下文类加载器, 也就是AppClassLoader。

  1. new Thread(new Runnable() {
  2. @Override
  3. public void run() {
  4. ClassLoader threadcontextClassLosder = Thread.currentThread().getContextClassLoader();
  5. System.out.println(threadcontextClassLosder); //sun.misc.Launcher$AppClassLoader@19821f
  6. }
  7. }).start();

这个子线程在执行时打印的信息为sun.misc.Launcher$AppClassLoader@19821f,可以看到和主线程中的AppClassLoader是同一个对象(哈希值相同)。

也可以为线程设置特定的类加载器,这样的话,线程在执行时就会使用这个特定的类加载器来加载使用到的类。如下代码:

  1. Thread th = new Thread(new Runnable() {
  2. @Override
  3. public void run() {
  4. ClassLoader threadcontextClassLosder = Thread.currentThread().getContextClassLoader();
  5. System.out.println(threadcontextClassLosder); //jg.zhang.java.testclassloader.ClassLoaderTest$3@1b67f74
  6. }
  7. });
  8. th.setContextClassLoader(new ClassLoader() {});
  9. th.start();

在线程运行之前,为它设置了一个匿名内部类的类加载器对象,线程运行时,输出的信息为:jg.zhang.java.testclassloader.ClassLoaderTest$3@1b67f74,也就是我们设置的那个类加载器对象。

类加载器的可见性

下面验证类加载器的可见性,也就是 子类的加载器可以看见所有的父类加载器加载的类,而父类加载器看不到子类加载器加载的类。

以下代码使用父加载器ExtClassLoader加载子加载器AppClassLoader路径下的类,由输出可知,是不可能实现的。

  1. try {
  2. Class.forName("jg.zhang.java.testConcurrent.Person", true,
  3. ClassLoaderTest.class.getClassLoader().getParent());
  4. System.out.println("1 -- 类被加载");
  5. } catch (ClassNotFoundException e) {
  6. //e.printStackTrace();
  7. System.out.println("1 -- 未找到类");
  8. }

输出为 :1 -- 未找到类 。说明抛出了ClassNotFoundException异常。原因是让ExtClassLoader加载 jg.zhang.java.testConcurrent.Person这个类因为这个类不在jre/lib/ext目录下或者java.ext.dirs系统属性定义的目录下,所以抛出ClassNotFoundException。所以父加载器不能加载应该被子加载器加载的类。也就是说这个类在父加载器中不可见。这种机制依赖于委派机制。

下面代码使用子加载器AppClassLoader 加载父加载器BootStrap中的类,这是可以实现的。

  1. try {
  2. Class.forName("java.lang.String", true,
  3. ClassLoaderTest.class.getClassLoader());
  4. System.out.println("2 -- 类被加载");
  5. } catch (ClassNotFoundException e) {
  6. //e.printStackTrace();
  7. System.out.println("2 -- 未找到类");
  8. }

输出为:2 -- 类被加载。说明成功加载了String类。是因为在指定由AppClassLoader加载String类时,由AppClassLoader一直委派到BootStrap加载。虽然是由子加载器的父加载器加载的,但是也可以说,父加载器加载的类对于子加载器来说是可见的。这同样依赖于委派机制。其实在虚拟机启动初期,java.lang.String已经被BootStrap预加载了,这时再次加载,虚拟机发现已经加载,不会再重复加载。这同时也证明了类加载器的单一性。

测试代码

到此为止,类加载器的知识就全部讲完了。以下是整个测试代码:

  1. package jg.zhang.java.testclassloader;
  2. /**
  3. * 参考ImportNew上的一篇文章<<类加载器的工作原理>>,
  4. * 文章地址:http://www.importnew.com/6581.html
  5. *
  6. * Java类加载器基于三个机制:委托、可见性和单一性。
  7. * 委托机制是指将加载一个类的请求交给父类加载器,如果这个父类加载器不能够找到或者加载这个类,那么再加载它。
  8. * 可见性的原理是子类的加载器可以看见所有的父类加载器加载的类,而父类加载器看不到子类加载器加载的类。
  9. * 单一性原理是指仅加载一个类一次,这是由委托机制确保子类加载器不会再次加载父类加载器加载过的类。
  10. *
  11. * 三种类加载器: 每个类加载器在创建的时候已经指定他们对应的目录, 也就是说每个类加载器去哪里加载类是确定的
  12. * 我认为在ClassLoader类中应该会有getTargetPath()之类的方法, 得到他们对应的路径,找了找jdk的文档,发现是没有的.
  13. * AppClassLoader  --   加载classpath指定的路径中的类
  14. * ExtClassLoader  --   加载jre/lib/ext目录下或者java.ext.dirs系统属性定义的目录下的类
  15. * BootStrap       --   加载JRE/lib/rt.jar中的类
  16. *
  17. *
  18. *
  19. * @author zhangjg
  20. *
  21. */
  22. public class ClassLoaderTest {
  23. public static void main(String[] args) {
  24. test1();
  25. test2();
  26. test3();
  27. }
  28. /**
  29. * 验证线程上下文类加载器
  30. */
  31. private static void test3() {
  32. /**
  33. * 1 每个线程都会有一个上下文类加载器,由于在线程执行时加载用到的类,默认情况下是父线程
  34. * 的上下文类加载器, 也就是AppClassLoader
  35. */
  36. new Thread(new Runnable() {
  37. @Override
  38. public void run() {
  39. ClassLoader threadcontextClassLosder = Thread.currentThread().getContextClassLoader();
  40. System.out.println(threadcontextClassLosder); //sun.misc.Launcher$AppClassLoader@19821f
  41. }
  42. }).start();
  43. /**
  44. * 2 也可以给创建的线程设定特定的上下文类加载器
  45. */
  46. Thread th = new Thread(new Runnable() {
  47. @Override
  48. public void run() {
  49. ClassLoader threadcontextClassLosder = Thread.currentThread().getContextClassLoader();
  50. System.out.println(threadcontextClassLosder); //jg.zhang.java.testclassloader.ClassLoaderTest$3@1b67f74
  51. }
  52. });
  53. th.setContextClassLoader(new ClassLoader() {});
  54. th.start();
  55. }
  56. /**
  57. * 测试可见性,可见性依赖于委托机制
  58. */
  59. private static void test2() {
  60. /**
  61. * 1 让ExtClassLoader加载 jg.zhang.java.testConcurrent.Person这个类
  62. * 因为这个类不在jre/lib/ext目录下或者java.ext.dirs系统属性定义的目录下
  63. * 所以抛出ClassNotFoundException
  64. *
  65. * 所以父加载器不能加载应该被子加载器加载的类,这个类在父加载器中不可见
  66. * 这种机制依赖于委派机制
  67. */
  68. try {
  69. Class.forName("jg.zhang.java.testConcurrent.Person", true,
  70. ClassLoaderTest.class.getClassLoader().getParent());
  71. System.out.println("1 -- 类被加载");
  72. } catch (ClassNotFoundException e) {
  73. //e.printStackTrace();
  74. System.out.println("1 -- 未找到类");
  75. }
  76. /**
  77. * 2 让AppClassLoader加载java.lang.String类
  78. * 没有抛出异常,说明类被正常加载了
  79. * 虽然是由AppClassLoader一直委派到BootStrap而加载的
  80. * 所以可以说,父加载器加载的类对于子加载器来说是可见的,这同样依赖于委派机制
  81. *
  82. * 其实在虚拟机启动初期,java.lang.String已经被BootStrap预加载了
  83. * 这时再次加载,虚拟机发现已经加载,不会再重复加载
  84. */
  85. try {
  86. Class.forName("java.lang.String", true,
  87. ClassLoaderTest.class.getClassLoader());
  88. System.out.println("2 -- 类被加载");
  89. } catch (ClassNotFoundException e) {
  90. //e.printStackTrace();
  91. System.out.println("2 -- 未找到类");
  92. }
  93. }
  94. /**
  95. * 验证三种类加载器的父子关系
  96. */
  97. private static void test1() {
  98. ClassLoader appClassLoader = ClassLoaderTest.class.getClassLoader();
  99. System.out.println(appClassLoader); //sun.misc.Launcher$AppClassLoader@19821f
  100. ClassLoader sysClassLoader = ClassLoader.getSystemClassLoader();
  101. System.out.println(sysClassLoader);  //sun.misc.Launcher$AppClassLoader@19821f
  102. //由上面的验证可知, 应用程序类加载器和系统类加载器是相同的, 因为地址是一样的
  103. ClassLoader extClassLoader = appClassLoader.getParent();
  104. System.out.println(extClassLoader);  //sun.misc.Launcher$ExtClassLoader@addbf1
  105. //AppClassLoader的父加载器是ExtClassLoader
  106. System.out.println(extClassLoader.getParent()); //null
  107. //ExtClassLoader的父加载器是null, 也就是BootStrap,这是由c语言实现的
  108. }
  109. }

Java中的类加载器详解相关推荐

  1. Java中的static关键字详解

    ** Java中的static关键字详解 ** 在一个类中定义一个方法为static,即静态的,那就是说无需本类的对象就可以调用此方法.调用一个静态方法就是 "类名.方法名" ,静 ...

  2. java中的进制输出转换_Java I/O : Java中的进制详解

    作者:李强强 上一篇,泥瓦匠基础地讲了下Java I/O : Bit Operation 位运算.这一讲,泥瓦匠带你走进Java中的进制详解. 一.引子 在Java世界里,99%的工作都是处理这高层. ...

  3. Java中的main()方法详解

    源文作者:leizhimin    源文链接:http://lavasoft.blog.51cto.com/62575/53263 源文作者版权申明: 版权声明:原创作品,允许转载,转载时请务必以超链 ...

  4. java中Freemarker list指令详解

    java Freemarker中list指令主要是进行迭代服务器端传递过来的List集合. 定义 <#list nameList as names> ${names} </#list ...

  5. Java中的Runtime类详解

    Java中的Runtime类详解 1.类注释 /**Every Java application has a single instance of class Runtime that allows ...

  6. 异常将上下文初始化事件发送到类的侦听器实例._Java CLassLoader类加载器详解,一点课堂(多岸学院)...

    Java CLassLoader 类加载器(class loader)是 Java™中的一个很重要的概念.类加载器负责加载 Java 类的字节代码到 Java 虚拟机中.本文首先详细介绍了 Java ...

  7. java中properties作用,Java中Properties的使用详解

    Java中有个比较重要的类Properties(Java.util.Properties),主要用于读取Java的配置文件,各种语言都有自己所支 持的配置文件,配置文件中很多变量是经常改变的,这样做也 ...

  8. Java 中的伪共享详解及解决方案

    转载自  Java 中的伪共享详解及解决方案 1. 什么是伪共享 CPU 缓存系统中是以缓存行(cache line)为单位存储的.目前主流的 CPU Cache 的 Cache Line 大小都是 ...

  9. java中list和map详解

    java中list和map详解 一.概叙 List , Set, Map都是接口,前两个继承至Collection接口,Map为独立接口, List下有ArrayList,Vector,LinkedL ...

  10. stringtokenizer java_基于Java中的StringTokenizer类详解(推荐)

    StringTokenizer是字符串分隔解析类型,属于:Java.util包. 1.StringTokenizer的构造函数 StringTokenizer(String str):构造一个用来解析 ...

最新文章

  1. AI发现人类肾细胞有一半结构未知,UCSD最新研究登上Nature,算法已开源
  2. 解读 Q_D, Q_Q 指针
  3. 类选项html 最后无距离,各种距离 一览无遗
  4. 深度测试与alpha混合(3)
  5. Python update 函数 - Python零基础入门教程
  6. Serverless 实战 —— Serverless + Egg.js 后台管理系统实战
  7. RabbitMQ学习总结(8)——RabbitMQ后台管理控制台页面属性总结
  8. 操作属性之修改样式属性
  9. 2019 CCF CSP-J2题解
  10. 查看手机已经记住的WIFI密码
  11. Python 从视频中分离音频
  12. Nexus3功能介绍
  13. matlab菲涅尔衍射光强分布,求助!!!用颜色表示的菲涅尔衍射的光强分布图
  14. html中form表单提交中文乱码问题基本解决办法
  15. STM32入门学习 第二天
  16. 写到最前面的话——研究生毕业论文致谢
  17. 如何让你开发的产品在同质化洪流中脱颖而出?
  18. NYOJ 87-棋盘分割(记忆化搜索)
  19. web测试 (四)兼容性测试
  20. Dubbo的版本处理错误

热门文章

  1. 修复40G的老IDE硬盘
  2. html代码实现简单的简历模板
  3. 如何优雅地制作精排 ePub —— 个人电子书制作规范及基本样式表
  4. 嵌入式linux开发实战——项目1认识嵌入式系统
  5. Java利用itext实现导出PDF文件
  6. c语言在线考试系统的需求分析,在线考试系统需求分析.doc
  7. 海康威视摄像头录制的视频无法用PR剪辑
  8. python爬取酷狗音乐top500_爬取酷狗音乐Top500
  9. 林子雨spark scala版编程小结
  10. 计算机ping使用的端口,ping 端口:Ping端口命令的使用方法介绍