`

JVM加载class文件的原理

阅读更多

     当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.
下面的例程说明了类装载器的父子关系.

   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、一个经典的实例说明

 

我们看看下面的代码:

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中的代码了。

 

 

分享到:
评论
2 楼 liuxuejin 2011-04-08  
lz!已经不写这个博客了。高就了!他朋友也好少更改这个博客呢
1 楼 GaoMatrix 2010-12-26  
楼主 怎么没有图片呀

相关推荐

    JVM加载class文件的原理机制

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

    codeegginterviewgroup#CodeEggDailyInterview#84.JVM加载class文件的原理机制

    JVM加载class文件的原理机制JVM加载class文件的原理机制 JVM中类的装载是由类加载器(ClassLoader)和它的子类来实现的,Java中的类加

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

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

    JVM执行子系统原理

    详细介绍了JVM执行子系统的工作原理,包括类文件结构与字节码指令(Class类文件结构、JVM字节码指令简介)、JVM类加载机制(类加载器、类加载时机、类加载过程)、字节码执行引擎(运行时候的栈结构、方法调用、方法...

    JVM中编译Class、内存回收、多线程原理和使用

    JVM负责装载class文件并执行,因此,首先是JDK如何将Java代码编译为class文件、如何装载class文件及如何执行class,将源码编译为class文件的实现取决于各个JVM实现或各种源码编译器。class文件通常由类加载器...

    JVM性能优化相关面试题21道.pdf

    JVM 面试题:Java 类加载过程、JVM 加载 Class 文件的原理机制、Java内存分配

    面试必问之jvm与性能优化

    类加载器本身也是一个类,而它的工作就是把class文件从硬盘读取到内存中。在写程序的时候,我们几乎不需要关心类的加载,因为这些都是隐式装载的,除非我们有特殊的用法,像是反射,就需要显式的加载所需要的类。 ...

    大厂真题之携程-Java高级

    描述一下 JVM 加载 Class 文件的原理机制? 在面试 java 工程师的时候,这道题经常被问到,故需特别注意。 Java 中的所有类,都需要由类加载器装载到 JVM 中才能运行。类加 载器本身也是一个类,而它的工作就是把 ...

    Java中Class类工作原理详解

    1.Class对象 Class对象包含了与类相关的信息。...如果尚未加载,JVM就会根据类名查找.class文件,并将其载入。 一旦某个类的Class对象被载入内存,它就被用来创建这个类的所有对象。看下面示例。

    深入理解JVM内存结构及运行原理全套视频加资料.txt

     第67讲 Class文件设计理念以及意义 免费 00:13:41  第68讲 文件结构-魔数 免费 00:09:49  第69讲 文件结构-常量池 免费 00:23:44  第70讲 文件结构-访问标志 免费 00:11:36  第71讲 文件结构-类索引 00:...

    java基础测试.doc

    描述一下JVM加载class文件的原理机制?  每一个class文件都是一个封装的整体,可供程序员的工程通过环境变量,包的应用调用,主要分三步:装载,链接,校验; .................

    DexClassloader:这个一个demo,用来实现加载class文件,如果在实际项目中可以实现,动态修改代码的业务逻辑

    这个一个demo,用来实现加载class文件,如果在实际项目中可以实现,动态修改代码的业务逻辑 首先在安卓中如果我们想实现的动态加载,比如知道安卓的底层运行原理, 首先安卓底层下载的时候使用的是 Classloader,同时...

    share-code-loaded:JVM代码字节热加载

    工作原理基于jdk代理方式,实现JVM的Instrumentation进行premain或agentmain代理加载以及TransformerManager的transform方法进行翻译,对增加的class进行listener,对已有class文件内容变化lastModified进行实时...

    Java进阶教程解密JVM视频教程

    * 在字节码与类加载技术章节,会从一个 class 文件开始分析其每一字节的含义。学习字节码指令的的运行流程,字节码指令与常量池、方法区的关系。掌握条件分支、循环控制、异常处理、构造方法在字节码级别的实现原理...

    Java面试宝典

    Java面试宝典 经典题库 Java中的异常处理机制的简单原理和应用 运行时异常与一般异常有何异同? Error与Exception有什么区别? JVM加载class文件的原理机制? …………

    JVM面试专题

    7、描述一下JVM加载class文件的原理机制? 8、Java对象创建过程 9、类的生命周期【加载过程】 10、Java 中会存在内存泄漏吗,请简单描述。 11、GC是什么?为什么要有GC? 12、做GC时,⼀个对象在内存各个Space中被...

    深入理解Java虚拟机视频教程(jvm性能调优+内存模型+虚拟机原理)视频教程

    第67节Class文件设计理念以及意义 [免费观看] 00:13:41分钟 | 第68节文件结构-魔数 [免费观看] 00:09:49分钟 | 第69节文件结构-常量池 [免费观看] 00:23:44分钟 | 第70节文件结构-访问标志 [免费观看] 00:11:36...

    java反射机制原理详解.docx

    我们创建一个类,通过编译,生成对应的.calss文件,之后使用java.exe加载(jvm的类加载器)此.class文件,此.class文件加载到内存以后,就是一个运行时类,存在缓存区,那么这个运行时类的本身就是一个class的实例 ...

    Java基础加强之类加载器

    类加载是指将类的class文件读入内存,并为之创建一个Java.lang.Class对象,也是说当程序中使用任何类时,系统都会为之建立一个java.lang.Class对象。  类加载器负责加载所有类,系统为所有被载入内存中的类生成一...

    JVM——Java虚拟机架构

    平台无关性原理:编译后的Java程序(.class文件)由JVM执行。JVM屏蔽了与具体平台相关的信息,使程序可以在多种平台上不加修改地运行。Java虚拟机在执行字节码时,把字节码解释成具体平台上的机器指令执行。因此实现...

Global site tag (gtag.js) - Google Analytics