Android进阶知识:类加载相关

1. 前言
类加载原理作为程序运行的基础,一直在程序的背后默默的付出。如今 Android 中的插件化、热修复等动态加载技术的实现也都涉及到了类加载的原理。关于类加载的相关知识我以前也是遇到一点看一点,没有完整的详细的了解过,最近有时间专门对这块知识进行了学习,于是这里做一个总结。
2. 类加载过程
一个类从.class 文件被加载到内存,到在内存中使用,最后从内存中卸载,这是一个完整的生命周期过程。不过在获得.class 文件之前,我们编码时的文件格式还是.java 文件格式,还记得刚学 Java 时学到过在完成编码之后要先执行 javac 命令进行编译,编译生成对应的.class 文件,之后再通过 java 命令执行 Java 程序。不过当时只知道是先编译再运行,并不知道到底是怎么运行的谁去运行的。
其实一个类从.class 文件被加载到内存到从内存中卸载,整个生命周期一共经过以下几个阶段:

加载
连接(包含验证、准备、解析三个阶段)
初始化
使用
卸载

2.1 加载阶段
在加载阶段虚拟机主要完成以下三件事情:

通过一个类的全限定名来获取定义此类的二进制字节流。
将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
在内存中生成一个代表这个类的 java.lang.Class 对象,作为方法区这个类的各种数据的访问入口。

这个简单的来说加载阶段主要就是将类的.class 文件作为二进制字节流读入内存,并且在内存中实例化一个 java.lang.Class 对象以便后续访问。在这个阶段中.class 文件二进制字节流的读取来源没有太多限制,可以非常灵活。比如可以从本地系统中读取、可以从 jar 包中读取、可以从网络下载等等。
2.2 连接—验证阶段
验证是连接阶段中的第一步,主要的作用是保证 Class 文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机的自身安全。在验证阶段大致会完成以下四个检验操作:

文件格式验证:验证字节流是否符合 Class 文件格式的规范,并且能被当前版本的虚拟机处理。
元数据验证:对字节码描述的信息进行语义分析,保证其描述符合 Java 语言规范的要求。
字节码验证:通过数据流和控制流分析,确定程序逻辑是合法的、符合罗急的。
符号引用验证:对类自身以外的信息进行匹配性校验。

从以上几个操作可以看出,这个阶段主要就是将二进制字节流进行一个合法验证,包括文件格式、语义、数据流控制流和符号引用等。保证不会出现类似文件格式错误、继承了被 final 修饰的类、指令跳转错误、类型转换错误、修饰符访问性等等错误情况。
2.3 连接—准备阶段
准备阶段中主要是为类中静态变量在方法区里分配内存并且设置类变量的初始值。这里的初始值即零值。具体如下:

数据类型
零值

int
0

long
0L

short
(short)0

char
'u0000'

byte
(byte)0

boolean
false

float
0.0f

double
0.0d

reference
null

2.4 连接—解析阶段
解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调试限定符 7 类符号引用进行。
2.5 初始化阶段
初始化阶段中真正开始执行类中定义的 Java 程序代码,为所有类变量进行赋值,执行静态代码块。

  1. Java 中类加载器

从上面的类加载过程可以看出在初始化阶段以前除了加载阶段,其余阶段都是由虚拟机主导控制。而在加载阶段可以通过自定义类加载器进行参与。类加载器顾名思义是用来加载类的,负责将类的.class 文件转换成内存中类的二进制字节流。Java 中的类加载器按类型分有两种:系统类加载器和自定义类加载器。其中系统类加载器有主要有三种,分别是:引导类加载器(Bootstrap ClassLoader)、拓展类加载器(Extensions ClassLoader)和应用程序类加载器(Application ClassLoader)。
3.1 系统类加载器
3.1.1 引导类加载器(Bootstrap ClassLoader)
这个类加载器是用 C /C++ 语言实现的,用来加载 JDK 中的核心类,主要加载 $JAVA_HOME/jre/lib 目录下的类,例如 rt.jar、resources.jar 等包中的类。
3.1.2 拓展类加载器(Extensions ClassLoader)
这个类加载器是用 Java 语言实现的,实现类为 ExtClassLoader,用来加载 Java 的拓展类,主要加载 $JAVA_HOME/jre/lib/ext 目录和系统属性 java.ext.dir 所指定的目录。
3.1.3 应用程序类加载器(Application ClassLoader)
这个类加载器是用 Java 语言实现的,实现类为 AppClassLoader,可以通过 ClassLoader.getSystemClassLoader 方法获取到,主要加载 Classpath 目录和系统属性 java.class.path 指定的目录下的类。
3.1.4 ClassLoader 的继承关系


public class ClassLoaderDemo {
    public static void main(String[] args) {
        ClassLoader classLoader = ClassLoaderDemo.class.getClassLoader();
        while (classLoader != null) {
            System.out.println("ClassLoader:" + classLoader);
            classLoader = classLoader.getParent();
        }
    }
}

这里新建一个 Java 类 ClassLoaderDemo,循环打印其类加载器和父类加载器,控制台输出结果如下。

从结果可以看出 ClassLoaderDemo 类的类加载器是 AppClassLoader,AppClassLoader 的父类加载器是 ExtClassLoader,而 ExtClassLoader 的父类加载器就为 null 了,这是因为 ExtClassLoader 的父类加载器 BootstrapClassLoader 是由 C /C++ 语言实现的,所以在 Java 中无法获取到它的引用。接下来再进入源码来看一下,先看 AppClassLoader。

static class AppClassLoader extends URLClassLoader {
    ......
}

查看源码发现 AppClassLoader 的父类并不是 ExtClassLoader 而是 URLClassLoader,再看 ExtClassLoader。

static class ExtClassLoader extends URLClassLoader {
    ......
}

ExtClassLoader 的父类也是 URLClassLoader 进而再看 URLClassLoader。

public class URLClassLoader extends SecureClassLoader implements Closeable {
    ......
}

public class SecureClassLoader extends ClassLoader {
    ......
}

public abstract class ClassLoader {
    ......
}

URLClassLoader 的父类是 SecureClassLoader,而 SecureClassLoader 的父类是 ClassLoader,ClassLoader 是一个抽象类。通过对源码的跟踪发现,似乎这里的继承关系与控制台输出的结果不太一致。于是进一步去看输出 ClassLoaderDemo 中调用的 ClassLoader.getParent() 方法源码。

public final ClassLoader getParent() {
        if (parent == null)
            return null;
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            checkClassLoaderPermission(parent, Reflection.getCallerClass());
        }
        return parent;
    }
     // The parent class loader for delegation
    // Note: VM hardcoded the offset of this field, thus all new fields
    // must be added *after* it.
    private final ClassLoader parent;

ClassLoader 的 getParent 方法中看到返回的是一个成员变量中的 parent,他是一个 ClassLoader 类型对象。继续跟踪寻找它是在哪里初始化赋值的。

private ClassLoader(Void unused, ClassLoader parent) {
        this.parent = parent;
        if (ParallelLoaders.isRegistered(this.getClass())) {
            parallelLockMap = new ConcurrentHashMap<>();
            package2certs = new ConcurrentHashMap<>();
            domains =
                Collections.synchronizedSet(new HashSet<ProtectionDomain>());
            assertionLock = new Object();
        } else {
            // no finer-grained lock; lock on the classloader instance
            parallelLockMap = null;
            package2certs = new Hashtable<>();
            domains = new HashSet<>();
            assertionLock = this;
        }
    }

    protected ClassLoader(ClassLoader parent) {
        this(checkCreateClassLoader(), parent);
    }

    protected ClassLoader() {
        this(checkCreateClassLoader(), getSystemClassLoader());
    }

跟踪查看源码发现父类加载器 parent 是在 ClassLoader 的构造函数时传入的,如果没有传入默认调用 getSystemClassLoader 方法获取一个父类加载器。接下来继续查看 getSystemClassLoader 方法。

public static ClassLoader getSystemClassLoader() {
        initSystemClassLoader();
        if (scl == null) {
            return null;
        }
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            checkClassLoaderPermission(scl, Reflection.getCallerClass());
        }
        return scl;
    }

getSystemClassLoader 方法中又调用了 initSystemClassLoader 方法初始化系统类加载器,方法最后将这个类加载器 scl 返回。继续查看 initSystemClassLoader 方法。

private static synchronized void initSystemClassLoader() {
        if (!sclSet) {
            if (scl != null)
                throw new IllegalStateException("recursive invocation");
            sun.misc.Launcher l = sun.misc.Launcher.getLauncher();
            if (l != null) {
                Throwable oops = null;
                scl = l.getClassLoader();
      .......
}

这个方法中获取到 Launcher 之后调用了 Launcher 的 getClassLoader 方法获取到创建的类加载器,于是再到 Launcher 中查看。

public ClassLoader getClassLoader() {
        return this.loader;
}

Launcher 的 getClassLoader 方法中返回了其成员变量中的 loader 对象,于是再去寻找这个对象的创建。

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);
        }
    ......
}

在 Launcher 的构造函数里找到,这里是通过 Launcher.AppClassLoader.getAppClassLoader(var1) 方法创建的 loader,于是再进入查看。

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);
                }
            });
}

getAppClassLoader 方法中最终 new 了一个 AppClassLoader 返回,这就又回到了 AppClassLoader 的构造方法。之前看过构造传入的第二个参数就是 parent 父类加载器,这里传入的 var0 可以看到就是 ExtClassLoader 类型。
总结来说 ClassLoader 的父子关系并不是由继承实现的,A 是 B 的父类加载器,并不表示 B 继承了 A,每个 CLassLoader 中保存了一个它的父类加载器的引用,通过 getParent 方法获得的就是它的值,它是在类加载器创建时构造函数中进行赋值的。如果构造中没有传入父类加载器默认调用 getSystemClassLoader 方法获取系统类加载器,通过查看发现默认系统类加载器就是 AppClassLoader。在 Launcher 的构造方法中,会依次创建 ExtClassLoader 和 AppClassLoader,此时 ExtClassLoader 作为父类加载器由构造函数传入 AppClassLoader。实际的类加载继承关系如下图。

3.2 自定义类加载器
在一些特殊需求场景下可能需要程序员自定义类加载器。例如从网络下载一个加密过的.class 类文件,此时就需要自定义类加载器,先进行文件解密再进行类加载。下面就来模拟一下这个例子,先定义一个测试类 TestPrint。


public class TestPrint {
    public void printString() {
        System.out.println("测试输出字符串");
    }
}

使用 javac 命令编译生成 TestPrint.class 文件。

再写个加密文件方法,这里加密就用简单使用下 Base64 加密,将编译生成的.class 文件转成二进制字节流加密后再保存成本地文件。


public class Test {
    public static void main(String[] args) {
        byte[] classBytes = FileIOUtils.readFile2BytesByStream("/Users/sy/Downloads/ClassLoader/TestPrint.class");
        FileIOUtils.writeFileFromBytesByStream("/Users/sy/Downloads/ClassLoader/TestPrint.class",Base64.getEncoder().encode(classBytes));
}
}

得到加密后的 TestPrint.class 后接下来编写自定义的类加载器 MyClassLoader。


public class MyClassLoader extends ClassLoader {
    private String path;
    protected MyClassLoader(String path) {
        this.path = path;
    }
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        Class aClass = null;
        // 获取二进制字节流
        byte[] classBytes = loadClassBytes(name);
        if (classBytes == null) {
            System.out.println("class data is null");
        } else {
            aClass = defineClass(name, classBytes, 0, classBytes.length);
        }
        return aClass;
    }
    private byte[] loadClassBytes(String name) {
        String fileName = getFileName(name);
        File file = new File(path, fileName);
        InputStream inputStream = null;
        ByteArrayOutputStream outputStream = null;
        try {
            inputStream = new FileInputStream(file);
            outputStream = new ByteArrayOutputStream();
            byte[] buffer = new byte[1024];
            int len = 0;

            while ((len = inputStream.read(buffer)) != -1) {
                outputStream.write(buffer, 0, len);
            }
            return outputStream.toByteArray();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                inputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                outputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return null;
    }
    private String getFileName(String name) {
        String fileName;
        int index = name.lastIndexOf(".");
        if (index == -1) {
            fileName = name + ".class";
        } else {
            fileName = name.substring(index + 1) + ".class";
        }
        return fileName;
    }
}

自定义类加载器分为以下几个步骤:

继承抽象类 ClassLoader。
复写 findClass 方法。
在 findClass 方法中获取到二进制字节流后,调用 defineClass 方法。

在 MyClassLoader 的 findClass 方法中首先读取到本地硬盘下的 TestPrint.class 文件的字节流,然后调用 defineClass 方法,该方法会将字节流转化为 Class 类型。最后写一个测试类调用。


public class TestMyClassLoader {
    public static void main(String[] args) {
        // 初始化类加载器
        MyClassLoader myClassLoader = new MyClassLoader("/Users/sy/Downloads/ClassLoader");
        try {
            // 使用自定义类加载器获取Class
            Class<?> printTest = myClassLoader.loadClass("TestPrint");
            // 创建实例
            Object instance = printTest.newInstance();
            System.out.println("classloader:" + instance.getClass().getClassLoader());
            Method method = printTest.getDeclaredMethod("printString", null);
            method.setAccessible(true);
            method.invoke(instance, null);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

运行结果

此时发生错误是因为做了 Base64 加密,需要在 defineClass 前进行一个解码操作,修改 findClass 方法。


@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
        Class aClass = null;
        // 获取二进制字节流
        byte[] classBytes = loadClassBytes(name);
        // Base64 decode
        classBytes = Base64.getDecoder().decode(classBytes);
        if (classBytes == null) {
            System.out.println("class data is null");
        } else {
            aClass = defineClass(name, classBytes, 0, classBytes.length);
        }
        return aClass;
}

再次运行结果