每日一点JVM---类加载相关

写在前面

今日份复习的是类加载相关的知识,会分为什么是类加载何时类加载Java的类加载过程常见类加载器及其之间的关系类加载器的双亲委派机制为什么需要双亲委派机制来看。

什么是类加载?

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

何时类加载?

关于类加载的时机,《Java虚拟机规范》只是严格规定了有且只有六种情况必须立刻对类进行“初始化”。
简单来说,就是:

  1. 遇到new、getstatic、putstatic或invokestatic这四条字节码指令时,如果类型没有初始化,就需要初始化。能够生成这四条指令的场景有:使用new实例化对象、读取或设置一个类型的静态字段和调用一个类型的静态方法。
  2. 使用java.lang.reflect包的方法对类型进行反射调用的时候。
  3. 当初始化类的时候,发现父类还没有被初始化,则先触发父类的初始化。
  4. 当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的那个类),虚拟机会先初始化这个主类。
  5. 使用JDK 7的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果为REF_getStatic、REF_putStatic、REF_invokeStatic和REF_newInvokeSpecial四种类型的方法句柄,并且这个方法句柄对应的类没有进行初始化,则需要先触发其初始化。
  6. 当一个接口中顶一个了JDK 8新加入的默认方法(被default关键字修饰的接口方法)时,如果有这个接口的实现类发生了初始化,那该接口要在其之前被初始化

Java的类加载过程

在Java中,一个类从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期将会经历加载(Loading)验证(Verification)准备(Preparation)解析(Resolution)初始化(Initialization)、**使用(Using)卸载(Unloading)**七个阶段。其中验证、准备和解析三个部分统称为连接(Linking)。而Java的类加载过程包括加载、连接和初始化三部分。
在这里插入图片描述

加载

加载阶段,包括一下三个步骤:

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

验证

根据Java虚拟机规范,来校验你加载进来的“.class”文件中的内容是否符合规范

准备

准备阶段是正式为类中定义的变量(即静态变量,被static修饰的变量分配内存并设置类变量初始值的阶段。此处的初始值通常是数据类型的零值。只有被Final修饰的static变量才会在准备节点被初始化为属性指定的值。

解析

解析阶段是Java虚拟机将常量池内的符号引用替换为直接引用的过程。

初始化

类的初始化阶段是类加载过程的最后一个步骤,在初始化阶段,Java虚拟机才真正开始执行类中编写的Java程序代码,将主导权移交给应用程序。换句话说,初始化的目的就是为类变量赋值(对static静态代码块的执行)。

常见类加载器及其之间的关系

Java中通过类加载器来实现类加载阶段中的“通过一个类的全限定名来获取描述该类的二进制字节流”,以便让应用程序自己决定如何去获取所需的类
对于任意一个类,都必须由加载它的类加载器这个类本身一起共同确立在Java虚拟机中的唯一性,每一个类加载器,都拥有一个独立的类加载空间。换句话说,比较两个类是否“相等”,只有在他们是同一类加载器加载的前提下才有意义。
类加载器一般分为两种,一种是启动类加载器(Bootstrap ClassLoader),这个类加载器由C++实现,是虚拟机自身的一部分;另一种就是其他所有的类加载器,他们都由Java实现独立于虚拟机外部,并且全部继承自抽象类java.lang.ClassLoader
常见的类加载器有启动类加载器(Bootstrap ClassLoader)扩展类加载器(Extension ClassLoader)应用程序类加载器(Application ClassLoader)

  • 启动类加载器负责加载我们在机器上安装的Java目录下的核心类,位于JAVA_HOME/lib目录下或者被-Xbootclasspath参数所指定的路径中存放的并且是Java虚拟机能够识别的。
  • 扩展类加载器负责加载JAVA_HOME/lib/ext目录中的类,或者被java.ext.dirs系统变量所指定的路径中所有的类库。以上两个类加载器加载某些类以支撑系统的运行。
  • 应用程序类加载器负责加载“ClassPath”环境变量所指定的路径中的类,可理解为加载你写好的那些类
  • 自定义类加载器,根据需求加载你的类

类加载器的双亲委派机制

在这里插入图片描述
上图中展示的各种类加载器之间的层次关系被称为类加载器的“双亲委派机制”。双亲委派机制要求除了顶层的启动类加载器外,其余的类加载器都应有自己的父类加载器。不过类加载器的父子关系一般不是以继承来实现的,而是通常使用组合关系来复用父加载器的代码。
在双亲委派机制中,如果一个类加载器收到了类加载的请求,他首先不会自己去尝试着加载,而是委派给父类加载器去加载,每一层都这样,最终传送到最顶层的启动类加载器,只有当父加载器反馈自己无法完成这个加载请求在他的搜索范围内没有找到所需的类)时,子加载器才会尝试自己去完成加载。

为什么需要双亲委派机制?

使用双亲委派机制来组织类加载器之间的关系,一个好处就是Java中的类随着他的类加载器一起具备的一种带有优先级的层次关系,例如加载java.lang.Object类,他存放在rt.jar中,不管谁去加载他,最终都是委派哥处于模型最顶端的启动类加载器进行加载,保证了Object类在程序的各种类加载器环境中的唯一

Tomcat这种Web容器的类加载器如何设计?

首先我们看看Tomcat的应用环境要求它能够做到什么,解决什么问题。

  1. Tomcat作为一个Web容器,可能需要部署两个应用程序,不同的应用程序可能会依赖同一个第三方类库的不同版本,不能要求同一份类库在同一个服务器只有一份,因此要保证每个应用程序的类库都是独立和相互隔离的
  2. 部署在同一个Web容器中相同的类库相同的版本可以共享。
  3. Web容器自身依赖的类库应该与应用程序的类库隔离开来。
  4. Web容器要支持jsp的修改,也就是热部署
    如果要遵循双亲委派机制,那么第1点和第3点是不可能实现的,参照上文的依靠全限定类名去唯一确认一个类,是无法加载两个相同类库的不同版本的。第4点的热部署想要实现,使用双亲委派机制也是不可以的,因为类名不变,类加载器还是取方法区存在的类,而不会去重新加载
    Toncat类加载器
    Tomcat自定义了CommonCatalniaShared等类加载器,是用来加载Tomcat自己的一些核心基础类库的。
    Tomcat为每个部署在里面的Web应用都有一个对应的WebApp类加载器,负责加载我们部署的应用程序的依赖类。
    至于JSP类加载器,则是给每个JSP都准备了一个JSP类加载器,Tomcat打破了双亲委派机制,每个WebApp负责加载自己对应的那个应用程序的class文件,不会传导到上层类加载器去加载。
    在一个JSP文件修改了,就直接卸载这个类加载器重新创建类加载器,以达到重新加载JSP文件的目的。

猜你喜欢

转载自blog.csdn.net/fucccck_ly/article/details/108108689