首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >JVM类加载机制和双亲委派模型

JVM类加载机制和双亲委派模型

作者头像
用户3467126
发布2019-08-12 19:37:38
发布2019-08-12 19:37:38
7270
举报
文章被收录于专栏:爱编码爱编码

什么是类加载机制?

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

类加载的时机

类的生命周期是从类被加载到虚拟机的内存中,到卸载出内存为止。

类的生命周期:

加载 loading 验证 verification 准备 preparation 解析 resolution 初始化 initialization 使用 using 卸载 unloading

Java语言里,类型的加载和连接过程(连接过程包括验证、准备、解析)是在程序运行期间完成的。

加载(装载)、验证、准备、初始化和卸载这五个阶段顺序是固定的,类的加载过程必须按照这种顺序开始。解析阶段不一定,它在某些情况下可以在初始化之后再开始,这是为了运行时动态绑定特性(JIT例如接口只在调用的时候才知道具体实现的是哪个子类)。值得注意的是:这些阶段通常都是互相交叉的混合式进行的,通常会在一个阶段执行的过程中调用或激活另外一个阶段。

类的加载过程

加载阶段

主要完成以下3件事情:1.通过“类全名”来获取定义此类的二进制字节流 2.将字节流所代表的静态存储结构转换为方法区的运行时数据结构 3.在java堆中生成一个代表这个类的java.lang.Class对象,作为方法区这些数据的访问入口

验证阶段

这个阶段目的在于确保Class文件的字节流中包含信息符合当前虚拟机要求,不会危害虚拟机自身安全。主要包括四种验证:1.文件格式验证:基于字节流验证,验证字节流是否符合Class文件格式的规范,并且能被当前虚拟机处理。2.元数据验证:基于方法区的存储结构验证,对字节码描述信息进行语义验证。3.字节码验证:基于方法区的存储结构验证,进行数据流和控制流的验证。4.符号引用验证:基于方法区的存储结构验证,发生在解析中,是否可以将符号引用成功解析为直接引用。

准备阶段

仅仅为类变量(即static修饰的字段变量)分配内存并且设置该类变量的初始值即零值,这里不包含用final修饰的static,因为final在编译的时候就会分配了,同时这里也不会为实例变量分配初始化。类变量会分配在方法区中,而实例变量是会随着对象一起分配到Java堆中。

解析阶段

解析主要就是将常量池中的符号引用替换为直接引用的过程。符号引用就是一组符号来描述目标,可以是任何字面量,而直接引用就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄。有类或接口的解析,字段解析,类方法解析,接口方法解析。这里要注意如果有一个同名字段同时出现在一个类的接口和父类中,那么编译器一般都会拒绝编译。

初始化阶段

初始化阶段依旧是初始化类变量和其他资源,这里将执行用户的static字段和静态语句块的赋值操作。这个过程就是执行类构造器方法的过程。

上述过程可以使用下面的脑图来概括:

类加载器的层次结构

从Java虚拟机的角度来说,只存在两种不同的类加载器:一种是启动类加载器(Bootstrap ClassLoader),这个类加载器使用C++语言实现(HotSpot虚拟机中),是虚拟机自身的一部分;另一种就是所有其他的类加载器,这些类加载器都有Java语言实现,独立于虚拟机外部,并且全部继承自java.lang.ClassLoader。

从开发者的角度,类加载器可以细分为:

启动(Bootstrap)类加载器:负责将 Java_Home/lib下面的类库加载到内存中(比如rt.jar)。由于引导类加载器涉及到虚拟机本地实现细节,开发者无法直接获取到启动类加载器的引用,所以不允许直接通过引用进行操作。

标准扩展(Extension)类加载器:是由 Sun 的 ExtClassLoader(sun.misc.Launcher$ExtClassLoader)实现的。它负责将Java_Home /lib/ext或者由系统变量 java.ext.dir指定位置中的类库加载到内存中。开发者可以直接使用标准扩展类加载器。

应用程序(Application)类加载器:是由 Sun 的 AppClassLoader(sun.misc.Launcher$AppClassLoader)实现的。它负责将系统类路径(CLASSPATH)中指定的类库加载到内存中。开发者可以直接使用系统类加载器。由于这个类加载器是ClassLoader中的getSystemClassLoader()方法的返回值,因此一般称为系统(System)加载器。

除此之外,还有自定义的类加载器,它们之间的层次关系被称为类加载器的双亲委派模型。该模型要求除了顶层的启动类加载器外,其余的类加载器都应该有自己的父类加载器,而这种父子关系一般通过组合(Composition)关系来实现,而不是通过继承(Inheritance)。

代码语言:javascript
复制
public class Test {  public static void main(String[] args) {    System.out.println(Test.class.getClassLoader());    System.out.println(Test.class.getClassLoader().getParent());    System.out.println(Test.class.getClassLoader().getParent().getParent());  }}

执行结果:

代码语言:javascript
复制
sun.misc.Launcher$AppClassLoader@18b4aac2sun.misc.Launcher$ExtClassLoader@4554617cnull

注:AppClassLoader 和 ExtClassLoader 由 Java 编写并且都是 java.lang.ClassLoader 的子类,而 BootstarapClassLoader 并非由 Java 实现而是由C++ 实现,所以打印结果为null。

自定义类加载器

代码语言:javascript
复制
public class Test {    public void say (){      System.out.println("Hello");    }}

自定义一个类加载器,继承ClassLoader。

代码语言:javascript
复制
package com.coach.jvm;import java.io.ByteArrayOutputStream;import java.io.FileInputStream;import java.io.IOException;import java.lang.reflect.Method;
public class CustomClassLoader extends ClassLoader {  private final String classesDir;
public CustomClassLoader(String classesDir) {    this.classesDir = classesDir;}@Overrideprotected Class<?> findClass(String name) throws  ClassNotFoundException {    String fileName = name;    if (fileName.indexOf('.') != -1) {      fileName = fileName.replaceAll("\\.", "\\\\");  }  fileName = fileName + ".class";try {  try (FileInputStream in = new FileInputStream(classesDir + fileName)) {      try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {          byte[] buffer = new byte[1024];          int len = 0;          while ((len = in.read(buffer)) != -1) {              out.write(buffer,0,len);          }          byte[] data = out.toByteArray();          return defineClass(name, data, 0, data.length);      }    }  } catch (IOException e) {        throw new ClassNotFoundException(name);  }}
public static void main(String[] args) throws ReflectiveOperationException{//1. 将Test.java 编译为Test.class 后复制到 E:\classes 下,当然也可以选择其他目录作为加载目录。      //2. 加载        ClassLoader classLoader = new CustomClassLoader("E:\\classes\\");        Class<?> clazz = classLoader.loadClass("com.coach.jvm.Test");//如果你的Test在一个包内,需要加上包名,如x.y.z.Test      //3. 通过反射调用say()方法      Object instance = clazz.newInstance();      Method method = clazz.getMethod("say", null);        method.invoke(instance);//Hello    }}

上面的代码中,我们使用了自定义的类加载器来加载Test类,并使用反射机制成功调用了Test类的方法。

双亲委派模型过程

某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,依次递归,如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务时,才自己去加载。

使用双亲委派模型的好处在于Java类随着它的类加载器一起具备了一种带有优先级的层次关系。例如类java.lang.Object,它存在在rt.jar中,无论哪一个类加载器要加载这个类,最终都是委派给处于模型最顶端的Bootstrap ClassLoader进行加载,因此Object类在程序的各种类加载器环境中都是同一个类。相反,如果没有双亲委派模型而是由各个类加载器自行加载的话,如果用户编写了一个java.lang.Object的同名类并放在ClassPath中,那系统中将会出现多个不同的Object类,程序将混乱。因此,如果开发者尝试编写一个与rt.jar类库中重名的Java类,可以正常编译,但是永远无法被加载运行。

双亲委派模型的系统实现

在java.lang.ClassLoader的loadClass()方法中,先检查是否已经被加载过,若没有加载则调用父类加载器的loadClass()方法,若父加载器为空则默认使用启动类加载器作为父加载器。如果父加载失败,则抛出ClassNotFoundException异常后,再调用自己的findClass()方法进行加载。

代码语言:javascript
复制
 protected Class<?> loadClass(String name, boolean resolve)        throws ClassNotFoundException    {        synchronized (getClassLoadingLock(name)) {            // First, check if the class has already been loaded            // 首先,检查该Class是否已经被加载,如果已加载直接返回。            Class c = findLoadedClass(name);            // 没有被加载            if (c == null) {                long t0 = System.nanoTime();                try {                    //是否存在上层加载器,如果存在交由上层加载器加载                    if (parent != null) {                        c = parent.loadClass(name, false);                    } else {//如果不存在继续向上委派给BootstarapClassLoader加载                        c = findBootstrapClassOrNull(name);                    }                } catch (ClassNotFoundException e) {                    // ClassNotFoundException thrown if class not found                    // from the non-null parent class loader                }                   //所有上层加载器都无法加载,由当前加载器进行加载                if (c == null) {                    // If still not found, then invoke findClass in order                    // to find the class.                    long t1 = System.nanoTime();                    c = findClass(name);
                    // this is the defining class loader; record the stats                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);                    sun.misc.PerfCounter.getFindClasses().increment();                }            }            if (resolve) {                resolveClass(c);            }            return c;        }    }

注意,双亲委派模型是Java设计者推荐给开发者的类加载器的实现方式,并不是强制规定的。

参考文章

https://www.jianshu.com/p/5f3278916b38 https://blog.csdn.net/xu768840497/article/details/79175335

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-08-09,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 爱编码 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 什么是类加载机制?
  • 类加载的时机
  • 类的生命周期:
  • 类的加载过程
    • 加载阶段
    • 验证阶段
    • 准备阶段
    • 解析阶段
    • 初始化阶段
  • 自定义类加载器
  • 双亲委派模型过程
  • 双亲委派模型的系统实现
  • 参考文章
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档