源码解析Java类加载器

参考内容:

我们都知道Java的类加载器结构为下图所示(JDK8及之前,JDK9进行了模块化):

关于三层类加载器、双亲委派机制,本文不再板书,读者可自行百度。

那么在JDK的源码中,三层结构的具体实现是怎么样的呢?

Bootstrap ClassLoader(引导类加载器)

引导类加载器是由C++实现的,并非Java代码实现,所以在Java代码中是无法获取到该类加载器的。

一般大家都称类加载器分为四种(引导类、扩展类、系统类以及用户自定义的类加载器),但其实在JVM虚拟机规范中的支持两种类型的类加载器,分别为引导类加载器(Bootstrap ClassLoader)和自定义类加载器(User-Defined ClassLoader),所以扩展类和系统类也可以统称为自定义类加载器。

Extension ClassLoader(扩展类加载器)和Appclass Loader(系统类加载器)

扩展类加载器和系统类加载器都是由Java语言编写,具体实现为sum.misc.Launcher中的两个内部类ExtClassLoader和AppClassLoader实现,我们进入到LaunchLacher这个类中看看(这个类在oracle jdk是没有公开源码的,需要看具体源码的读者可以下载open jdk中查看具体源码,笔者这里就只是使用IDEA反编译后生成的代码进行解析):

首先是Laucncher的构造方法:

public Launcher() {
  Launcher.ExtClassLoader var1;
  try {
  	// 获取扩展类加载器
   var1 = Launcher.ExtClassLoader.getExtClassLoader();
  } catch (IOException var10) {
   throw new InternalError("Could not create extension class loader", var10);
  }

  try {
  	// 获取系统类加载器
   this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
  } catch (IOException var9) {
   throw new InternalError("Could not create application class loader", var9);
  }
		// 此处是将系统类加载器设置为当前线程的上下文加载器
  Thread.currentThread().setContextClassLoader(this.loader);
  String var2 = System.getProperty("java.security.manager");
  if (var2 != null) {
   SecurityManager var3 = null;
   if (!"".equals(var2) && !"default".equals(var2)) {
    try {
     var3 = (SecurityManager)this.loader.loadClass(var2).newInstance();
    } catch (IllegalAccessException var5) {
    } catch (InstantiationException var6) {
    } catch (ClassNotFoundException var7) {
    } catch (ClassCastException var8) {
    }
   } else {
    var3 = new SecurityManager();
   }

   if (var3 == null) {
    throw new InternalError("Could not create SecurityManager: " + var2);
   }

   System.setSecurityManager(var3);
  }

 }

可以看到在Launcher的构造方法中定义了一个Launcher.ExtClassLoader类型的局部变量var1(这里是反编译后的变量名),并调用Launcher.ExtClassLoader.getExtClassLoader()方法给该局部变量赋值,以及调用Launcher.AppClassLoader.getAppClassLoader(var1);给实例变量(类型为Launcher.AppClassLoader)赋值,需要注意的是,在给系统类加载器赋值时,将扩展类加载器作为参数传入到了方法中。

同时,在构造方法中,将系统类加载器设置为了当前线程的上下文类加载器,关于上下文类加载器,主要用于基础类型调用回用户代码时方法父类加载器区请求子类加载器完成类加载的行为,主要用于JDBC、JNDI等SPI服务提供者接口,这里不详细展开。

上述源码中的**getExtClassLoader()与getAppClassLoader()**方法源码如下:

getExtClassLoader()是Launcher中的内部类ExtClassLoader(扩展类加载器)的一个静态方法:

// 这是ExtClassLoader类内部的定义
private static volatile Launcher.ExtClassLoader instance;// 单例模式实例对象

public static Launcher.ExtClassLoader getExtClassLoader() throws IOException {
// 从这里可以看出,ExtClassLoader是一个由double-checking形成的懒汉式单例对象
   if (instance == null) {
    Class var0 = Launcher.ExtClassLoader.class;
    synchronized(Launcher.ExtClassLoader.class) {
     if (instance == null) {
      instance = createExtClassLoader(); // 创建ExtClassLoader
     }
    }
   }

   return instance;
  }

// createExtClassLoader()方法
private static Launcher.ExtClassLoader createExtClassLoader() throws IOException {
   try {
    return (Launcher.ExtClassLoader)AccessController.doPrivileged(new PrivilegedExceptionAction<Launcher.ExtClassLoader>() {
     public Launcher.ExtClassLoader run() throws IOException {
      File[] var1 = Launcher.ExtClassLoader.getExtDirs();
      int var2 = var1.length;

      for(int var3 = 0; var3 < var2; ++var3) {
       MetaIndex.registerDirectory(var1[var3]);
      }

      return new Launcher.ExtClassLoader(var1); // 调用构造方法
     }
    });
   } catch (PrivilegedActionException var1) {
    throw (IOException)var1.getException();
   }
  }

// ExtClassLoader的构造方法
public ExtClassLoader(File[] var1) throws IOException {
			// 此处第二个参数需要格外注意!!,我们进入父类的构造方法查看该参数是什么
   super(getExtURLs(var1), (ClassLoader)null, Launcher.factory);
   SharedSecrets.getJavaNetAccess().getURLClassPath(this).initLookupCache(this);
  }

// 父类URLClassLoader的构造方法
// 此处的第二个参数是父类构造器的引用,也就解释了为什么在调用获得ExtClassLoader的
public URLClassLoader(URL[] urls, ClassLoader parent, getParent()方法获取父类构造器为null
       URLStreamHandlerFactory factory) {
  super(parent);
  // this is to make the stack depth consistent with 1.1
  SecurityManager security = System.getSecurityManager();
  if (security != null) {
   security.checkCreateClassLoader();
  }
  acc = AccessController.getContext();
  ucp = new URLClassPath(urls, factory, acc);
 }

getAppClassLoader()是Launcher中的内部类AppClassLoader(系统类加载器)的一个静态方法:

public static ClassLoader getAppClassLoader(final ClassLoader var0) throws IOException {
   final String var1 = System.getProperty("java.class.path");
   final File[] var2 = var1 == null ? new File[0] : Launcher.getClassPath(var1);
   return (ClassLoader)AccessController.doPrivileged(new PrivilegedAction<Launcher.AppClassLoader>() {
    public Launcher.AppClassLoader run() {
     URL[] var1x = var1 == null ? new URL[0] : Launcher.pathToURLs(var2);
     return new Launcher.AppClassLoader(var1x, var0); // 与扩展类加载器不同的是,系统类加载器并不是单例模式的
    }
   });
  }
// AppClassLoader的构造方法
AppClassLoader(URL[] var1, ClassLoader var2) {
 // 这里的var2 对应上述getAppClassLoader()方法中的var0,而var0对应的就是Launcher的构造方法中获取到的ExtClassLoader
 // 在ExtClassLoader源码的分析中,我们知道这个var2代表的就是父类构造器,所以此处就是将AppClassLoader的父类设置为ExtClassLoader
   super(var1, var2, Launcher.factory);
   this.ucp.initLookupCache(this);
  }

通过上述两个方法,就可以解释为什么在获取扩展类加载器的父类时为null(即引导加载器),以及不同类加载器看似是继承(Inheritance)关系,实际上是包含关系。在下层加载器中,包含着上层加载器的引用。

ClassLoader抽象类

上述的ExtClassLoader和AppClassLoader均继承于ClassLoader类,ClassLoader抽象类也是类加载机制的基石,接下来我们就进入到该类中,看看它的一些主要方法。

public final classLoader getParent()

返回该类加载器的超类加载器

public Class<?>loadclass(String name) throws ClassNotFoundException

加载名称为name的类,返回结果为java.lang.Class类的实例。如果找不到类,则返ClassNotFoundException异常。该方法中的逻辑就是双亲委派模式的实现。

protected class<?> findClass(string name)throws ClassNotFoundException

  • 查找二进制名称为name的类,返回结果为java.lang.Class类的实例。这是一个受保护的方法,JVM鼓励我们重写此方法,需要自定义加载器遵循双亲委托机制,该方法会在检查完父类加载器之后被loadClass()方法调用。
  • 在JDK1.2之前,在自定义类加载时,总会去继承ClassLoader类并重写loadClass方法,从而实现自定义的类加载类。但是在JDK1.2之后已不再建议用户去覆盖loadClass()方法,而是建议把自定义的类加载逻辑写在findClass()方法中,从前面的分析可知, findClass()方法是在loadClass()方法中被调用的,当loadclass()方法中父加载器加载失败后,则会调用自己的findClass()方法来完成类加载,这样就可以保证自定义的类加载器也符合双亲委托模式。
  • 需要注意的是ClassLoader类中并没有实现findClass()方法的具体代码逻辑,取而代之的是抛出ClassNotFoundException异常,同时应该知道的是findClass方法通常是和defineClass方法一起使用的。一般情况下,在自定义类加载器时,会直接覆盖ClassLoader的findClass()方法并编写加载规则,取得要加载类的字节码后转换成流,然后调用defineClass()方法生成类的Class对象。

protected final Class<?> defineClass(String name, byte[] b, int off,int len)

  • 根据给定的字节数组b转换为Class的实例,off和len参数表示实际Class信息在byte数组中的位置和长度,其中byte数组b是ClassLoader从外部获取的。这是受保护的方法,只有在自定义ClassLoader子类中可以使用。
  • defineClass()方法是用来将byte字节流解析郕VM能够识别的cClass对象(ClassLoader中已实现该方法逻辑),通过这个方法不仅能够通过class文件实例化class对象,也可以通过其他方式实例化class对象,如通过网络接收一个类的字节码,然后转换为byte字节流创建对应的Class对象。
  • defineClass()方法通常与findClass()方法一起使用,一般情况下,在自定义类加载器时,会直接覆盖ClassLoader的findClass()方法并编写加载规则,取得要加载炎的字节码后转换成流,然后调用defineClass()方法生成类的Class对象。

protected final void resoiveClass(class<?> c)

  • 链接指定的一个Java类。使用该方法可以使用类的Class对象创建完成的同时也被解析。前面我们说链接阶段主要是对字节码进行验证,为类变量分配内存并设置初始值同时将字节码文件中的符号引用转换为直接引用。

protected final Class<?> findLoadedClass(String name)

  • 查找名称为name的已经被加载过的类,返回结果为java.lang.Class类的实例。这个方法是final方法,无法被修改。

private final ClassLoader parent;

  • 它也是一个ClassLoader的实例,这个字段所表示的ClassLoader也称为这个ClassLoader的双亲。在类加载的过程中,classLoader可能会将某些请求交予自己的双亲处理。

关于这些方法,不一一展开,主要看一下loadClass()和findClass()。

loadClass()

public Class<?> loadClass(String name) throws ClassNotFoundException {
		// loadClass调用重载含有两个参数的loadClass,其中第二个参数表示在加载时是否解析,默认为false
  return loadClass(name, false);
 }
// 含有两个参数的重载loadClass方法
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException{// resolve:true->加载class的同时进行解析操作
  synchronized (getClassLoadingLock(name)) {// 同步操作,保证只能加载一次
   // 首先在缓存中判断是否已经加载同名的类
   Class<?> c = findLoadedClass(name);
   if (c == null) {
    long t0 = System.nanoTime();
    // 此处就是双亲委派机制的具体实现,其实就是让父类加载器先去加载。
    try {
     // 获取当前类加载器的父类加载器
     if (parent != null) {
      // 如果存在父类加载器,则调用父类加载器的loadClass进行加载(双亲委派)
      c = parent.loadClass(name, false);
     } else {
     		 // parent == null:父类加载器是引导类加载器
      c = findBootstrapClassOrNull(name);
     }
    } catch (ClassNotFoundException e) {
     // ClassNotFoundException thrown if class not found
     // from the non-null parent class loader
    }
				// 当前类加载器的父类加载器未加载此类 or 当前类加载器未加载此类
    if (c == null) {
     // If still not found, then invoke findClass in order
     // to find the class.
     long t1 = System.nanoTime();
     // 调用当前类加载器的findClass()
     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;
  }
 }

findClass()

//在ClassLoader中的findClass()方法
rotected Class<?> findClass(String name) throws ClassNotFoundException {
  throw new ClassNotFoundException(name);
 }

可以看到,在ClassLoader中的findCLass()方法直接抛出异常,所以具体的实现是由子类进行重写实现了;在ClassLoader的子类SecureClassLoader的子类URLClassLoader中对该方法进行了重写。

URLClassLoader中的findCLass()方法

protected Class<?> findClass(final String name)
  throws ClassNotFoundException
 {
  final Class<?> result;
  try {
   result = AccessController.doPrivileged(
    new PrivilegedExceptionAction<Class<?>>() {
     public Class<?> run() throws ClassNotFoundException {
      String path = name.replace('.', '/').concat(".class");// 类名路径字符串格式替换
      Resource res = ucp.getResource(path, false);// 获得class源文件
      if (res != null) {
       try {
       	// 调用defineClass()方法获得要加载的类对应的Class对象,
       	// defineClass()的作用就是根据给定的class源文件返回一个对应的Class对象
        return defineClass(name, res);
       } catch (IOException e) {
        throw new ClassNotFoundException(name, e);
       }
      } else {
       return null;
      }
     }
    }, acc);
  } catch (java.security.PrivilegedActionException pae) {
   throw (ClassNotFoundException) pae.getException();
  }
  if (result == null) {
   throw new ClassNotFoundException(name);
  }
  return result;
 }

最后补充一点关于数组类加载的细节

数组类的Class对象,不是由类加载器去创建的,而是在Java运行期JVM根据需要自动创建的。对于数组类的类加载器来说,是通过Class.getClassLoader()返回的,与数组当中元素类型的类加载器是一样的,如果数组当中的元素类型是基本数据类型,数组类是没有类加载器的。

到此这篇关于Java类加载器的文章就介绍到这了,更多相关Java类加载器内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Java基础之自定义类加载器

    一.类加载器关系 自定义类加载器 创建一个类继承ClassLoader类,同时重写findClass方法,用于判断当前类的class文件是否已被加载 二.基于本地class文件的自定义类加载器 本地class文件路径 自定义类加载器: //创建自定义加载器类继承ClassLoader类 public class MyClassLoader extends ClassLoader{ // 包路径 private String Path; // 构造方法,用于初始化Path属性 public MyC

  • java类加载机制、类加载器、自定义类加载器的案例

    类加载机制 java类从被加载到JVM到卸载出JVM,整个生命周期包括:加载(Loading).验证(Verification).准备(Preparation).解析(Resolution).初始化(Initialization).使用(using).和卸载(Unloading)七个阶段. 其中验证.准备和解析三个部分统称为连接(Linking). 1.加载 加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang.Clas

  • 浅谈Java类的加载,链接及初始化

    一 类生命周期 Loading Linking(Verification.Preparation.Resolution) Initializing 二 类加载器 1 图解 2 代码 package jvm; public class T002_ClassLoadLevel { public static void main(String[] args) { System.out.println(String.class.getClassLoader()); System.out.println(

  • 详解Java的类加载机制及热部署的原理

    一.什么是类加载 类的加载指的是将类的.class文件的二进制数据读入到内存中,将其放在运行数据区的方法去,然后再堆区创建一个java.lang.Class对象,用来封装类在方法区的数据结构.类的加载的最终产品是位于堆区中的Class对象,Class对象封装了类在方法区的数据结构,并且向Java程序员提供了访问方法区的数据结构的接口. 类加载器并不需要等到某个类被"首次主动使用"时再加载它,JVM规范允许类加载器在预料某个类将要被使用时就预先加载它,如果在预先加载的过程中遇到了.cla

  • Java三个类加载器及它们的相互关系

    一.什么是类加载器? 虚拟机设计团队把类加载阶段中的"通过一个类的全限定名来获取描述此类的二进制字节流"这个动作放到Java虚拟机外部去实现,以便让应用程序自己决定如何去获取所需要的类.实现这个动作的代码模块称为"类加载器" 类加载器可以说是Java语言的一项创新,也是Java语言流行的重要原因之一,它最初是为了满足Java Applet的需求而开发出来的.虽然目前Java Applet技术基本上已经"死掉",但类加载器却在类层次划分.OSGi.

  • 源码解析Java类加载器

    参考内容: 深入理解Java虚拟机(JVM高级特性与最佳实践) --周志明老师 尚硅谷深入理解JVM教学视频--宋红康老师 我们都知道Java的类加载器结构为下图所示(JDK8及之前,JDK9进行了模块化): 关于三层类加载器.双亲委派机制,本文不再板书,读者可自行百度. 那么在JDK的源码中,三层结构的具体实现是怎么样的呢? Bootstrap ClassLoader(引导类加载器) 引导类加载器是由C++实现的,并非Java代码实现,所以在Java代码中是无法获取到该类加载器的. 一般大家都

  • 深度源码解析Java 线程池的实现原理

    java 系统的运行归根到底是程序的运行,程序的运行归根到底是代码的执行,代码的执行归根到底是虚拟机的执行,虚拟机的执行其实就是操作系统的线程在执行,并且会占用一定的系统资源,如CPU.内存.磁盘.网络等等.所以,如何高效的使用这些资源就是程序员在平时写代码时候的一个努力的方向.本文要说的线程池就是一种对 CPU 利用的优化手段. 线程池,百度百科是这么解释的: 线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务.线程池线程都是后台线程.每个线程都使用默认的

  • ReentrantLock从源码解析Java多线程同步学习

    目录 前言 管程 管程模型 MESA模型 主要特点 AQS 共享变量 资源访问方式 主要方法 队列 node节点等待状态 ReentrantLock源码分析 实例化ReentrantLock 加锁 A线程加锁成功 B线程尝试加锁 释放锁 总结 前言 如今多线程编程已成为了现代软件开发中的重要部分,而并发编程中的线程同步问题更是一道难以逾越的坎.在Java语言中,synchronized是最基本的同步机制,但它也存在着许多问题,比如可重入性不足.死锁等等.为了解决这些问题,Java提供了更加高级的

  • Java类加载器ClassLoader源码层面分析讲解

    目录 Launcher 源码 AppClassLoader 源码 ExtClassLoader 源码 ClassLoader 源码 总结 最终总结一下 Launcher 源码 sun.misc.Launcher类是java 虚拟机的入口,在启动 java应用 的时候会首先创建Launcher.在初始化Launcher对象的时候会创建一个ExtClassLoader拓展程序加载器 和 AppClassLoader应用程序类加载器(这俩鬼东西好像只是加载类的路径不一样而已),然后由这俩类加载器去加载

  • ClassLoader类加载源码解析

    Java类加载器 1.BootClassLoader: 用于加载Android Framework层class文件. 2.PathClassLoader: 用于Android应用程序类加载器.可以加载指定的dex,jar.zip.zpk中的classes.dex 3.DexClassLoader:加载指定的dex,以及jar.zip.apk中的classes.dex 源码解析 1.ClassLoader中提供loadClass用于加载指定类 //ClassLoader.java public C

  • Java源码解析之ClassLoader

    一.前言 一个完整的Java应用程序,当程序在运行时,即会调用该程序的一个入口函数来调用系统的相关功能,而这些功能都被封装在不同的class文件当中,所以经常要从这个class文件中要调用另外一个class文件中的方法,如果另外一个文件不存在的,则会引发系统异常.而程序在启动的时候,并不会一次性加载程序所要用的所有class文件,而是根据程序的需要,通过Java的类加载机制(ClassLoader)来动态加载某个class文件到内存当中的,从而只有class文件被载入到了内存之后,才能被其它cl

  • Tomcat的类加载机制流程及源码解析

    目录 前言 1.Tomcat 的类加载器结构图: 2.Tomcat 的类加载流程说明: 3.源码解析: 4.为什么tomcat要实现自己的类加载机制: 前言 在前面 Java虚拟机:对象创建过程与类加载机制.双亲委派模型 文章中,我们介绍了 JVM 的类加载机制以及双亲委派模型,双亲委派模型的类加载过程主要分为以下几个步骤: (1)初始化 ClassLoader 时需要指定自己的 parent 是谁 (2)先检查类是否已经被加载过,如果类已经被加载了,直接返回 (3)若没有加载则调用父加载器 p

  • java TreeMap源码解析详解

    java TreeMap源码解析详解 在介绍TreeMap之前,我们来了解一种数据结构:排序二叉树.相信学过数据结构的同学知道,这种结构的数据存储形式在查找的时候效率非常高. 如图所示,这种数据结构是以二叉树为基础的,所有的左孩子的value值都是小于根结点的value值的,所有右孩子的value值都是大于根结点的.这样做的好处在于:如果需要按照键值查找数据元素,只要比较当前结点的value值即可(小于当前结点value值的,往左走,否则往右走),这种方式,每次可以减少一半的操作,所以效率比较高

  • Java类加载器ClassLoader用法解析

    这篇文章主要介绍了Java类加载器ClassLoader用法解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 正文 当程序主动使用某个类时,如果该类还未被加载到内存中,则JVM会通过加载.连接.初始化3个步骤来对该类进行初始化.如果没有意外,JVM将会连续完成3个步骤,所以有时也把这个3个步骤统称为类加载或类初始化. 一.类加载过程 1.加载 加载指的是将类的class文件读入到内存,并为之创建一个java.lang.Class对象,也就是说

  • Java Lambda 表达式源码解析

    Java Lambda 源码分析 问题: Lambda 表达式是什么?JVM 内部究竟是如何实现 Lambda 表达式的?为什么要这样实现? 一.基本概念 1.Lambda 表达式 下面的例子中,() -> System.out.println("1") 就是一个 Lambda 表达式.Java 8 中每一个 Lambda 表达式必须有一个函数式接口与之对应.Lambda 表达式就是函数式接口的一个实现. @Test public void test0() { Runnable

随机推荐