ClassLoader的加载机制

简介

一个完整的Java程序是由多个.class文件组成,在程序运行过程中,需要将这些.class文件加载到JVM中才可以使用,而ClassLoader就是负责加载.class文件的。

何时被加载

Java程序启动时,不会一次性加载程序中所有的.class文件,而是在运行过程中动态的加载相应的类到内存中;
通常情况下,.class文件会在以下两种情况被ClassLoader主动加载到内存中:
调用类构造器;
调用类中的static变量或者静态方法;

ClassLoader分类

JVM中自带3个类加载器,他们在JVM中分工不同,但互相依赖。

1、启动类加载器BootstrapClassLoader

由C/C++语言编写,本身属于虚拟机的一部分,无法在Java代码中直接获取它的引用,BootstrapClassLoader加载系统属性 “sun.boot.class.path” 配置下的类文件

System.out.println(System.getProperty("sun.boot.class.path"));

打印结果:JRE目录下的jar包或者.class文件
		E:\JAVA\jdk1.8.0_221\jre\lib\resources.jar;
		E:\JAVA\jdk1.8.0_221\jre\lib\rt.jar;
		E:\JAVA\jdk1.8.0_221\jre\lib\sunrsasign.jar;
		E:\JAVA\jdk1.8.0_221\jre\lib\jsse.jar;
		E:\JAVA\jdk1.8.0_221\jre\lib\jce.jar;
		E:\JAVA\jdk1.8.0_221\jre\lib\charsets.jar;
		E:\JAVA\jdk1.8.0_221\jre\lib\jfr.jar;
		E:\JAVA\jdk1.8.0_221\jre\classes

2、扩展类加载器ExtClassLoader;

ExtClassLoader加载系统属性 “java.ext.dirs” 配置下的类文件;

System.out.println(System.getProperty("java.ext.dirs"));

打印结果:E:\JAVA\jdk1.8.0_221\jre\lib\ext;C:\windows\Sun\Java\lib\ext

3、系统加载器AppClassLoader;

面向用户类加载器,用于加载自己编写的代码以及使用的第三方jar包;

System.out.println(System.getProperty("java.class.path"));//查看AppClassLoader加载的类

打印结果:E:\JavaProject\Test1\build\classes

双亲委派模式

如何知道使用哪一个类加载去加载相应的类,就要用到双亲委派模式。双亲委派模式就是当类加载器收到加载类或资源的请求时,通常是先委托给父类加载器加载,只有当父类加载器找不到指定类或资源时,自身才会执行实际的类加载过程。
其具体实现代码在ClassLoader.java中的loadClass方法中:

protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
 {
     synchronized (getClassLoadingLock(name)) {
         // First, check if the class has already been loaded
         //判断该Class是否已经加载,如果已加载则直接将该Class返回
         Class<?> c = findLoadedClass(name);
         if (c == null) {
             long t0 = System.nanoTime();
             try {
             //如果该Class没有被加载过,则判断parent是否为空,如果不为空则将加载的任务委托给parent
                 if (parent != null) {
                     c = parent.loadClass(name, false);
                 } else {
                 	//如果parent为空,则直接调用BootstrapClassLoader加载该类
                     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();
                 //如果还是没有成功,则调用当前ClassLoader的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;
     }
 }

每一个ClassLoader中都有一个ClassLoader类型的parent引用,并且在构造器中传入parent的值。继续深入源码可知,AppClassLoader传入的parent就是ExtClassLoader,而ExtClassLoader没有传入任何parent,即为null。

举个栗子

Tset test = new Test();
  1. 默认情况下,JVM首先使用AppClassLoader加载Test类;
  2. AppClassLoader将加载的任务委派给它的父类加载器(ExtClassLoader);
  3. ExtClassLoader的parent为null,所以直接将加载任务委派给BootstrapClassLoader;
  4. BootstrapClassLoader在jdk/lib目录下无法找到Test类,因此返回null;
  5. 因为parent和BootstrapClassLoader都没有成功加载Test类,所以AppClassLoader调用自身的findClass加载Test;
  6. 最终Test类被AppClassLoader加载到内存中;

如果想保持双亲委派模式,应该重写findClass(name)方法,如果想破坏双亲委派模式,可以重写loadClass(name)方法。

自定义ClassLoader

JVM预置的3种ClassLoader只能加载特定目录下的.class文件,如果想加载其他特殊位置下的.jar包或类时(如网络或磁盘上的.class文件),默认的ClassLoader就不能满足需求了,所以需要通过自定义ClassLoader加载.class文件。

步骤:

  1. 自定义一个类继承抽象类ClassLoader;
  2. 重写findClass方法;
  3. 在findClass中,调用defineClass方法将字节码转换成Class对象,并返回。

在这里插入图片描述

Android中的ClassLoader

Android和传统的JVM一样,需要通过ClassLoader将目标加载到内存中,类加载器之间也符合双亲委派模式,但Android中的ClassLoader的加载细节还是有略微差别。
Android虚拟机无法直接运行.class文件,Android会将所有.class文件转换成一个.dex文件,并且Android将加载.dex文件的实现封装在BaseDexClassLoader中,一般使用它的两个子类:PathClassLoader和DexClassLoader。

PathClassLoader

用来加载系统apk和被安装到手机中apk内的dex文件,构造函数如下:

//dexPath:dex文件路径,或者包含dex文件的jar包路径,一般是已经安装应用的apk文件路径
public PathClassLoader(String dexPath, ClassLoader parent) {
    super((String)null, (File)null, (String)null, (ClassLoader)null);
    throw new RuntimeException("Stub!");
}

//librarySearchPath:C/C++ native库的路径
public PathClassLoader(String dexPath, String librarySearchPath, ClassLoader parent) {
    super((String)null, (File)null, (String)null, (ClassLoader)null);
    throw new RuntimeException("Stub!");
}

PathClassLoader只有两个构造方法,具体实现在BaseDexClassLoader里。当一个App被安装到手机后,apk里面的class.dex中的class均是通过PathClassLoader来加载的,可以通过代码验证;

ClassLoader loader = MainActivity.class.getClassLoader();
System.out.println(loader.toString())

打印结果如下:

dalvik.system.PathClassLoader[DexPathList[[zip file “/data/app/com.gree.smartcarddemo-Oc5Bm8N4ZFvwIUNapQAacQ==/base.apk”],
nativeLibraryDirectories=[/data/app/com.gree.smartcarddemo-Oc5Bm8N4ZFvwIUNapQAacQ==/lib/arm64, /data/app/com.gree.smartcarddemo-Oc5Bm8N4ZFvwIUNapQAacQ==/base.apk!/lib/arm64-v8a, /system/lib64, /product/lib64]]]

DexClassLoader

DexClassLoader可以从SD卡上加载包含class.dex的.jar和.apk文件,这也是插件化和热修复的基础,在不需要安装应用的情况下,加载需要使用的dex文件。构造函数如下:

//dexPath:包含class.dex的apk、jar文件路径,多个路径用文件分隔符分隔
//optimizedDirectory用来缓存优化dex文件的路径,即从apk或jar文件中提取出来的dex文件,该路径不能为空
//且应该是私有的,有读写权限的路径
public DexClassLoader(String dexPath, String optimizedDirectory, String librarySearchPath, ClassLoader parent) {
    super((String)null, (File)null, (String)null, (ClassLoader)null);
    throw new RuntimeException("Stub!");
}
发布了4 篇原创文章 · 获赞 0 · 访问量 34

猜你喜欢

转载自blog.csdn.net/er_fa/article/details/105527136