(转) Java虚拟机类加载顺序研究

本贴最后更新于 3207 天前,其中的信息可能已经时移世异

当JVM(Java虚拟机)启动时,会形成由三个类加载器组成的初始类加载器层次结构:

        Bootstrap Classloader
                        |
        Extension Classloader
                        |
        System Classloader

1.Bootstrap Classloader -引导(也称为原始)类加载器,这个加载器的是非常特殊的,它实际上不是 java.lang.ClassLoader的子类,而是由JVM自身实现的

它所加载的有如下的资源:

D:/Program Files/Java/jre6/lib/resources.jar
D:/Program Files/Java/jre6/lib/rt.jar
D:/Program Files/Java/jre6/lib/sunrsasign.jar
D:/Program Files/Java/jre6/lib/jsse.jar
D:/Program Files/Java/jre6/lib/jce.jar
D:/Program Files/Java/jre6/lib/charsets.jar
D:/Program Files/Java/jre6/classes

这时大家知道了为什么我们不需要在系统属性CLASSPATH中指定这些类库了吧,因为JVM在启动的时候就自动加载它们了。

2.Extension Classloader -扩展类加载器 ExtClassLoader,它负责加载JRE的扩展目录(JAVA_HOME/jre/lib/ext或者由java.ext.dirs系统属性指定的)中JAR的类包,它已经属于最上层的加载器了。它所加载的有如下的资源:

D:/Program Files/Java/jre6/lib/ext;C:/WINDOWS/Sun/Java/lib/ext


3. System Classloader -系统(也称为应用)类加载器AppClassLoader 加载来自在命令java中的-classpath或者java.class.path系统属性或者 CLASSPATH操作系统属性所指定的JAR类包和类路径。也就是加载项目中用到的第三方包和项目中的类,它的父加载器为ExtClassLoader

它所加载的有如下的资源:

E:/works/encoder/lib/antlr-2.7.6.jar
E:/works/encoder/lib/cglib-2.2.jar
E:/works/encoder/lib/cglib-nodep-2.1_3.jar
E:/works/encoder/lib/commons-codec-1.1.jar
E:/works/encoder/lib/commons-collections-3.2.jar
E:/works/encoder/lib/commons-lang-2.1.jar
E:/works/encoder/lib/commons-logging-1.0.4.jar
E:/works/encoder/lib/commons-logging.jar
E:/works/encoder/lib/dom4j-1.4.jar
E:/works/encoder/lib/ehcache-0.9.jar
E:/works/encoder/lib/gameserver_core_1.0.1.jar
E:/works/encoder/lib/hibernate-annotations.jar
E:/works/encoder/lib/hibernate-commons-annotations.jar
E:/works/encoder/lib/hibernate-entitymanager.jar
E:/works/encoder/lib/hibernate-tools.jar
E:/works/encoder/lib/hibernate2.jar
E:/works/encoder/lib/hibernate3.jar
E:/works/encoder/lib/jakarta-oro.jar
E:/works/encoder/lib/jasypt-1.5.jar
E:/works/encoder/lib/javassist-3.9.0.GA.jar
E:/works/encoder/lib/jta.jar
E:/works/encoder/lib/jzlib-1.0.7.jar
E:/works/encoder/lib/libthrift.jar
E:/works/encoder/lib/log4j-1.2.15.jar
E:/works/encoder/lib/mina-integration-beans-2.0.0-M4.jar
E:/works/encoder/lib/MySQL-connector-java-5.0.8-bin.jar
E:/works/encoder/lib/persistence.jar
E:/works/encoder/lib/proxool-0.9.1.jar
E:/works/encoder/lib/proxool-cglib.jar
E:/works/encoder/lib/slf4j-api-1.5.6.jar
E:/works/encoder/lib/slf4j-log4j12-1.5.6.jar
E:/works/encoder/lib/spring.jar
E:/works/encoder/lib/springside3-core-3.1.4.jar
E:/works/encoder/lib/xerces-2.6.2.jar
E:/works/encoder/lib/xml-apis.jar

classloader 加载类用的是全盘负责委托机制。所谓全盘负责,即是当一个classloader加载一个Class的时候,这个Class所依赖的和引用的所有 Class也由这个classloader负责载入,除非是显式的使用另外一个classloader载入;委托机制则是先让parent(父)类加载器 (而不是super,它与parent classloader类不是继承关系)寻找,只有在parent找不到的时候才从自己的类路径中去寻找。此外类加载还采用了cache机制,也就是如果 cache中保存了这个Class就直接返回它,如果没有才从文件中读取和转换成Class,并存入cache,这就是为什么我们修改了Class但是必须重新启动JVM才能生效的原因。
类加载器的顺序是:
先是bootstrap classloader,然后是extension classloader,最后才是system classloader。大家会发现加载的Class越是重要的越在靠前面。这样做的原因是出于安全性的考虑,试想如果system classloader“亲自”加载了一个具有破坏性的“java.lang.System”类的后果吧。这种委托机制保证了用户即使具有一个这样的类,也把它加入到了类路径中,但是它永远不会被载入,因为这个类总是由bootstrap classloader来加载的。

System.out.println("bootstrap classloader -引导(也称为原始)类加载器");
  URL[] urls=sun.misc.Launcher.getBootstrapClassPath().getURLs();
     for (int i = 0; i < urls.length; i++) {
       System.out.println(urls[i].toExternalForm());
     }
  System.out.println("extension classloader -扩展类加载器");
  System.out.println(System.getProperty("java.ext.dirs"));
  System.out.println("system classloader -系统(也称为应用)类加载器");
  System.out.println(System.getProperty("java.class.path").replace(";", "/n"));

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

JAVA中的每一个类都是通过类加载器加载到内存中的。对于类加载器的工作流程如下表示:
1.searchfile() 
找到我所要加载的类文件。(抛除JAR包的概念,现在只是要加载一个.class文件)
2.loadDataClass()
读取这个类文件的字节码。
3.defineClass()
加载类文件。(加载的过程其实很复杂,我们现在先不研究它。)
从这个过程中我们能很清楚的发现,自定义的类加载能够很轻松的控制每个类文件的加载过程。这样在第二步(loadDataClass)和第三步(difineClass)之间,我们将会有自己的空间灵活的控制这个过程。
我们加密解密的技术就应用到这里。

了解ClassLoader
1, 什么是 ClassLoader? 
Java 程序并不是一个可执行文件,是需要的时候,才把装载到 JVM中。ClassLoader 做的工作就是 JVM 中将类装入内存。 而且,Java ClassLoader 就是用 Java 语言编写的。这意味着您可以创建自己的 ClassLoader 
ClassLoader 的基本目标是对类的请求提供服务。当 JVM 需要使用类时,它根据名称向 ClassLoader 请求这个类,然后 ClassLoader 试图返回一个表示这个类的 Class 对象。 通过覆盖对应于这个过程不同阶段的方法,可以创建定制的 ClassLoader。
2, 一些重要的方法
A) 方法 loadClass 
ClassLoader.loadClass() 是 ClassLoader 的入口点。该方法的定义如下: 
Class loadClass( String name, boolean resolve ;
name JVM 需要的类的名称,如 Foo 或 java.lang.Object。
resolve 参数告诉方法是否需要解析类。在准备执行类之前,应考虑类解析。并不总是需要解析。如果 JVM 只需要知道该类是否存在或找出该类的超类,那么就不需要解析。

B) 方法 defineClass 
defineClass 方法是 ClassLoader 的主要诀窍。该方法接受由原始字节组成的数组并把它转换成 Class 对象。原始数组包含如从文件系统或网络装入的数据。defineClass 管理 JVM 的许多复杂、神秘和倚赖于实现的方面 -- 它把字节码分析成运行时数据结构、校验有效性等等。不必担心,您无需亲自编写它。事实上,即使您想要这么做也不能覆盖它,因为它已被标记成final的。

C) 方法 findSystemClass 
findSystemClass 方法从本地文件系统装入文件。它在本地文件系统中寻找类文件,如果存在,就使用 defineClass 将原始字节转换成 Class 对象,以将该文件转换成类。当运行 Java 应用程序时,这是 JVM 正常装入类的缺省机制。(Java 2 中 ClassLoader 的变动提供了关于 Java 版本 1.2 这个过程变动的详细信息。) 对于定制的 ClassLoader,只有在尝试其它方法装入类之后,再使用 findSystemClass。原因很简单:ClassLoader 是负责执行装入类的特殊步骤,不是负责所有类。例如,即使 ClassLoader 从远程的 Web 站点装入了某些类,仍然需要在本地机器上装入大量的基本 Java 库。而这些类不是我们所关心的,所以要 JVM 以缺省方式装入它们:从本地文件系统。这就是 findSystemClass 的用途。

D) 方法 resolveClass 
正如前面所提到的,可以不完全地(不带解析)装入类,也可以完全地(带解析)装入类。当编写我们自己的 loadClass 时,可以调用 resolveClass,这取决于 loadClass 的 resolve 参数的值。


E) 方法 findLoadedClass 
findLoadedClass 充当一个缓存:当请求 loadClass 装入类时,它调用该方法来查看 ClassLoader 是否已装入这个类,这样可以避免重新装入已存在类所造成的麻烦。应首先调用该方法。

3, 怎么组装这些方法 
1) 调用 findLoadedClass 来查看是否存在已装入的类。
2) 如果没有,那么采用那种特殊的神奇方式来获取原始字节。
3) 如果已有原始字节,调用 defineClass 将它们转换成 Class 对象。
4) 如果没有原始字节,然后调用 findSystemClass 查看是否从本地文件系统获取类。
5) 如果 resolve 参数是 true,那么调用 resolveClass 解析 Class 对象。
6) 如果还没有类,返回 ClassNotFoundException。

4,Java 2 中 ClassLoader 的变动
1)loadClass 的缺省实现 
定制编写的 loadClass 方法一般尝试几种方式来装入所请求的类,如果您编写许多类,会发现一次次地在相同的、很复杂的方法上编写变量。 在 Java 1.2 中 loadClass 的实现嵌入了大多数查找类的一般方法,并使您通过覆盖 findClass 方法来定制它,在适当的时候 findClass 会调用 loadClass。 这种方式的好处是您可能不一定要覆盖 loadClass;只要覆盖 findClass 就行了,这减少了工作量。

2)新方法:findClass
loadClass 的缺省实现调用这个新方法。findClass 的用途包含您的 ClassLoader 的所有特殊代码,而无需要复制其它代码(例如,当专门的方法失败时,调用系统 ClassLoader)。

3) 新方法:getSystemClassLoader 
如果覆盖 findClass 或 loadClass,getSystemClassLoader 使您能以实际 ClassLoader 对象来访问系统 ClassLoader(而不是固定的从 findSystemClass 调用它)。

4) 新方法:getParent 
为了将类请求委托给父代 ClassLoader,这个新方法允许 ClassLoader 获取它的父代 ClassLoader。当使用特殊方法,定制的 ClassLoader 不能找到类时,可以使用这种方法。
父代 ClassLoader 被定义成创建该 ClassLoader 所包含代码的对象的 ClassLoader。

  • Java

    Java 是一种可以撰写跨平台应用软件的面向对象的程序设计语言,是由 Sun Microsystems 公司于 1995 年 5 月推出的。Java 技术具有卓越的通用性、高效性、平台移植性和安全性。

    3194 引用 • 8214 回帖

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...

推荐标签 标签

  • ActiveMQ

    ActiveMQ 是 Apache 旗下的一款开源消息总线系统,它完整实现了 JMS 规范,是一个企业级的消息中间件。

    19 引用 • 13 回帖 • 678 关注
  • 叶归
    5 引用 • 16 回帖 • 10 关注
  • 30Seconds

    📙 前端知识精选集,包含 HTML、CSS、JavaScript、React、Node、安全等方面,每天仅需 30 秒。

    • 精选常见面试题,帮助您准备下一次面试
    • 精选常见交互,帮助您拥有简洁酷炫的站点
    • 精选有用的 React 片段,帮助你获取最佳实践
    • 精选常见代码集,帮助您提高打码效率
    • 整理前端界的最新资讯,邀您一同探索新世界
    488 引用 • 384 回帖 • 5 关注
  • 正则表达式

    正则表达式(Regular Expression)使用单个字符串来描述、匹配一系列遵循某个句法规则的字符串。

    31 引用 • 94 回帖 • 1 关注
  • Q&A

    提问之前请先看《提问的智慧》,好的问题比好的答案更有价值。

    9417 引用 • 42904 回帖 • 109 关注
  • 链滴

    链滴是一个记录生活的地方。

    记录生活,连接点滴

    171 引用 • 3842 回帖
  • MyBatis

    MyBatis 本是 Apache 软件基金会 的一个开源项目 iBatis,2010 年这个项目由 Apache 软件基金会迁移到了 google code,并且改名为 MyBatis ,2013 年 11 月再次迁移到了 GitHub。

    173 引用 • 414 回帖 • 367 关注
  • 支付宝

    支付宝是全球领先的独立第三方支付平台,致力于为广大用户提供安全快速的电子支付/网上支付/安全支付/手机支付体验,及转账收款/水电煤缴费/信用卡还款/AA 收款等生活服务应用。

    29 引用 • 347 回帖
  • CentOS

    CentOS(Community Enterprise Operating System)是 Linux 发行版之一,它是来自于 Red Hat Enterprise Linux 依照开放源代码规定释出的源代码所编译而成。由于出自同样的源代码,因此有些要求高度稳定的服务器以 CentOS 替代商业版的 Red Hat Enterprise Linux 使用。两者的不同在于 CentOS 并不包含封闭源代码软件。

    239 引用 • 224 回帖 • 1 关注
  • 代码片段

    代码片段分为 CSS 与 JS 两种代码,添加在 [设置 - 外观 - 代码片段] 中,这些代码会在思源笔记加载时自动执行,用于改善笔记的样式或功能。

    用户在该标签下分享代码片段时需在帖子标题前添加 [css] [js] 用于区分代码片段类型。

    133 引用 • 890 回帖 • 1 关注
  • abitmean

    有点意思就行了

    31 关注
  • PostgreSQL

    PostgreSQL 是一款功能强大的企业级数据库系统,在 BSD 开源许可证下发布。

    22 引用 • 22 回帖 • 1 关注
  • CodeMirror
    1 引用 • 2 回帖 • 155 关注
  • ZooKeeper

    ZooKeeper 是一个分布式的,开放源码的分布式应用程序协调服务,是 Google 的 Chubby 一个开源的实现,是 Hadoop 和 HBase 的重要组件。它是一个为分布式应用提供一致性服务的软件,提供的功能包括:配置维护、域名服务、分布式同步、组服务等。

    59 引用 • 29 回帖 • 2 关注
  • HBase

    HBase 是一个分布式的、面向列的开源数据库,该技术来源于 Fay Chang 所撰写的 Google 论文 “Bigtable:一个结构化数据的分布式存储系统”。就像 Bigtable 利用了 Google 文件系统所提供的分布式数据存储一样,HBase 在 Hadoop 之上提供了类似于 Bigtable 的能力。

    17 引用 • 6 回帖 • 60 关注
  • 导航

    各种网址链接、内容导航。

    43 引用 • 177 回帖 • 2 关注
  • API

    应用程序编程接口(Application Programming Interface)是一些预先定义的函数,目的是提供应用程序与开发人员基于某软件或硬件得以访问一组例程的能力,而又无需访问源码,或理解内部工作机制的细节。

    79 引用 • 431 回帖
  • 小说

    小说是以刻画人物形象为中心,通过完整的故事情节和环境描写来反映社会生活的文学体裁。

    31 引用 • 108 回帖 • 1 关注
  • AWS
    11 引用 • 28 回帖 • 9 关注
  • Solo

    Solo 是一款小而美的开源博客系统,专为程序员设计。Solo 有着非常活跃的社区,可将文章作为帖子推送到社区,来自社区的回帖将作为博客评论进行联动(具体细节请浏览 B3log 构思 - 分布式社区网络)。

    这是一种全新的网络社区体验,让热爱记录和分享的你不再感到孤单!

    1440 引用 • 10067 回帖 • 491 关注
  • Tomcat

    Tomcat 最早是由 Sun Microsystems 开发的一个 Servlet 容器,在 1999 年被捐献给 ASF(Apache Software Foundation),隶属于 Jakarta 项目,现在已经独立为一个顶级项目。Tomcat 主要实现了 JavaEE 中的 Servlet、JSP 规范,同时也提供 HTTP 服务,是市场上非常流行的 Java Web 容器。

    162 引用 • 529 回帖 • 4 关注
  • WebSocket

    WebSocket 是 HTML5 中定义的一种新协议,它实现了浏览器与服务器之间的全双工通信(full-duplex)。

    48 引用 • 206 回帖 • 297 关注
  • FlowUs

    FlowUs.息流 个人及团队的新一代生产力工具。

    让复杂的信息管理更轻松、自由、充满创意。

    1 引用
  • 工具

    子曰:“工欲善其事,必先利其器。”

    295 引用 • 750 回帖 • 1 关注
  • Eclipse

    Eclipse 是一个开放源代码的、基于 Java 的可扩展开发平台。就其本身而言,它只是一个框架和一组服务,用于通过插件组件构建开发环境。

    76 引用 • 258 回帖 • 630 关注
  • Access
    1 引用 • 3 回帖 • 4 关注
  • 分享

    有什么新发现就分享给大家吧!

    247 引用 • 1794 回帖