Java——classloader 类加载

    科技2022-07-21  92

    类加载过程

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

    加载-》验证-》准备-》解析-》初始化-》使用-》卸载

    其中类加载的过程包括了加载、验证、准备、解析、初始化五个阶段。 在这五个阶段中,加载、验证、准备和初始化这四个阶段发生的顺序是确定的,而解析阶段则不一定,它在某些情况下可以在初始化阶段之后开始,这是为了支持 Java 语言的运行时绑定(也成为动态绑定或晚期绑定)。

    加载

    加载时类加载的第一个过程,在这个阶段,将完成一下三件事情:

    通过一个类的全限定名获取该类的二进制流。将该二进制流中的静态存储结构转化为方法去运行时数据结构。在内存中生成该类的Class对象,作为该类的数据访问入口。

    验证

    验证的目的是为了确保Class文件的字节流中的信息不回危害到虚拟机.在该阶段主要完成以下四钟验证:

    文件格式验证:验证字节流是否符合Class文件的规范,如主次版本号是否在当前虚拟机范围内,常量池中的常量是否有不被支持的类型.元数据验证:对字节码描述的信息进行语义分析,如这个类是否有父类,是否集成了不被继承的类等。字节码验证:是整个验证过程中最复杂的一个阶段,通过验证数据流和控制流的分析,确定程序语义是否正确,主要针对方法体的验证。如:方法中的类型转换是否正确,跳转指令是否正确等。符号引用验证:这个动作在后面的解析过程中发生,主要是为了确保解析动作能正确执行。

    准备

    准备阶段是为类的静态变量分配内存并将其初始化为默认值,这些内存都将在方法区中进行分配。准备阶段不分配类中的实例变量的内存,实例变量将会在对象实例化时随着对象一起分配在Java堆中。

    解析

    该阶段主要完成符号引用到直接引用的转换动作。解析动作并不一定在初始化动作完成之前,也有可能在初始化之后。

    初始化

    初始化时类加载的最后一步,前面的类加载过程,除了在加载阶段用户应用程序可以通过自定义类加载器参与之外,其余动作完全由虚拟机主导和控制。到了初始化阶段,才真正开始执行类中定义的Java程序代码。

    classloader分类

    实现通过类的权限定名获取该类的二进制字节流的代码块叫做类加载器。 主要有一下四种类加载器:

    启动类加载器(Bootstrap ClassLoader)用来加载java核心类库,无法被java程序直接引用。是用原生C++代码来实现的,并不继承自java.lang.ClassLoader。如:rt.jar、resources.jar、charsets.jar等扩展类加载器(extensions class loader):它用来加载 Java 的扩展库。Java 虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载 Java 类。系统类加载器(system ClassLoader):也叫 APPClassLoader 它根据 Java 应用的类路径(CLASSPATH)来加载 Java 类。一般来说,Java 应用的类都是由它来完成加载的。可以通过 ClassLoader.getSystemClassLoader()来获取它。用户自定义类加载器(custom ClassLoader),通过继承 java.lang.ClassLoader类的方式实现。

    类加载器的层次关系(双亲委派模型)

    这种层次关系称为类加载器的双亲委派模型。我们把每一层上面的类加载器叫做当前层类加载器的父加载器,当然,它们之间的父子关系并不是通过继承关系来实现的,而是使用组合关系来复用父加载器中的代码。

    “双亲委派模型” 的工作流程是:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把请求委托给父加载器去完成,依次向上,因此,所有的类加载请求最终都应该被传递到顶层的启动类加载器中,只有当父加载器在它的搜索范围中没有找到所需的类时,即无法完成该加载,子加载器才会尝试自己去加载该类。

    双亲委派机制的好处

    保证java核心库的安全性(例如:如果用户自己写了一个java.lang.String类就会因为双亲委派机制不能被加载,不会破坏原生的String类的加载)

    ClassLoader 中与加载类相关的方法

    为什么要破坏双亲委托

    父类无法加载需要的文件需要委托子类加载器去加载

    比如jdbc:

    JDBC的Driver接口定义在JDK中,其实现由各个数据库的服务商来提供,比如MySQL驱动包。DriverManager 类中要加载各个实现了Driver接口的类,然后进行管理,但是DriverManager位于 JAVA_HOME中jre/lib/rt.jar 包,由BootStrap类加载器加载,而其Driver接口的实现类是位于服务商提供的 Jar 包,根据类加载机制,当被装载的类引用了另外一个类的时候,虚拟机就会使用装载第一个类的类装载器装载被引用的类。也就是说BootStrap类加载器还要去加载jar包中的Driver接口的实现类。我们知道,BootStrap类加载器默认只负责加载 JAVA_HOME中jre/lib/rt.jar 里所有的class,所以需要由子类加载器去加载Driver实现,这就破坏了双亲委派模型。

    类的隔离加载

    比如每个Tomcat的webappClassLoader加载自己的目录下的class文件,不会传递给父类加载器。

    事实上,tomcat之所以造了一堆自己的classloader,大致是出于下面三类目的:

    对于各个 webapp中的 class和 lib,需要相互隔离,不能出现一个应用中加载的类库会影响另一个应用的情况,而对于许多应用,需要有共享的lib以便不浪费资源。与 jvm一样的安全性问题。使用单独的 classloader去装载 tomcat自身的类库,以免其他恶意或无意的破坏;热部署。相信大家一定为 tomcat修改文件不用重启就自动重新装载类库而惊叹吧。

    具体参见:阿里面试题:JDBC、Tomcat为什么要破坏双亲委派模型 https://www.javazhiyin.com/44347.html

    类加载过程详解

    JVM将类加载过程分为三个步骤:装载(Load),链接(Link)和初始化(Initialize)

    装载:   查找并加载类的二进制数据; 2)链接:   验证:确保被加载类信息符合JVM规范、没有安全方面的问题。   准备:为类的静态变量分配内存,并将其初始化为默认值。   解析:把虚拟机常量池中的符号引用转换为直接引用。 3)初始化:   为类的静态变量赋予正确的初始值。 ps:解析部分需要说明一下,Java 中,虚拟机会为每个加载的类维护一个常量池【不同于字符串常量池,这个常量池只是该类的字面值(例如类名、方法名)和符号引用的有序集合。 而字符串常量池,是整个JVM共享的】这些符号(如int a = 5;中的a)就是符号引用,而解析过程就是把它转换成指向堆中的对象地址的相对地址。

    类的初始化步骤: 1)如果这个类还没有被加载和链接,那先进行加载和链接 2)假如这个类存在直接父类,并且这个类还没有被初始化(注意:在一个类加载器中,类只能初始化一次),那就初始化直接的父类(不适用于接口) 3)如果类中存在static标识的块,那就依次执行这些初始化语句。

    参考资料

    类加载机制 深入理解和探究Java类加载机制

    Processed: 0.011, SQL: 8