《深入理解Java虚拟机》读书笔记4——虚拟机类加载机制

1.类加载过程

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

       类从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期包括了:加载、验证、准备、解析、初始化、使用和卸载七个阶段。

《《深入理解Java虚拟机》读书笔记4——虚拟机类加载机制》

1.1加载

        在加载阶段,虚拟机需要完成以下三件事情:

  1. 通过一个类的全限定名来获取定义此类的二进制字节流。
  2. 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
  3. 在Java堆中生成一个代表这个类的java.lang.Class对象,最为方法区这些数据的访问入口。

        加载阶段完成后,虚拟机外部的二进制字节流就按照虚拟机所需的格式存储在方法区之中,方法区中的数据存储格式由虚拟机实现自行定义,虚拟机规范未规定此区域的具体数据结构。然后再Java堆中实例化一个java.lang.Class类的对象,这个对象将作为程序访问方法区中的这些类型数据的外部接口。加载阶段与连接阶段的部分内容是交叉进行的。

1.2验证

        这一阶段的目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。它会完成下面四个阶段的检验过程:文件格式验证、元数据验证、字节码验证和符号引用验证。

  • 文件格式验证:验证字节流是否符合Class文件格式的规范,并且能被当前版本的虚拟机处理。
  • 元数据验证:对字节码描述的信息进行语义分析,以保证其描述的信息符合Java语言规范的要求。
  • 字节码验证:进行数据流和控制流分析。以保证被检验类的方法在运行时不会做出危害虚拟机安全的行为。
  • 符号引用验证:对类自身以外(常量池中的各种符号引用)的信息进行匹配性的校验。

1.3准备

        准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些内存都在方法区中进行分配。

1.4解析

        解析阶段是虚拟机将常量池内的符合引用替换为直接引用的过程。解析动作主要针对类或接口、字段、类方法、接口方法四类符号引用进行,分别对应于常量池的CONSTANT_Class_info、CONSTANT_Fieldref_info、CONSTANT_Methodref_info及CONSTANT_InterfaceMethodref_info四种常量类型。

1.5初始化

       类初始化阶段是类加载的最后一步,这一步是开始执行类中定义的Java程序代码(或者说是字节码)。

2.类加载器

2.1双亲委派模型

       站在Java虚拟机的角度讲,只存在两种不同的类加载喊叫:一种是启动类加载器(Bootstrap ClassLoader),这个类加载器使用C++语言实现,是虚拟机自身的一部分;另外一种用户自定义类装载器。前者是Java虚拟机实现的一部分,后者是Java程序的一部分。由不同的类装载器装载的类将被放在虚拟机内部的不同命名空间中。
        从Java开发人员的角度来看,类加载器还可以划分的更细致一些,绝大部分Java程序都会使用到以下三种系统提供的类加载器:

       启动类加载器(bootstrap classloader):它用来加载 Java 的核心库,是用原生代码(本地代码,与平台有关)来实现的,并不继承自java.lang.ClassLoader。这个类加载器负责将存放在<JAVA_HOME>\lib目录中的,或者被-Xbootclasspath参数所指定的路径中的,并且是虚拟机识加的(仅按照文件名识别,如rt.jar,名字不符合的类库即使放在lib目录中也不会被加载)类库加载到虚拟机内存中。启动类加载器无法被Java程序直接引用。
       扩展类加载器(extensions classloader):扩展类加载器是由Sun的ExtClassLoader(sun.misc.Launcher$ExtClassLoader) 实现的。它负责将 <Java_Runtime_Home>/lib/ext 或者由系统变量java.ext.dir 指定位置中的类库加载到内存中。

       应用程序类加载器(application classloader):系统类加载器是由Sun AppClassLoader(sun.misc.Launcher$AppClassLoader)实现的,由于这个类加载器是ClassLoader中getSystemClassLoader()方法的返回值,所以一般也称它为系统类加载器。它根据 Java 应用的类路径(CLASSPATH)来加载 Java 类。开发者可以直接使用这个类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序默认的类加载器。

       应用程序都是由这三种类加载器互相配合进行加载的,如果有必要,我们还可以加入自定义的类加载器。因为JVM自带的ClassLoader只是懂得从本地文件系统加载标准的java class文件,因此如果编写了自己的ClassLoader,便可以做到如下几点:

  • 在执行非置信代码之前,自动验证数字签名。
  • 动态地创建符合用户特定需要的定制化构建类。
  • 从特定的场所取得java class,例如数据库中和网络中。

《《深入理解Java虚拟机》读书笔记4——虚拟机类加载机制》

      

       上图的这种层次关系称为类加载器的双亲委派模型
       双亲委派模型的工作流程是:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把请求委托给父加载器去完成,依次向上,因此,所有的类加载请求最终都应该被传递到顶层的启动类加载器中,只有当父加载器在它的搜索范围中没有找到所需的类时,即无法完成该加载,子加载器才会尝试自己去加载该类。
       使用双亲委派模型来组织类加载器之间的关系,有一个很明显的好处,就是Java类随着它的类加载器一起具备了一种带有优先级的层次关系,这对于保证Java程序的稳定运作很重要。例如,类java.lang.Object类存放在${JAVA_HOME}\jre\lib下的rt.jar之中,因此无论是哪个类加载器要加载此类,最终都会委派给启动类加载器进行加载,这边保证了Object类在程序中的各种类加载器中都是同一个类。

2.2破坏双亲委派模型

       上文提到双亲委派模型并不是一个强制性的约束模型,而是Java设计者们推荐给开发者们的类加载器实现方式。在Java世界里面大部分的类加载器都遵循这个模型,但也有例外的情况,到现在为止,双亲委派模型主要出现过三次较大规模的“被破坏”情况。

       双亲委派模型的第一次“被破坏”其实发生在双亲委派模型出现之前——即 JDK 1.2 发布之前。JDK 1.2 之后已不提倡用户再去覆盖loadClass() 方法,而应当把自己的类加载逻辑写到 findClass() 方法中,在 loadClass()方法的逻辑里如果父类加载失败,则会调用自己的 findClass() 方法来完成加载,这样就可以保证新写出来的类加载器是符合双亲委派规则的。
       双亲委派模型的第二次“被破坏”是由这个模型自身的缺陷所导致的。如果基础类又要调用回用户的代码,那该怎么办?如JNDI服务。为了解决这个问题, Java 设计团队只好引入了一个不太优雅的设计:线程上下文类加载器(Thread Context   ClassLoader)。

      双亲委派模型的第三次“被破坏”是由于用户对程序动态性的追求而导致的。OSGi 实现模块化热部署的关键则是它自定义的类加载器机制的实现。每一个程序模块(OSGi中称为Bundle)都有一个自己的类加载器,当需要更换一个Bundle时,就把Bundle连同类加载器一起换掉以实现代码的热替换。在 OSGi 环境下,类加载器不再是双亲委派模型中的树状结构,而是进一步发展为更加复杂的网状结构,当收到类加载请求时,OSGI将按照下面的顺序进行类搜索:

  1. 将以java.*开头的类,委派给父类加载器加载。
  2. 否则,将委派列表名单内的类,委派给父类加载器加载。
  3. 否则,将import列表中的类,委派给Export这个类的Bundle的类加载器加载。
  4. 否则,查找当前Bundle的ClassPath,使用自己的类加载器加载。
  5. 否则,查找类是否在自己的Fragment Bundle中,如果在,则委派给Fragment Bundle的类加载器加载。
  6. 否则,查找Dynamic Import列表的Bundle,委派给对应Bundle的类加载器加载。
  7. 否则,类查找失败。

        上面的查找顺序中只有开头两点仍然符合双亲委派规则,其余的类查找都是在平级的类加载器中进行的。在 Java 程序员中基本有一个共识:OSGi中对类加载器的使用是很值得学习的,弄懂了OSGi的实现,就可以算是掌握了类加载器的精髓。

《深入理解Java虚拟机》读书笔记系列现共有7篇文章,如下。如需了解更详细内容,可购买原书。

  1. Java内存区域与内存溢出异常
  2. 垃圾收集器与内存分配策略
  3. 类文件结构
  4. 虚拟机类加载机制
  5. 类加载及执行子系统的案例与实战
  6. Java内存模型和线程
  7. 线程安全与锁优化
    原文作者:java虚拟机
    原文地址: https://blog.csdn.net/limingjian/article/details/41863877
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞