转载地址 : http://hxraid.iteye.com/blog/747625

当Java编译器编译好.class文件之后,我们需要使用JVM来运行这个class文件。那么最开始的工作就是要把字节码从磁盘输入到内存中,这个过程我们叫做【加载 】。加载完成之后,我们就可以进行一系列的运行前准备工作了,比如: 为类静态变量开辟空间,将常量池存放在方法区内存中并实现常量池地址解析,初始化类静态变量等等。这篇文章我们要好好谈谈JVM是如何加载class文件的?
 
1、JVM加载类的过程
      当我们使用命令来执行某一个Java程序(比如Test.class)的时候:java Test
      (1) java.exe 会帮助我们找到 JRE ,接着找到位于 JRE 内部的 jvm.dll ,这才是真正的 Java 虚拟机器 , 最后加载动态库,激活 Java 虚拟机器。
      (2) 虚拟机器激活以后,会先做一些初始化的动作,比如说读取系统参数等。一旦初始化动作完成之后,就会产生第一个类装载器 ―― Bootstrap Loader(启动类装载器 ) 。
      (3) Bootstrap Loader 所做的初始工作中,除了一些基本的初始化动作之外,最重要的就是加载 Launcher.java 之中的 ExtClassLoader(扩展类装载器) ,并设定其 Parent 为 null ,代表其父加载器为 BootstrapLoader 。
      (4) 然后 Bootstrap Loader 再要求加载 Launcher.java 之中的 AppClassLoader(用户自定义类装载器 ) ,并设定其 Parent 为之前产生的 ExtClassLoader 实体。这两个加载器都是以静态类的形式存在的。
      这里要请大家注意的是, Launcher$ExtClassLoader.class 与 Launcher$AppClassLoader.class 都是由 Bootstrap Loader 所加载,所以 Parent 和由哪个类加载器加载没有关系。
      初学者对这个过程很难理解,我们将在下面详细的讲讲类装载器和"Parent"是什么。
 
 
2、类装载器体系结构
 
      JVM加载class文件必须通过一个叫做类装载器的程序,它的作用就是从磁盘文件中将要运行代码的字节码流加载进内存(JVM管理的方法区)中。下面是几个比较重要的概念:
 
      (1) 启动类装载器 : 每个Java虚拟机实现都必须有一个启动类装载器。它只负责在系统类(核心Java API的class文件)的安装路径中查找要装入的类。这个装载器的实现由C++ 所撰写而成,是JVM实现的一部分。

(2) 扩展类装载器和自定义类装载器 : 负责除核心Java API以外的其它class文件的装载。例如、用于安装或下载标准扩展的class文件,在类路径中发现的类库的class文件,用于应用程序运行的class文件等等。这里有一点需要注意:自定义类装载器并非由应用程序员自己实现,它也是JVM
 
      (3) 命名空间:  Java虚拟机为每一个类装载器维护一个唯一标识的命名空间。一个Java程序可以多次装载具有同一个全限定名的多个类。 Java虚拟机要确定这"多个类"的唯一性,因此,当多个类 装载器都装载了同名的类时,为了唯一地标识这个类,还要在类名前加上装载该类的类装载器的标识(指出了类所位于的命名空间)。下图显示了两个类装载器有关的命名空间,显然,不同的类装载器允许装载相同的类Volcano。
      命名空间有助于安全的实现,因为你可以有效地在装入了不同命名空间的类之间设置一个防护罩。在Java虚拟机中,在同一个命名空间内的类可以直接进行交 互,而不同的命名空间中的类甚至不能察觉彼此的存在,除非显式地提供了允许它们进行交互的机制。一旦加载后,如果一个恶意的类被赋予权限访问其他虚拟机加 载的当前类,它就可以潜在地知道一些它不应该知道的信息,或者干扰程序的正常运行。
 
 
3、双亲委托模型
 
      用户自定义类装载器经常依赖其他类装载器——至少依赖于虚拟机启动时创建的启动类装载器—来帮助它实现一些类装载请求:.在版本1.2前,非启动类装载器 必须显式地求助于其他类装载器,类装载器可以请求另一个用户自定义的类装载器来装载一个类,这个请求是通过对被请求的用户自定义类装载器调用 loadClass()来实现的。除此以外,类装载器也可以通过调用findSystemClass()来请求启动类装载器来装载类,这是类 ClassLoader中的一个静态方法。
      在版本1.2中,类装载器请求另一个类装载器来装载类型的过程被形式化,称为双亲委派模式 。
      从版本1.2开始、除启动类装载器以外的每一个类装载器,都有一个“双亲”类装载器 ,在某个特定的类装载器试图以常用方式装载类型以前,它会先默认地将这个任务“委派”给它的双亲——清求它的双亲来装载这个类型。这个双亲再依次请求它自 己的双亲来装载这个类型。这个委派的过程一直向上继续,直到达到启动类装载器,通常启动类装载器是委派链中的最后一个类装载器。如果一个类装载器的双亲类 装载器有能力来装载这个类型。则这个类装载器返回这个类型。否则,这个类装载器试图自己来装载这个类。
 
     当Java虚拟机开始运行时,在应用程序开始启动以前,它至少创建一个用户自定义装载器,也可能创建多个.所有这些装载器被连接在一个Parent-Child的委托链中,在这个链的顶端是启动类装载器。
 
    例如:假设你写了一个应用程序,在虚拟机上运行它.虚拟机在启动时实例化了两个用户自定义类装载器:一个"扩展类装载器",一个"类路径类装载器".这些类装载器和启动类装载器一起联入一个Parent-Child委托链中,如下图所示.

上图所示类路径类装载器的Parent是扩展类装载器, 扩展类装载器的Parent是启动类装载器.在图2中,类路径类装载器就被实例为系统类装载器.假设你的程序实例化它的网络类装载器,它就指明了系统类装载器作为它的Parent.
下面的例程说明了类装载器的父子关系.

Java代码  
  1. package test;
  2. import java.net.URL;
  3. import java.net.URLClassLoader;
  4. public class ClassLoaderTest {
  5. private static int count = -1;
  6. public static void testClassLoader(Object obj) {
  7. if (count < 0 && obj == null) {
  8. System.out.println("Input object is NULL";
  9. return;
  10. }
  11. ClassLoader cl = null;
  12. if (obj != null && !(obj instanceof ClassLoader)) {
  13. cl = obj.getClass().getClassLoader();
  14. } else if (obj != null) {
  15. cl = (ClassLoader) obj;
  16. }
  17. count++;
  18. String parent = "";
  19. for (int i = 0; i < count; i++) {
  20. parent += "Parent ";
  21. }
  22. if (cl != null) {
  23. System.out.println(
  24. parent + "ClassLoader name = " + cl.getClass().getName());
  25. testClassLoader(cl.getParent());
  26. } else {
  27. System.out.println(
  28. parent + "ClassLoader name = BootstrapClassLoader";
  29. count = -1;
  30. }
  31. }
  32. public static void main(String[] args) {
  33. URL[] urls = new URL[1];
  34. URLClassLoader urlLoader = new URLClassLoader(urls);
  35. ClassLoaderTest.testClassLoader(urlLoader);
  36. }
  37. }
   package test; import java.net.URL; import java.net.URLClassLoader; public class ClassLoaderTest { private static int count = -1; public static void testClassLoader(Object obj) { if (count < 0 && obj == null) { System.out.println("Input object is NULL"; return; } ClassLoader cl = null; if (obj != null && !(obj instanceof ClassLoader)) { cl = obj.getClass().getClassLoader(); } else if (obj != null) { cl = (ClassLoader) obj; } count++; String parent = ""; for (int i = 0; i < count; i++) { parent += "Parent "; } if (cl != null) { System.out.println( parent + "ClassLoader name = " + cl.getClass().getName()); testClassLoader(cl.getParent()); } else { System.out.println( parent + "ClassLoader name = BootstrapClassLoader"; count = -1; } } public static void main(String[] args) { URL[] urls = new URL[1]; URLClassLoader urlLoader = new URLClassLoader(urls); ClassLoaderTest.testClassLoader(urlLoader); } }  

以上例程的输出为:
ClassLoader name = java.net.URLClassLoader
Parent ClassLoader name = sun.misc.Launcher$AppClassLoader
Parent Parent ClassLoader name = sun.misc.Launcher$ExtClassLoader
Parent Parent Parent ClassLoader name = BootstrapClassLoader
 
 
类装载器请求过程
以上例程1为例.将main方法改为:
        ClassLoaderTest tc = new ClassLoaderTest();
        ClassLoaderTest.testClassLoader(tc);
输出为:
ClassLoader name = sun.misc.Launcher$AppClassLoader
Parent ClassLoader name = sun.misc.Launcher$ExtClassLoader
Parent Parent ClassLoader name = BootstrapClassLoader
 
     程序运行过程中,类路径类装载器发出一个装载ClassLoaderTest类的请求, 类路径类装载器必须首先询问它的Parent---扩展类装载器 ---来查找并装载这个类,同样扩展类装载器首先询问启动类装载器。由于ClassLoaderTest不是 Java API(JAVA_HOME\jre\lib)中的类,也不在已安装扩展路径(JAVA_HOME\jre\lib\ext)上,这两类装载器 都将返回而不会提供一个名为ClassLoaderTest的已装载类给类路径类装载器。类路径类装载器只能以它自己的方式来装载 ClassLoaderTest,它会从当前类路径上下载这个类。这样,ClassLoaderTest就可以在应用程序后面的执行中发挥作用。
      在上例中,ClassLoaderTest类的testClassLoader方法被首次调用,该方法引用了Java API中的类 java.lang.String。Java虚拟机会请求装载ClassLoaderTest类的类路径类装载器来装载 java.lang.String。就像前面一样,类路径类装载器首先将请求传递给它的Parent类装载器,然后这个请求一路被委托到启动类装载器。但 是,启动类装载器可以将java.lang.String类返回给类路径类装载器,因为它可以找到这个类,这样扩展类装载器就不必在已安装扩展路径中查找 这个类,类路径类装载器也不必在类路径中查找这个类。扩展类装载器和类路径类装载器仅需要返回由启动类装载器返回的类java.lang.String。 从这一刻开始,不管何时ClassLoaderTest类引用了名为java.lang.String的类,虚拟机就可以直接使用这个 java.lang.String类了。


4、一个经典的实例说明

我们看看下面的代码:

Java代码  
  1. package java.lang;
  2. public class String {
  3. public static void main(String[] args){
  4. }
  5. }
package java.lang;public class String {public static void main(String[] args){}
}

大家发现什么不同了吗?对了,我们写了一个与JDK中String一模一样的类,连包java.lang都一样,唯一不同的是我们自定义的String类有一个main函数。我们来运行一下:

java.lang.NoSuchMethodError: main
                     Exception in thread "main"

这是为什么? 我们的String类不是明明有main方法吗?

其实联系我们上面讲到的双亲委托模型,我们就能解释这个问题了。

运行这段代码,JVM会首先创建一个自定义类加载器,不妨叫做AppClassLoader,并把这个加载器链接到委托链中:AppClassLoader -> ExtClassLoader -> BootstrapLoader。

然后AppClassLoader会将加载java.lang.String的请求委托给ExtClassLoader,而 ExtClassLoader又会委托给最后的启动类加载器BootstrapLoader。

启动类加载器BootstrapLoader只能加载JAVA_HOME\jre\lib中的class类(即J2SE API),问题是标准API中确实有一个java.lang.String(注意,这个类和我们自定义的类是完全两个类)。BootstrapLoader以为找到了这个类,毫不犹豫的加载了j2se api中的java.lang.String。

最后出现上面的加载错误(注意不是异常,是错误,JVM退出),因为API中的String类是没有main方法的。

结论:我们当然可以自定义一个和API完全一样的类,但是由于双亲委托模型,使得我们不可能加载上我们自定义的这样一个类。所以J2SE规范中希望我们自定义的包有自己唯一的特色(网络域名)。还有一点,这种加载器原理使得JVM更加安全的运行程序,因为黑客很难随意的替代掉API中的代码了。

jvm加载class原理相关推荐

  1. J2EE JVM加载class文件的原理机制

    JVM加载class文件的原理机制 1.Java中的所有类,必须被装载到jvm中才能运行,这个装载工作是由jvm中的类装载器完成的,类装载器所做的工作实质是把类文件从硬盘读取到内存中 2.java中的 ...

  2. JVM加载class文件的原理机制

    阅读目录 1.JVM 简介 2.JVM 的组成部分 3.JVM加载class文件的原理机制 在面试java工程师的时候,这道题经常被问到,故需特别注意. 回到顶部 1.JVM 简介 JVM 是我们Ja ...

  3. 常见Java面试题之JVM加载class文件的原理机制

    JVM加载class文件的原理机制 JVM中类的装载是由类加载器(ClassLoader)和它的子类来实现的,Java中的类加载器是一个重要的Java运行时系统组件,它负责在运行时查找和装入类文件中的 ...

  4. 描述一下JVM加载class文件的原理机制

    Java中的所有类,都需要由类加载器装载到JVM中才能运行.类加载器本身也是一个类,而它的工作就是把class文件从硬盘读取到内存中.在写程序的时候,我们几乎不需要关心类的加载,因为这些都是隐式装载的 ...

  5. java文件读入原理_描述一下JVM加载class文件的原理机制

    1.JVM 简介 JVM 是我们Javaer 的最基本功底了,刚开始学Java 的时候,一般都是从"Hello World "开始的,然后会写个复杂点class ,然后再找一些开源 ...

  6. Java提高篇——JVM加载class文件的原理机制

    阅读目录 1.JVM 简介 2.JVM 的组成部分 3.JVM加载class文件的原理机制 在面试java工程师的时候,这道题经常被问到,故需特别注意. 回到顶部 1.JVM 简介 JVM 是我们Ja ...

  7. 网页性能优化02-懒加载工作原理

    懒加载工作原理 1.1-懒加载介绍(以图片懒加载为例) 1.为什么要有懒加载技术 (1)img标签特点:不管图片隐藏还是显示 有src属性都会去加载 例如电商类网站,一个页面有几百张图片.有时候假设用 ...

  8. python类加载机制_PHP面向对象自动加载机制原理与用法分析

    本文实例讲述了PHP面向对象自动加载机制原理与用法.分享给大家供大家参考,具体如下: 在学习PHP的面向对象的时候,会知道很多"语法糖",也就是魔术方法.有一个加自动加载的魔术方法 ...

  9. java string rt_如何使jvm加载我的java.lang.String而不是rt.jar中的那个

    我认真研究了Java类加载器.现在,我想编写一个与rt.jar中的类之一具有相同的包名称和类名称的类.例如,我自己编写一个java.lang.String类,以及如何打破父级委托模型以使jvm加载我的 ...

最新文章

  1. 福师计算机在线作业一2,福师《计算机》在线作业二答案.doc
  2. antd vue关闭模态对话框_如何在Bootstrap项目中用Vue.js替代jQuery
  3. 联系人排序java代码_Android仿微信联系人按字母排序
  4. Matlab处理图像二三例
  5. android云新消息接收者全局,消息推送之百度云推送Android集成与用法(示例代码)...
  6. CTF MISC(杂项)知识点总结——图片类(一)
  7. 如何在Python中使用Rest API
  8. 国王的烦恼 蓝桥杯(最小生成树 kru)
  9. 再见,MySQL !
  10. SSD硬盘,先不要用在Server上
  11. Oracle计算分组分位数
  12. 蓝桥杯 土地的面积计算
  13. 学历对于人工智能方向重要吗
  14. java orm全称_[Java-基础] 什么是ORM
  15. SpringCloud Gateway微服务网关实战与源码分析-上
  16. leetcode进行手机解绑与换绑
  17. python 图片生成视频 moviepy_MoviePy
  18. No enclosing instance of type 错误的解决方法
  19. 以太坊Dapp项目-拍卖网站-智能合约编写测试
  20. 为企业用户配置专有UPN后缀[为企业维护windows server 2008系列十三]

热门文章

  1. 【Servlet】JSP学习-概念和运行原理
  2. PAT1048 数字加密 (20 分)
  3. 一文读懂密码学中的证书
  4. 分布式系统CAP定理
  5. nginx tcp代理_nginx——TCP/UDP Load Balancing
  6. SpringBoot的@SpringBootApplication注解和其他几个注解
  7. Flink专题-Source
  8. lostash Timeout executing grok 问题排查
  9. leetcode--338. 比特位计数
  10. Leecode31. 下一个排列——Leecode大厂热题100道系列