虚拟机把描述类的数据从Class文件夹加载到内存,并对数据进行小燕、转换解析和初始化,最终形成可以被虚拟机直接使用的java类型,这就是虚拟机的类加载机制。

下面所说的Class文件不是具体的某个文件,应当是一串二进制的字节流,无论何种形式存在都可以。

类从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期:加载(Loading),验证(Verification),准备(Preparation),解析(Resolution)、初始化(Initializatin),是哦那个(Using)和卸载(Unloading)7个阶段。其中验证,准备,解析3个部分统称为链接(Linking)。

什么情况下进行加载并没有强制约束,但是对于初始化阶段,虚拟机规范则是严格规定了有且只有5种情况必须立即对类进行“初始化”(而加载,验证,准备自然需要在此之前开始):

  1. 遇到new,getstatic,putstatic或invokestatic这四个字节码指令。生成这4条指令的最常见Java代码场景是:使用new关键字,读取或设置一个类的静态字段(被final修饰,已在编译器把结果放入常量池的惊天字段除外),以及调用一个类的静态方法的时候。
  2. 使用java.lang.reflect 包的方法对类惊醒反射调用的时候。如果类还没初始化,则需要触发初始化
  3. 初始化一个类的时候,如果其父类还没初始化
  4. 虚拟机启动的时候,用户需要一个要执行的主类,虚拟机会先初始化这个主类。
  5. 当使用JDK1.7 的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果REF_getStatic、REF_putStatic、REF_invokeStatic、的方法句柄,并且这个方法句柄所对应的主类没有进行初始化,则需要先触发其初始化。

这里不说加载、验证、准备、解析、初始化的细节了。

类与类加载器

类加载器虽然只用实现类的加载动作,但它在java程序中起到的作用却远远不限于类加载阶段。对于任意一个类,都需要由加载它的类加载器和这个类本身一同确立其在java虚拟机中的唯一性,每一个类加载器,都拥有一个独立的类名称空间。这句话更通俗一点的意思就是:比较两个是否相等,只有这两个类是由一个类加载器加载的前提下才有意义,否则即使这两个类来源于同一个Class文件,被同一个虚拟机加载,只有加载他们的类加载器不同,那这两个类就必定不相等。

这里说的“相等”,包括类的Class对象的equals()方法、isAssignableFrom()方法、isInstance()方法的返回结果,也包括使用instanceof关键字做对象所属关系判定登情况。

双亲委派模型

从java虚拟机的角度来讲,只存再两种不同的类加载器:一种是启动类加载器,这个类加载器使用c++语言实现,是虚拟机自身的一部分;另一种就是所有其他的类加载器,这些类加载器都由Java语言实现,独立于虚拟机外部,并且全部继承自抽象类java.lang.ClassLoader。从java开发人员的角度来看,类加载器还可以划分的更细致一些

  • 启动类加载器(Bootstrap ClassLoader):这个类加载器不择将存放在<JAVA_HOME>\lib 目录中的,或者被-Xbootclasspath 参数所指定的路径中的,并且是虚拟机识别的类库加载到虚拟机内存中。启动类加载器无法被Java程序直接引用,用户在编写自定义类加载器时,如果需要把加载请求委派给引导类加载器,那直接使用null代替即可。
  • 扩展类加载器(Extension ClassLoader):这个加载器由sun.misc.Launcher$ExtClassLoader实现,它负责夹杂<JAVA_HOME>\lib\ext目录中的,或者被java.ext.dirs系统变量所指定的路径中的所有类库,开发者可以直接使用扩展类加载器。
  • 应用程序类加载器(Application ClassLoader):这个类加载器由sun.misc.Launcher$AppClassLoader实现。由于这个类加载器是ClassLoader中的getSystemClassLoader()方法的返回值,所以一般也称它为系统类加载器。它负责加载用户类路径(ClassPath)上所指定的类库,开发者可以直接使用这个类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序默认的类加载器。

类加载器的双亲委派模型并不是一个强制性的约束模型,而是Java设计者推荐给开发这的一种类加载器实现方式。

双亲委派模型的工作过程:如果一个类加载器接收到了类加载的请求,它首先把这个请求委托给他的父类加载器去完成,每个层次的类加载器都是如此,因此所有的加载请求都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它在搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载。
好处:java类随着它的类加载器一起具备了一种带有优先级的层次关系。例如类java.lang.Object,它存放在rt.jar中,无论哪个类加载器要加载这个类,最终都会委派给启动类加载器进行加载,因此Object类在程序的各种类加载器环境中都是同一个类。相反,如果用户自己写了一个名为java.lang.Object的类,并放在程序的Classpath中,那系统中将会出现多个不同的Object类,java类型体系中最基础的行为也无法保证,应用程序也会变得一片混乱。
实现:在java.lang.ClassLoader的loadClass()方法中,先检查是否已经被加载过,若没有加载则调用父类加载器的loadClass()方法,若父加载器为空则默认使用启动类加载器作为父加载器。如果父加载失败,则抛出ClassNotFoundException异常后,再调用自己的findClass()方法进行加载。

双亲委派模型的破坏

双亲委派模型的第一次“被破坏”其实发生在双亲委派模型出现之前--即JDK1.2发布之前。由于双亲委派模型是在JDK1.2之后才被引入的,而类加载器和抽象类java.lang.ClassLoader则是JDK1.0时候就已经存在,面对已经存在 的用户自定义类加载器的实现代码,Java设计者引入双亲委派模型时不得不做出一些妥协。为了向前兼容,JDK1.2之后的java.lang.ClassLoader添加了一个新的proceted方法findClass(),在此之前,用户去继承java.lang.ClassLoader的唯一目的就是重写loadClass()方法,因为虚拟在进行类加载的时候会调用加载器的私有方法loadClassInternal(),而这个方法的唯一逻辑就是去调用自己的loadClass()。JDK1.2之后已不再提倡用户再去覆盖loadClass()方法,应当把自己的类加载逻辑写到findClass()方法中,在loadClass()方法的逻辑里,如果父类加载器加载失败,则会调用自己的findClass()方法来完成加载,这样就可以保证新写出来的类加载器是符合双亲委派模型的。

双亲委派模型的第二次“被破坏”是这个模型自身的缺陷所导致的,双亲委派模型很好地解决了各个类加载器的基础类统一问题(越基础的类由越上层的加载器进行加载),基础类之所以被称为“基础”,是因为它们总是作为被调用代码调用的API。但是,如果基础类又要调用用户的代码,那该怎么办呢。

这并非是不可能的事情,一个典型的例子便是JNDI服务,它的代码由启动类加载器去加载(在JDK1.3时放进rt.jar),但JNDI的目的就是对资源进行集中管理和查找,它需要调用独立厂商实现部部署在应用程序的classpath下的JNDI接口提供者(SPI, Service Provider Interface)的代码,但启动类加载器不可能“认识”之些代码,该怎么办?

为了解决这个困境,Java设计团队只好引入了一个不太优雅的设计:线程上下文件类加载器(Thread Context ClassLoader)。这个类加载器可以通过java.lang.Thread类的setContextClassLoader()方法进行设置,如果创建线程时还未设置,它将会从父线程中继承一个;如果在应用程序的全局范围内都没有设置过,那么这个类加载器默认就是应用程序类加载器。了有线程上下文类加载器,JNDI服务使用这个线程上下文类加载器去加载所需要的SPI代码,也就是父类加载器请求子类加载器去完成类加载动作,这种行为实际上就是打通了双亲委派模型的层次结构来逆向使用类加载器,已经违背了双亲委派模型,但这也是无可奈何的事情。Java中所有涉及SPI的加载动作基本上都采用这种方式,例如JNDI,JDBC,JCE,JAXB和JBI等。

双亲委派模型的第三次“被破坏”是由于用户对程序的动态性的追求导致的,例如OSGi的出现。在OSGi环境下,类加载器不再是双亲委派模型中的树状结构,而是进一步发展为网状结构

深入理解JVM虚拟机-7虚拟机类加载机制的更多相关文章

  1. 《深入理解 Java 虚拟机》学习 -- 类加载机制

    <深入理解 Java 虚拟机>学习 -- 类加载机制 1. 概述 虚拟机把描述类的数据从 Class 文件加载到内存,并对数据进行校验.转换解析和初始化,最终形成可以被虚拟机直接使用的 J ...

  2. 深入理解JVM(③)虚拟机的类加载时机

    前言 Java虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验.转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这个过程被称为虚拟机的类加载机制. 类加载的时机 一个类型 ...

  3. 深入理解JVM(③)虚拟机的类加载过程

    前言 上一篇我们介绍到一个类的生命周期大概分7个阶段:加载.验证.准备.解析.初始化.使用.卸载.并且也介绍了类的加载时机,下面我们将介绍一下虚拟机中类的加载的全过程.主要是类生命周期的,加载.验证. ...

  4. 深入理解JVM(③)虚拟机性能监控、故障处理工具

    前言 JDK的bin目录中有一系列的小工具,除了java.exe.javac.exe这两个编译和运行Java程序外,还有打包.部署.签名.调试.监控.运维等各种场景都会用到这些小工具. 这些工具根据软 ...

  5. JVM内存模型与类加载机制

    一. java虚拟机的内存模型如图: 补习一下jvm内存模型中的各个组成部分 堆: 我们new出来的对象全部放在堆中,他是jvm所能够动态分配的最大的一块空间 优点: 内存动态分配,生命周期不必事先告 ...

  6. jvm字节码和类加载机制

    Class类文件的结构 任何一个Class文件都对应着唯一一个类或接口的定义信息,但反过来说,类或接口并不一定都得定义在文件里(类和接口也可以用反射的方式通过类加载器直接生成) Class文件时一组以 ...

  7. 深入理解JVM(一)类加载器部分、类变量、常量、jvm参数

    类加载概述 在java代码中,类型的加载.连接与初始化过程都是在程序运行期间完成的 类型:class.interface(object本身).类型可在运行期间生成,如动态代理.一种runting概念 ...

  8. 深入理解JVM(一)类加载器部分:双亲委派模型

    类加载器的父亲委托机制 在父亲委托机制中,各个类加载器按照父子关系形成了树形结构,除了根类加载器之外,其余的类加载器都有且只有一个父加载器. 先让最顶层可以加在的父加载器加栽(所有可加载的加载器中,处 ...

  9. 玩命学JVM(二)—类加载机制

    前言 Java程序运行图: 上一篇玩命学JVM(一)-认识JVM和字节码文件我们简单认识了 JVM 和字节码文件.那JVM是如何使用字节码文件的呢?从上图清晰地可以看到,JVM 通过类加载器完成了这一 ...

  10. JVM系列3:类加载机制

    了解类加载机制也是深入了解Java的重要一环,它包括加载过程.类加载器.加载机制等内容. 以下是我总结的思维导图. 首先讲讲类加载的时机,以下是会触发类加载的时机: 1.new.get/put/inv ...

随机推荐

  1. Bean的生命周期

    Bean的生命周期 原文:http://997004049-qq-com.iteye.com/blog/1729793 任何一个事物都有自己的生命周期,生命的开始.生命中.生命结束.大家最熟悉的应该是 ...

  2. java提高篇---HashTable

    在java中与有两个类都提供了一个多种用途的hashTable机制,他们都可以将可以key和value结合起来构成键值对通过put(key,value)方法保存起来,然后通过get(key)方法获取相 ...

  3. NET MVC1项目升级到MVC2最简单的方法

    NET MVC1项目升级到MVC2最简单的方法 把MVC1项目升级到MVC2,最简单的做法如下: 新建MVC2项目 新建一个MVC2项目,把原来MVC1的项目文件全部拷贝到新建MVC2项目目录里,依照 ...

  4. C++vector迭代器失效的问题

    转载:http://blog.csdn.net/olanmomo/article/details/38420907 转载:http://blog.csdn.net/stpeace/article/de ...

  5. 11个让你吃惊的 Linux 终端命令

    原文:http://linux.about.com/od/commands/tp/11-Linux-Terminal-Commands-That-Will-Rock-Your-World.htm 作者 ...

  6. Web自定义协议,BS端启动CS端,

    实例 1.准备CS项目,windows窗体应用程序,拖进来一个label控件来接受BS的参数,并显示,右击生成,复制该文件的bin目录下的exe,例如放在以下路径,例如C:\\simu\\下, 2.编 ...

  7. V-rep学习笔记:曲柄摇杆机构

    在ADAMS中创建一个曲柄摇杆机构很方便,但是V-rep中建模就比较麻烦.下面将自己在V-rep中建立曲柄摇杆机构模型的过程记录下来(由于对V-rep不是很熟,可能会有一些错误,只能等以后发现了再改进 ...

  8. Linux运行变量中的命名脚本

    single="ls -l" $single ============= multi="ls -l | grep e" echo $multi > tmp ...

  9. json和jsonp的传输方式

    jsonp传输会解决跨域的问题 $.ajax({ async: false, /* url: "http://127.0.0.1:8080/2015020601/background/mea ...

  10. firefox渗透师必备的利器

    工欲善必先利其器,firefox一直是各位渗透师必备的利器,小编这里推荐34款firefox渗透测试辅助插件,其中包含渗透测试.信息收集.代理.加密解密等功能. 1:Firebug Firefox的 ...