Java源码解析之ClassLoader

一、前言

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

Android平台上虚拟机运行的是Dex字节码,一种对class文件优化的产物,传统Class文件是一个Java源码文件会生成一个.class文件,而Android是把所有Class文件进行合并,优化,然后生成一个最终的class.dex,目的是把不同class文件重复的东西只需保留一份,如果我们的Android应用不进行分dex处理,最后一个应用的apk只会有一个dex文件。

二、java 中的 ClassLoader

BootstrapClassLoader
负责加载 JVM 运行时的核心类,比如 JAVA_HOME/lib/rt.jar 等等

ExtensionClassLoader
负责加载 JVM 的扩展类,比如 JAVA_HOME/lib/ext 下面的 jar 包

AppClassLoader
负责加载 classpath 里的 jar 包和目录

三、Android 中的 ClassLoader

BootClassLoader

负责 Android系统启动时会使用BootClassLoader来预加载常用类,与Java中的Bootstrap ClassLoader不同的是,它并不是由C/C++代码实现,而是由Java实现的。BootClassLoader是ClassLoader的一个内部类。
PathClassLoader

负责加载已经安装的Apk,也就是/data/app/package 下的apk文件,也可以加载/vendor/lib, /system/lib下的nativeLibrary

DexClassLoader

负责加载可以加载一个未安装的apk文件。

四、双亲委派机制

每一个 ClassLoader 中都有一个 parent 对象,代表的是父类加载器,在加载一个类的时候,会先使用父类加载器去加载,如果在父类加载器中没有找到,自己再进行加载,如果 parent 为空,那么就用系统类加载器来加载。通过这样的机制可以保证系统类都是由系统类加载器加载的。 下面是 ClassLoader 的 loadClass 方法的具体实现。

protected 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) {
                    // 没有找到,再自己加载
                    c = findClass(name);
                }
            }
            return c;
    }

五、源码分析

1.现在我们看下 BaseDexClassLoader 继承自ClassLoader

public class BaseDexClassLoader extends ClassLoader{

	...

	//存放需要加载的dexList
	private final DexPathList pathList;

	/**
     *
     * @param dexPath   需要加载的dex文件所在的路径
     * @param optimizedDirectory  Android系统将dex文件进行优化后所生成的ODEX文件的存放路径,该路径必须是一个内部存储路径。
     * @param librarySearchPath   目标类所使用的c、c++库存放的路径
     * @param parent  该加载器的父加载器,一般为当前执行类的加载器
     */
    public BaseDexClassLoader(String dexPath, File optimizedDirectory,
            String librarySearchPath, ClassLoader parent) {
        this(dexPath, optimizedDirectory, librarySearchPath, parent, false);
    }

	/**
     *
     * @param dexPath
     * @param optimizedDirectory
     * @param librarySearchPath
     * @param parent
     * @param isTrusted   是否已信任,关系到是否可调用隐藏API
     */
    public BaseDexClassLoader(String dexPath, File optimizedDirectory,
            String librarySearchPath, ClassLoader parent, boolean isTrusted) {
        super(parent);
        this.pathList = new DexPathList(this, dexPath, librarySearchPath, null, isTrusted);

        ...
    }

	 /**
     *
     * @param dexFiles 字节缓存数组的dex文件
     * @param parent   该加载器的父加载器
     */
	public BaseDexClassLoader(ByteBuffer[] dexFiles, ClassLoader parent) {
        // TODO We should support giving this a library search path maybe.
        super(parent);
        this.pathList = new DexPathList(this, dexFiles);
    }

	/**
     * 通过完整的类名寻找对应的类
     * @param name  传入一个完整的类名
     * @return
     * @throws ClassNotFoundException
     */
	@Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
		//1 在pathList中寻找name对应的类
        Class c = pathList.findClass(name, suppressedExceptions);
		// 如果未找到此类,则抛出异常
        if (c == null) {
            ClassNotFoundException cnfe = new ClassNotFoundException(
                    "Didn't find class \"" + name + "\" on path: " + pathList);
            for (Throwable t : suppressedExceptions) {
                cnfe.addSuppressed(t);
            }
            throw cnfe;
        }
        return c;
    }
}

PathClassLoader 和DexClassLoader: 继承自BaseDexClassLoader

public class PathClassLoader extends BaseDexClassLoader {

     /**
     *
     * @param dexPath   dex文件路径集合
     * @param parent    父加载器
     */
    public PathClassLoader(String dexPath, ClassLoader parent) {
        //调用父类BaseDexClassLoader 四参构造方法
        super(dexPath, null, null, parent);
    }

    /**
     *
     * @param dexPath   dex文件路径集合
     * @param librarySearchPath    包含 C/C++库的路径集合,多个路径用文件分隔符分隔分割,可以为null
     * @param parent    父加载器
     */
    public PathClassLoader(String dexPath, String librarySearchPath, ClassLoader parent) {
        //调用父类BaseDexClassLoader 四参构造方法
        super(dexPath, null, librarySearchPath, parent);
    }
}
public class DexClassLoader extends BaseDexClassLoader {

	 /**
     *
     * @param dexPath      dex文件路径集合,多个路径用文件分隔符分隔,默认文件分隔符为":"
     * @param optimizedDirectory   解压的dex文件存储路径,这个路径必须是一个内部存储路径,一般情况下使用当前应用程序的私有路径
     * @param librarySearchPath   包含 C/C++ 库的路径集合,多个路径用文件分隔符分隔分割,可以为null
     * @param parent       父加载器
     */
    public DexClassLoader(String dexPath, String optimizedDirectory,
            String librarySearchPath, ClassLoader parent) {

		// 调用父类BaseDexClassLoader 四参构造方法,在API26以上,librarySearchPath参数已弃用,使用此方法
        super(dexPath, null, librarySearchPath, parent);

		// API26及以下使用此方法
		// super(dexPath, new File(optimizedDirectory), librarySearchPath, parent);
    }
}

我们看1 处 pathList 的 findClass 是如何查找的

/*package*/ final class DexPathList {
    private static final String DEX_SUFFIX = ".dex";
    private static final String zipSeparator = "!/";

    private final ClassLoader definingContext;

	// dex/resource 存放dex的数组
    private Element[] dexElements;

	// 存放本地库文件的列表
    private final List<File> nativeLibraryDirectories;

	// 存放系统本地库文件的列表
    private final List<File> systemNativeLibraryDirectories;

	// 存放创建dexElement列表时引发异常的列表
    private IOException[] dexElementsSuppressedExceptions;

	DexPathList(ClassLoader definingContext, String dexPath,
            String librarySearchPath, File optimizedDirectory, boolean isTrusted) {
        ...

        this.definingContext = definingContext; //BaseDexClassLoader构造器中会传入其本身

        ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();
        // 通过dexPath路径使用分隔符将其转换成dexElements列表
        this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,
                                           suppressedExceptions, definingContext, isTrusted);

        ...
    }

	 // 为本地库搜索路径生成一个directory/zip path元素数组
	 private static Element[] makeDexElements(List<File> files, File optimizedDirectory,
            List<IOException> suppressedExceptions, ClassLoader loader, boolean isTrusted) {
      Element[] elements = new Element[files.size()];
      int elementsPos = 0;

      for (File file : files) {
          if (file.isDirectory()) { //如果是文件夹,则直接添加至elements
              elements[elementsPos++] = new Element(file);
          } else if (file.isFile()) { //如果是文件

              String name = file.getName();
              DexFile dex = null;
              //是否为 .dex 文件
              if (name.endsWith(DEX_SUFFIX)) {
                  // Raw dex file (not inside a zip/jar).
                  try {
                      //如果是 dex 文件,则加载这个文件
                      dex = loadDexFile(file, optimizedDirectory, loader, elements);
                      if (dex != null) {
                          //将dex 文件保存,注意第二个参数传的底 null
                          elements[elementsPos++] = new Element(dex, null);
                      }
                  } catch (IOException suppressed) {
                      System.logE("Unable to load dex file: " + file, suppressed);
                      suppressedExceptions.add(suppressed);
                  }
              } else {
                  //如果不是目录且不是 .dex 结尾的,那么他可能是 jar。加载这个文件
                      dex = loadDexFile(file, optimizedDirectory, loader, elements);
                  if (dex == null) {
                      elements[elementsPos++] = new Element(file);
                  } else {
                      // 保存dex 文件 和 当前的 file,这种情况下可能是 jar,具体可以看这个构造方法
                      elements[elementsPos++] = new Element(dex, file);
                  }

              if (dex != null && isTrusted) { //如果dex对象不为空且是允许信任状态
                dex.setTrusted(); // 将此dex对象设置为已信任,它可以访问平台的隐藏api
              }
          } else {
              System.logW("ClassLoader referenced unknown path: " + file);
          }
      }
      if (elementsPos != elements.length) {
          elements = Arrays.copyOf(elements, elementsPos);
      }
      return elements;
    }

	public Class<?> findClass(String name, List<Throwable> suppressed) {
		// 遍历dex列表
        for (Element element : dexElements) {
            //2
            Class<?> clazz = element.findClass(name, definingContext, suppressed);
			//如果找到我们需要的name类,直接返回当前clazz
            if (clazz != null) {
                return clazz;
            }
        }

        if (dexElementsSuppressedExceptions != null) {
            suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
        }
        return null;
    }
}

看注释2处 此时会通过makeDexElements方法生成一个Element数组,紧接着当前类中的findClass方法又会调用DexPathList中的Element类的findClass方法。

static class Element {

        private final File path;

        private final DexFile dexFile;

        private ClassPathURLStreamHandler urlHandler;
        private boolean initialized;

		...

		...

        public Class<?> findClass(String name, ClassLoader definingContext,List<Throwable> suppressed) {
			// 3 通过loadClassBinaryName方法寻找name类,找到即返回它,否则返回null
            return dexFile != null ? dexFile.loadClassBinaryName(name, definingContext, suppressed)
                    : null;
        }

    }

注释3 处   通过loadClassBinaryName 最后调用native层 defineClassNative的方法 分析到这里可以看出,最终进行Class字节码的加载操作,是通过底层的native方法来完成的。

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

(0)

相关推荐

  • jvm之java类加载机制和类加载器(ClassLoader)的用法

    当程序主动使用某个类时,如果该类还未被加载到内存中,则JVM会通过加载.连接.初始化3个步骤来对该类进行初始化.如果没有意外,JVM将会连续完成3个步骤,所以有时也把这个3个步骤统称为类加载或类初始化. 一.类加载过程 1.加载 加载指的是将类的class文件读入到内存,并为之创建一个java.lang.Class对象,也就是说,当程序中使用任何类时,系统都会为之建立一个java.lang.Class对象. 类的加载由类加载器完成,类加载器通常由JVM提供,这些类加载器也是前面所有程序运行的基础

  • Java运行时环境之ClassLoader类加载机制详解

    背景:听说ClassLoader类加载机制是进入BAT的必经之路. ClassLoader总述: 普通的Java开发其实用到ClassLoader的地方并不多,但是理解透彻ClassLoader类的加载机制,无论是对我们编写更高效的代码还是进BAT都大有裨益:而从"黄埔军校"出来的我对ClassLoader的理解都是借鉴了很多书籍和博客,站在了各大博主的肩膀上,感谢你们!上菜,Classloader最主要的作用就是将Java字节码文件(后缀为.class)加载到JVM中,JVM在启动时

  • java ClassLoader机制详细讲解

    要深入了解ClassLoader,首先就要知道ClassLoader是用来干什么的,顾名思义,它就是用来加载Class文件到JVM,以供程序使用的.我们知道,java程序可以动态加载类定义,而这个动态加载的机制就是通过ClassLoader来实现的,所以可想而知ClassLoader的重要性如何. 看到这里,可能有的朋友会想到一个问题,那就是既然ClassLoader是用来加载类到JVM中的,那么ClassLoader又是如何被加载呢?难道它不是java的类? 没有错,在这里确实有一个Class

  • Java Classloader机制用法代码解析

    做Java开发,对于ClassLoader的机制是必须要熟悉的基础知识,本文针对Java ClassLoader的机制做一个简要的总结.因为不同的JVM的实现不同,本文所描述的内容均只限于Hotspot Jvm. 本文将会从JDK默认的提供的ClassLoader,双亲委托模型,如何自定义ClassLoader以及Java中打破双亲委托机制的场景四个方面入手去讨论和总结一下. JDK默认ClassLoader JDK 默认提供了如下几种ClassLoader Bootstrp loader Bo

  • Python深度学习之简单实现猫狗图像分类

    一.前言 本文使用的是 kaggle 猫狗大战的数据集 训练集中有 25000 张图像,测试集中有 12500 张图像.作为简单示例,我们用不了那么多图像,随便抽取一小部分猫狗图像到一个文件夹里即可. 通过使用更大.更复杂的模型,可以获得更高的准确率,预训练模型是一个很好的选择,我们可以直接使用预训练模型来完成分类任务,因为预训练模型通常已经在大型的数据集上进行过训练,通常用于完成大型的图像分类任务. tf.keras.applications中有一些预定义好的经典卷积神经网络结构(Applic

  • 深入解析Java中的Classloader的运行机制

    java有两种类型的classload,一种是user-defined的,一种是jvm内置的bootstrap class loader,所有user-defined的class loader都是java.lang.ClassLoader的子类. 而jvm内置的class loader有3种,分别是 Bootstrap ClassLoader, Extension ClassLoader(即ExtClassLoader),System ClassLoader(即AppClassLoader).

  • Java类加载器ClassLoader用法解析

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

  • Java classloader和namespace详细介绍

    Java classloader和namespace详细介绍 Java虚拟机通过装载.连接和初始化一个JAVA类型,使该类型可以被正在运行的JAVA程序所使用.其中,装载就是把二进制形式的JAVA类型读入JAVA虚拟机中.连接就是把这种已经读入虚拟机的二进制形式的类型数据合并到虚拟机的运行时状态中去.连接阶段分为三个步骤-验证.准备和解析.验证确保了JAVA类型数据格式正确并适于JAVA虚拟机使用.准备负责为该类分配它所需的内存,比如为它的类变量分配内存.解析把常量池中的符号引用转换为直接引用,

  • Java中ClassLoader类加载学习总结

    双亲委派模型 类加载这个概念应该算是Java语言的一种创新,目的是为了将类的加载过程与虚拟机解耦,达到"通过类的全限定名来获取描述此类的二进制字节流"的目的.实现这个功能的代码模块就是类加载器.类加载器的基本模型就是大名鼎鼎的双亲委派模型(Parents Delegation Model).听上去很牛掰,其实逻辑很简单,在需要加载一个类的时候,我们首先判断该类是否已被加载,如果没有就判断是否已被父加载器加载,如果还没有再调用自己的findClass方法尝试加载.基本的模型就是这样(盗图

  • java自定义ClassLoader加载指定的class文件操作

    继承ClassLoader并且重写findClass方法就可以自定义一个类加载器,具体什么是类加载器以及类加载器的加载过程与顺序下次再说,下面给出一个小demo 首先定义一个类,比如MyTest,并且将其编译成class文件,然后放到一个指定的文件夹下面,其中文件夹的最后几层就是它的包名,这里我将这个编译好的类放到 : /Users/allen/Desktop/cn/lijie/MyTest.class package cn.lijie; public class MyTest { public

  • classloader类加载器_基于java类的加载方式详解

    基础概念 Classloader 类加载器,用来加载 Java 类到 Java 虚拟机中.与普通程序不同的是.Java程序(class文件)并不是本地的可执行程序.当运行Java程序时,首先运行JVM(Java虚拟机),然后再把Java class加载到JVM里头运行,负责加载Java class的这部分就叫做Class Loader. JVM本身包含了一个ClassLoader称为Bootstrap ClassLoader,和JVM一样,BootstrapClassLoader是用本地代码实现

随机推荐