篮之新喜 发表于 2025-4-13 10:18:38

JVM初探——走进类加载机制|三大特性 | 打破双亲委派SPI机制详解

目次
JVM是什么?
类加载机制
Class装载到JVM的过程
装载(load)——查找和导入class文件
链接(link)——验证、准备、解析
验证(verify)——包管加载类的正确性
准备(Prepare)——为类的静态变量分配内存,而且将其初始化为默认值
解析(resolve)——将类中的符号引用转化为直接引用
初始化——对类静态变量,静态代码块执行初始化操作
类加载器
类加载器的分类
加载原则
为啥类加载器要分层?
JVM类加载器的三种特性
全盘负责
父类委托(双亲委派)
缓存机制
破坏双亲委派机制
SPI(Service Provider Interface)
OSGi
SPI机制详解
SPI用途

JVM是什么?

Java Virtual Machine(Java虚拟机)
https://i-blog.csdnimg.cn/direct/29d99b59d67d4eb084a02e58a33aee2d.pnghttps://i-blog.csdnimg.cn/direct/fcafc9c71d88415ea6076ea6853fb847.png
JVM相干的内容大致分为下列三种


[*] 源码到类文件
[*] 类文件到JVM
[*] JVM内部各种行为(内部结构、执行方式、垃圾回收、本地调用等)
这期博客举行总结类文件到JVM的这个过程,换句话将类文件到虚拟机,即类加载机制。
类加载机制

Class装载到JVM的过程

我的明白是:虚拟机将Class文件加载到内存,并对数据举行校验,转化解析和初始化,形成虚拟机可以直接使用的Java类型,即java.lang.class
   分析Java中单例6种实现方式时候,专门还对类文件到虚拟机的过程举行分析了,点击查看对应博客~
https://i-blog.csdnimg.cn/direct/237aceaa986f4d8ca405cf23684af0c9.png
类加载机制是类的字节码文件的数据读入内存中,同时天生数据的访问入口的特殊机制,即类加载的最终产物是数据访问入口。
装载(load)——查找和导入class文件

Class对象封装了类在方法区内的数据结构,而且向Java步伐员提供了访问方法区内的数据结构的接口。在Java堆中天生一个代表这个类的java.lang.Class对象,作为对方法区中这些数据的访问入口


[*] 通过一个类的全限定名获取界说此类的二进制字节流
[*] 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
[*] 在Java堆中天生一个代表这个类的java.lang.Class对象,作为方法区中数据的访问入口
那么此时,JVM中内容:
https://i-blog.csdnimg.cn/direct/17c94881eaaa4a568fb734f31e0a0306.pnghttps://i-blog.csdnimg.cn/direct/2971edd99d5d402da86e93279b3217ce.png
链接(link)——验证、准备、解析

验证(verify)——包管加载类的正确性



[*] 文件格式验证
[*] 元数据验证
[*] 字节码验证
[*] 符号引用验证
准备(Prepare)——为类的静态变量分配内存,而且将其初始化为默认值

https://i-blog.csdnimg.cn/direct/acf75b9f04f442d3a74d9bb48ec9a0a0.png
解析(resolve)——将类中的符号引用转化为直接引用



[*] 符号引用:一组符号来描述目的,可以是任何字面量
[*] 直接引用:直接指向目的指针、相对偏移量大概一个简介定位到目的的句柄
解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。
解析动作重要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用限定符7类符号引用举行。
初始化——对类静态变量,静态代码块执行初始化操作


类加载器

在load装载阶段,将通过类的全限定名获取其界说的二进制字节流,必要借助类装载器完成。就是用来装在Class文件的
类加载器的分类



[*] Bootstrap ClassLoader 负责加载$JAVA_HOME中 jre/lib/rt.jar 里全部的class或 Xbootclassoath选项指定的jar包。由C++实现,不是ClassLoader子类。
[*] Extension ClassLoader 负责加载java平台中扩展功能的一些jar包,包括$JAVA_HOME中 jre/lib/*.jar 或 -Djava.ext.dirs指定目次下的jar包。
[*] App ClassLoader 负责加载classpath中指定的jar包和Djava.class.path 所指定目次下的类和 jar包。
[*] Custom ClassLoader 通过java.lang.ClassLoader的子类自界说加载class,属于应用步伐根据自 身必要自界说的ClassLoader,如tomcat、jboss都会根据j2ee规范自行实现ClassLoader。
https://i-blog.csdnimg.cn/direct/23cc08ff93a84a29897fad7dd02348d8.png
加载原则



[*] 查抄某类是否已经加载:自底向上,从Custom ClassLoader到BootStrap ClassLoader逐层查抄,只要存在ClassLoader加载过,那么就以为该类已经加载。且包管这个类被全部的加载器只加载一次。
[*] 加载顺序:自顶向下。由上层逐层尝试加载目的类
为啥类加载器要分层?

只有一个类加载器,就是如今的“Bootstrap”类加载器。也就是根类加载器。 但是如许会出现一个问题。
如果用户调用他编写的java.lang.String类。理论上该类可以访问和改变java.lang包下其他类的默认访问修饰符的属性和方法的本领。也就是说,我们其他的类使用String时也会调用这个类,因为只有一个类加载器,无法判定到底加载哪个。因为Java语言自己并没有制止这种行为,以是会出现问题。
可不可以使用不同级别的类加载器来对我们的信任级别做一个区分呢?
用三种根本的类加载器做为我们的三种不同的信任级别。最可信的级别是java核心API类。然后是安装的拓展类,最后才是在类路径中的类。
以是三种根本的类加载器就诞生了。
JVM类加载器的三种特性

全盘负责

当一个类加载器负责加载某个Class时,该Class所依赖的和引用的其他Class也将由该类加载器负责载入,除非表现使用别的一个类加载器来载入。
父类委托(双亲委派)

“双亲委派”是指子类加载器如果没有加载过该目的类,就先委托父类加载器加载该目的类,只有在父类加载器找不到字节码文件的情况下才从自己的类路径中查找并装载目的类。
https://i-blog.csdnimg.cn/direct/d10c9937465240df962f355bc3109d01.png
我们可以继续java.lang.ClassLoader类,实现自己的类加载器。如果想保持双亲委派模型,就应该重写findClass(name)方法;如果想破坏双亲委派模型,可以重写loadClass(name)方法。
缓存机制

缓存机制将会包管全部加载过的Class都将在内存中缓存,当步伐中必要使用某个Class时,类加载器先从内存的缓存区寻找该Class,只有缓存区不存在,系统才会读取该类对应的二进制数据,并将其转换成Class对象,存入缓存区。这就是为什么修改了Class后,必须重启JVM,步伐的修改才访问效。对于一个类加载器实例来说,相同全名的类只加载一次,即loadClass方法不会被重复调用。
   JDK8使用直接内存举行缓存,以是类变量只会初始化一次。
破坏双亲委派机制



[*] Tomcat
[*] SPI机制
[*] OSGi
双亲委派这个模型并不是强制模型,而且会带来一些些的问题。就比如java.sql.Driver。JDK只能提供一个规范接口,而不能提供实现。提供实现的是实际的数据库提供商。提供商的库总不能放JDK目次里吧。 以是java想到了几种办法可以用来打破我们的双亲委派。
SPI(Service Provider Interface)

JDK提供接口,供应商提供服务。步伐员编码时面向接口编程,然后JDK可以或许自动找到符合实现。
OSGi

代码热摆设,代码热替换。OSGi实现模块化热摆设的关键则是它自界说的类加载器机制的实现。
SPI机制详解

SPI 全称为 (Service Provider Interface) ,是JDK内置的一种服务提供发现机制。SPI是一种动态替换发现的机制, 比如有个接口,想运行时动态的给它添加实现,你只必要添加一个实现。我们常常遇到的就是java.sql.Driver接口,其他不同厂商可以针对同一接口做出不同的实现,mysql和postgresql都有不同的实现提供给用户,而Java的SPI机制可以为某个接口寻找服务实现。
https://i-blog.csdnimg.cn/direct/d7b75e9becb44197a9ceb59b91f52edd.png
如上图所示,接口对应的抽象SPI接口;实现方实现SPI接口;调用方依赖SPI接口。
SPI接口的界说在调用方,在概念上更依赖调用方;组织上位于调用方所在的包中,实现位于独立的包中。
当服务的提供者提供了一种接口的实现之后,必要在classpath下的META-INF/services/目次里创建一个以服务接口定名的文件,这个文件里的内容就是这个接口的具体的实现类。当其他的步伐必要这个服务的时候,就可以通过查找这个jar包(一般都是以jar包做依赖)的META-INF/services/中的配置文件,配置文件中有接口的具体实现类名,可以根据这个类名举行加载实例化,就可以使用该服务了。JDK中查找服务实现的工具类是:java.util.ServiceLoader。
SPI用途

数据库DriverManager、Spring、ConfigurableBeanFactory等都用到了SPI机制,这里以数据库DriverManager为例,看一下实在现的内幕。DriverManager是jdbc里管理和注册不同数据库driver的工具类。针对一个数据库,可能会存在着不同的数据库驱动实现。我们在使用特定的驱动实现时,不渴望修改现有的代码,而渴望通过一个简单的配置就可以达到效果。 在使用mysql驱动的时候,会有一个疑问,DriverManager是怎么得到某确定驱动类的?我们在运用Class.forName("com.mysql.jdbc.Driver")加载mysql驱动后,就会执行此中的静态代码把driver注册到DriverManager中,以便后续的使用。
package com.mysql.jdbc;

import java.sql.DriverManager;
import java.sql.SQLException;

public class Driver extends NonRegisteringDriver implements java.sql.Driver {
    public Driver() throws SQLException {
    }

    static {
      try {
            DriverManager.registerDriver(new Driver());
      } catch (SQLException var1) {
            throw new RuntimeException("Can't register driver!");
      }
    }
} 驱动的类的静态代码块中,调用DriverManager的注册驱动方法new一个自己当参数传给驱动管理器。Mysql DriverManager实现
    /**
   * Load the initial JDBC drivers by checking the System property
   * jdbc.properties and then use the {@code ServiceLoader} mechanism
   */
    static {
      loadInitialDrivers();
      println("JDBC DriverManager initialized");
    } 可以看到其内部的静态代码块中有一个loadInitialDrivers方法,loadInitialDrivers用法用到了上文提到的spi工具类ServiceLoader:
private static void loadInitialDrivers() {
      String drivers;
      try {
            drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
                public String run() {
                  return System.getProperty("jdbc.drivers");
                }
            });
      } catch (Exception ex) {
            drivers = null;
      }
      // If the driver is packaged as a Service Provider, load it.
      // Get all the drivers through the classloader
      // exposed as a java.sql.Driver.class service.
      // ServiceLoader.load() replaces the sun.misc.Providers()

      AccessController.doPrivileged(new PrivilegedAction<Void>() {
            public Void run() {

                ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
                Iterator<Driver> driversIterator = loadedDrivers.iterator();

                /* Load these drivers, so that they can be instantiated.
               * It may be the case that the driver class may not be there
               * i.e. there may be a packaged driver with the service class
               * as implementation of java.sql.Driver but the actual class
               * may be missing. In that case a java.util.ServiceConfigurationError
               * will be thrown at runtime by the VM trying to locate
               * and load the service.
               *
               * Adding a try catch block to catch those runtime errors
               * if driver not available in classpath but it's
               * packaged as service and that service is there in classpath.
               */
                try{
                  while(driversIterator.hasNext()) {
                        driversIterator.next();
                  }
                } catch(Throwable t) {
                // Do nothing
                }
                return null;
            }
      });
    println("DriverManager.initialize: jdbc.drivers = " + drivers);

      if (drivers == null || drivers.equals("")) {
            return;
      }
      String[] driversList = drivers.split(":");
      println("number of Drivers:" + driversList.length);
      for (String aDriver : driversList) {
            try {
                println("DriverManager.Initialize: loading " + aDriver);
                Class.forName(aDriver, true,
                        ClassLoader.getSystemClassLoader());
            } catch (Exception ex) {
                println("DriverManager.Initialize: load failed: " + ex);
            }
      } 先查找jdbc.drivers属性的值,然后通过SPI机制查找驱动
public final class ServiceLoader<S>
    implements Iterable<S>
{

    private static final String PREFIX = "META-INF/services/";
private boolean hasNextService() {
            if (nextName != null) {
                return true;
            }
            if (configs == null) {
                try {
                  String fullName = PREFIX + service.getName();
                  if (loader == null)
                        configs = ClassLoader.getSystemResources(fullName);
                  else
                        configs = loader.getResources(fullName);
                } catch (IOException x) {
                  fail(service, "Error locating configuration files", x);
                }
            }
            while ((pending == null) || !pending.hasNext()) {
                if (!configs.hasMoreElements()) {
                  return false;
                }
                pending = parse(service, configs.nextElement());
            }
            nextName = pending.next();
            return true;
      }
可以看到加载META-INF/services/ 文件夹下类名为文件名(这里相当于Driver.class.getName())的资源,然后将其加载到虚拟机。注释有这么一句“Load these drivers, so that they can be instantiated.” 意思是加载SPI扫描到的驱动来触发他们的初始化。即触发他们的static代码块。
/**
   * Registers the given driver with the {@code DriverManager}.
   * A newly-loaded driver class should call
   * the method {@code registerDriver} to make itself
   * known to the {@code DriverManager}. If the driver is currently
   * registered, no action is taken.
   *
   * @param driver the new JDBC Driver that is to be registered with the
   *               {@code DriverManager}
   * @param da   the {@code DriverAction} implementation to be used when
   *               {@code DriverManager#deregisterDriver} is called
   * @exception SQLException if a database access error occurs
   * @exception NullPointerException if {@code driver} is null
   * @since 1.8
   */
    public static synchronized void registerDriver(java.sql.Driver driver,
            DriverAction da)
      throws SQLException {

      /* Register the driver if it has not already been added to our list */
      if(driver != null) {
            registeredDrivers.addIfAbsent(new DriverInfo(driver, da));
      } else {
            // This is for compatibility with the original DriverManager
            throw new NullPointerException();
      }

      println("registerDriver: " + driver);

    } 将自己注册到驱动管理器的驱动列表中
public class DriverManager {
    // List of registered JDBC drivers
    private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<>();
} 当获取毗连的时候调用驱动管理器的毗连方法从列表中获取。
private static Connection getConnection(
      String url, java.util.Properties info, Class<?> caller) throws SQLException {
      /*
         * When callerCl is null, we should check the application's
         * (which is invoking this class indirectly)
         * classloader, so that the JDBC driver class outside rt.jar
         * can be loaded from here.
         */
      ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;
      synchronized(DriverManager.class) {
            // synchronize loading of the correct classloader.
            if (callerCL == null) {
                callerCL = Thread.currentThread().getContextClassLoader();
            }
      }

      if(url == null) {
            throw new SQLException("The url cannot be null", "08001");
      }

      println("DriverManager.getConnection(\"" + url + "\")");

      // Walk through the loaded registeredDrivers attempting to make a connection.
      // Remember the first exception that gets raised so we can reraise it.
      SQLException reason = null;

      for(DriverInfo aDriver : registeredDrivers) {
            // If the caller does not have permission to load the driver then
            // skip it.
            if(isDriverAllowed(aDriver.driver, callerCL)) {
                try {
                  println("    trying " + aDriver.driver.getClass().getName());
                  Connection con = aDriver.driver.connect(url, info);
                  if (con != null) {
                        // Success!
                        println("getConnection returning " + aDriver.driver.getClass().getName());
                        return (con);
                  }
                } catch (SQLException ex) {
                  if (reason == null) {
                        reason = ex;
                  }
                }

            } else {
                println("    skipping: " + aDriver.getClass().getName());
            }

      }

      // if we got here nobody could connect.
      if (reason != null)    {
            println("getConnection failed: " + reason);
            throw reason;
      }

      println("getConnection: no suitable driver found for "+ url);
      throw new SQLException("No suitable driver found for "+ url, "08001");
    } private static boolean isDriverAllowed(Driver driver, ClassLoader classLoader) {
      boolean result = false;
      if(driver != null) {
            Class<?> aClass = null;
            try {
                aClass =Class.forName(driver.getClass().getName(), true, classLoader);
            } catch (Exception ex) {
                result = false;
            }

             result = ( aClass == driver.getClass() ) ? true : false;
      }

      return result;
    }



免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: JVM初探——走进类加载机制|三大特性 | 打破双亲委派SPI机制详解