• JVM知识(二):类加载器原理


        我们知道我们编写的java代码,会经过编译器编译成字节码(class文件),再把字节码文件装载到JVM中,最后映射到各个内存区域中,我们的程序就可以在内存中运行了。那么问题来了,这些字节码文件是怎么装载到JVM中去的呢。中间经过了哪些步骤?我们常说的双亲派模式又是怎么回事?本文就来说说这些问题。

    类加载器的流程

    1、加载

       加载是类加载的第一步。它首先通过class文件的路径读取到二进制流,并解析二进制流将里面的元数据(类型、常量等)载入到方法区(方法区上文说了就是存放这些元数据的),在java堆中生成对应的java.lang.Class对象(java堆存放实例对象)。

    2、连接

      连接过程分为3步:验证、准备、解析

      2.1、验证

          验证的主要目的就是判断class文件的合法性,比如class文件一定是以0xCAFEBABE开头的。另外对版本号也会做校验,例如使用jdk1.8编译后的class文件要在jdk1.6虚拟机上运行,因为版本问题就会验证不通过。除此之外还会对元数据、字节码进行验证等等。

      2.2、准备

        准备过程就是分配内存,给类的一些字段设置初始值,例如:

        public static int a = 1;

        这段这段代码在准备阶段a的值就会被初始化为0,只有在后面类初始化阶段才会被设置为1.

        但是对于static final(常量),在准备阶段就会被设置成指定的值,例如:

        public static final int a=1;

         这段代码在准备阶段a的值就是1了。

      2.3、解析

        解析过程就是将符号引用替换为直接引用,例如某个类基础java.lang.object,原来的符号引用记录的是"java.lang.object"这个符号,凭借这个符号并不能找到java.lang.object这个对象在哪里,而直接引用就是要找到java.lang.object所在的内存地址,建立直接引用关系,这样就方便查询到具体对象了。

    3、初始化

      

    这一步真正去执行类初始化的代码逻辑,包括静态字段赋值的动作,以及执行类定义中的静态初始化块内的逻辑,编译器在编译阶段就会把这部分逻辑整理好,父类型的初始化逻辑优先于当前类型的逻辑。

    初始化过程,主要包括执行类构造方法、static变了赋值语句,static{}语句块,需要注意的是如果一个子类进行初始化,那么它会先初始化其父类,保证父类在子类之前被初始化。所以其实在java中初始化一个类,那么必然会先初始化java.lang.object,因为所有的java类都继承java.lang.object。换句话说,java.lang.object类是java所有类的父类。

    类加载器

       类加载器ClassLoader,它是一个抽象类,ClassLoader的具体实例负责把java字节码读取到JVM当中,ClassLoader还可以定制以满足不同字节流的加载方式。ClassLoader负责整个类装载流程中的“加载”阶段。

      ClassLoader的重要方法:

      public Class<?> loadClass(String name) throws ClassNotFoundException :载入并返回一个类
      protected final Class<?> defineClass(byte[] b, int off, int len):定义一个类,该方法不公开被调用
      protected Class<?> findClass(String name) throws ClassNotFoundException:查找类,loadClass的回调方法
      protected final Class<?> findLoadedClass(String name):查找已经加载的类

      系统中的ClassLoader

      BootStrap Classloader (启动ClassLoader)

      Extension ClassLoader (扩展ClassLoader)

      App ClassLoader(应用 ClassLoader)

      Custom ClassLoader(自定义ClassLoader)

    每个ClassLoader都有另外一个ClassLoader作为父ClassLoader,BootStrap Classloader除外,它没有父Classloader。

    ClassLoader加载机制如下:

    自下向上检查类是否被加载,一般情况下,首先从App ClassLoader中调用findLoadedClass方法查看是否已经加载,如果没有加载,则会交给父类,Extension ClassLoader去查看是否加载,还没加载,则再调用其父类,BootstrapClassLoader查看是否已经加载,如果仍然没有,自顶向下尝试加载类,那么从 Bootstrap ClassLoader到 App ClassLoader依次尝试加载。

    值得注意的是即使两个类来源于相同的class文件,如果使用不同的类加载器加载,加载后的对象是完全不同的,这个不同反应在对象的 equals()、isAssignableFrom()、isInstance()等方法的返回结果,也包括了使用 instanceof 关键字对对象所属关系的判定结果。

    双亲模式: 

    protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { // First, check if the class has already been loaded Class c = findLoadedClass(name); //通过名称来查找已经被加载的类 if (c == null) { try { if (parent != null) { c = parent.loadClass(name, false); } else { 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. c = findClass(name); //如果这个通过名称没有查找到已经加载的类,则通过名称去查找这个类 } } if (resolve) { resolveClass(c); } return c; }

     从代码上可以看出,首先查看这个类是否被加载,如果没有则调用父类的loadClass方法,直到BootstrapClassLoader(没有父类),我们把这个过程叫做双亲模式。

    双亲模式的问题:

    层ClassLoader,无法加载底层ClassLoader的类

    Java框架(rt.jar)如何加载应用的类?

    比如:javax.xml.parsers包中定义了xml解析的类接口
    Service Provider Interface SPI 位于rt.jar 
    即接口在启动ClassLoader中。
    而SPI的实现类,在AppLoader。

    这样就无法用BootstrapClassLoader去加载SPI的实现类。

    解决方法:

    JDK中提供了一个方法:

    Thread. setContextClassLoader()

    用以解决顶层ClassLoader无法访问底层ClassLoader的类的问题;
    基本思想是,在顶层ClassLoader中,传入底层ClassLoader的实例。

    双亲模式是默认的模式,但不是必须这么做;
    Tomcat的WebappClassLoader 就会先加载自己的Class,找不到再委托parent;
    OSGi的ClassLoader形成网状结构,根据需要自由加载Class。
     

  • 相关阅读:
    Dockerfile构建
    ElasticSearch学习之集成客户端
    区块链学习之Hyperledger Fabric开发环境搭建(Go+Docker+Fabric)
    区块链学习之什么是区块链(基础入门)
    python学习之多版本管理及Python安装/卸载遇到的坑(MAC版)
    ElasticSearch学习之基本原理概述
    ElasticSearch学习之基本概念及单机/集群部署
    Kafka学习之Kafka选举机制简述
    Kafka学习之内核原理剖析
    通过idea将java项目发布到harbor仓库
  • 原文地址:https://www.cnblogs.com/MoreThinking/p/9844765.html
Copyright © 2020-2023  润新知