[关闭]
@Awille 2018-12-14T16:55:30.000000Z 字数 6750 阅读 93

理解ClassLoader

android java 热修复基础


Java中的ClassLoader

ClassLoader的类型

java中的类加载器有两种类型,即系统类加载器和自定义类加载器。
系统类加载器分为
1、Boostrap ClassLoader(引导类加载器)
有c/c++实现,用于加载指定的JDK核心类库。

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

由以上代码可以输出Bootstrap所加载的目录:

C:\Program Files\Java\jdk1.8.0_191\jre\lib\resources.jar;
C:\Program Files\Java\jdk1.8.0_191\jre\lib\rt.jar;
C:\Program Files\Java\jdk1.8.0_191\jre\lib\sunrsasign.jar;
C:\Program Files\Java\jdk1.8.0_191\jre\lib\jsse.jar;
C:\Program Files\Java\jdk1.8.0_191\jre\lib\jce.jar;
C:\Program Files\Java\jdk1.8.0_191\jre\lib\charsets.jar;
C:\Program Files\Java\jdk1.8.0_191\jre\lib\jfr.jar;
C:\Program Files\Java\jdk1.8.0_191\jre\classes

可以看到基本都是lib文件夹下的jar包,都为核心的类库

2、Extension ClassLoader (拓展类加载器)
用来加载以下目录的类库:
a、$JAVA_HOME/jre/lib 目录中的jar包
b、系统属性java.ext.dir所指定的目录

查看其加载的目录:

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

结果为:

C:\Program Files\Java\jdk1.8.0_191\jre\lib\ext;
C:\Windows\Sun\Java\lib\ext

3、Application ClassLoader(应用程序类加载器)
实现类为AppClassLoader,也称作系统类加载器(System ClassLoader)
用来加载以下的类库
a、当前程序的Classpath目录
b、java.class.path指定的目录

4、Custom ClassLoader(自定义类加载器)
通过继承java.lang.ClassLoader来实现自己的自定义类加载器

ClassLoader的继承关系

public class Test {
    public static void main(String[] args) {
        ClassLoader loader = Test.class.getClassLoader();
        while (loader != null) {
            System.out.println(loader);
            loader = loader.getParent();
        }
    }
}

查看输出:

sun.misc.Launcher$AppClassLoader@18b4aac2
    sun.misc.Launcher$ExtClassLoader@1b6d3586

根据输出结果,是可以猜测 Test类的类加载器是系统类加载器,而系统类加载器的父类为拓展类加载器。其实bootstrapClassLoader也是应该打出来的,因为其实由c++来实现的,所以没有打印出来。
这里系统类加载器的父类与拓展类加载器,但是这并不代表着系统类加载继承了拓展类加载器。
java中实际的类继承关系为:
ClassLoader(抽象类)<-----SecureLoader(继承ClassLoader并加入权限功能,没有实现)<------URLClassLoader(继承SecureLoader,加入功能可以通过URL从jar文件和文件夹中加载类和资源)

ExtClassLoader和AppClassLoader都继承自URLClassLoader,他们都为Launcher的内部类,Launcher是java虚拟机的入口,这连两个类都在Launcher中进行初始化。

双亲委托模式

在加载一个类时,首先判断该class是否已经加载,如果没有则不是自身去查找,而是委托给父类加载器去找,这样依次递归到达最顶层,到最顶层如果Bootsrap ClassLoader找到了该class,就直接返回,如果没有找到就向下查找,知道最后让最下层的自身去查找。

classLoader中的loadClass方法 :

protected Class<?> loadClass(String name, boolean resolve)
    throws ClassNotFoundException
{
    synchronized (getClassLoadingLock(name)) {
        // First, check if the class has already been loaded
        Class<?> c = findLoadedClass(name);//首先从加载缓存中查看是否已经加载 
        if (c == null) {//如果没有
            long t0 = System.nanoTime();
            try {
                if (parent != null) {
                    c = parent.loadClass(name, false);//递归交给父类
                } else {
                    c = findBootstrapClassOrNull(name);
                    //递归失败到最顶层,BootstrapClassLoader在指定目录中加载
                }
            } catch (ClassNotFoundException e) {
                // ClassNotFoundException thrown if class not found
                // from the non-null parent class loader
            }
            if (c == null) {//如果BootstrapClassLoader加载失败
                // If still not found, then invoke findClass in order
                // to find the class.
                long t1 = System.nanoTime();
                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;
    }
}

这种模式的好处:
1、避免类的重复
2、更加安全,防止出现自定义一个String类代替系统String这种情况

自定义类加载器

如果需要加载网络上或者D盘中的jar包,则需要自定义类加载器
实现步骤一般为:
(1)定义一个classloader并继承ClassLoader
(2)重写findclass方法,在findclass中调用defineclass

import java.io.*;
public class DiskClassLoader extends ClassLoader {
    private String path;
    DiskClassLoader(String path) {
        this.path = path;
    }
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
    Class clazz = null;
    byte[] classData = loadClassData(name);
    System.out.println(classData.length);
    if (classData == null) {
        throw new ClassNotFoundException();
    } else {
        clazz = defineClass(name.substring(0, 4), classData, 0, classData.length);
    }
    return clazz;
}
public byte[] loadClassData(String name) { //name为文件名
    File file = new File(path, name);
    FileInputStream in = null;
    ByteArrayOutputStream out = null;
    try {
        in = new FileInputStream(file);
        out = new ByteArrayOutputStream();
        byte[] buffer = new byte[1024];
        int length = 0;
        while ((length = in.read(buffer)) != -1) {
            out.write(buffer, 0, length);
        }
        return out.toByteArray();
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        try {
            if (in != null) {
                in.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        try {
            if (out != null) {
                out.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    return null;
}
}

写一个测试类编译成class文件,放在D盘根目录之下:

public class Test {
    public void say() {
        System.out.println("yyyyyyyy");
    }
}

测试:

public class Testee {
    public static void main(String[] args) {
        DiskClassLoader diskClassLoader = new DiskClassLoader("D:\\");
        try {
            Class c = diskClassLoader.loadClass("Test.class");
            if (c != null) {
                try {
                    Object obj = c.newInstance();
                    Method method = c.getDeclaredMethod("say", null);
                    method.invoke(obj, null);
                } catch (InstantiationException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                } catch (NoSuchMethodException e) {
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                    e.printStackTrace();
                }
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

结果:

384
yyyyyyyy

成功。

Android中的classLoader

android中的classloader与java中的classloader还是有比较大的区别的,java中的classloader用来加载class文件(包括jar包里面的)。android中的classloader用来加载dex文件。

ClassLoader类型

android中的classloader同样也分两种类型,系统类加载器与自定义类加载器。
系统类加载器:
(1)BootClassLoader
android系统会启动BootClassLoader来加载常用类
(java中boostrapClassLoader会用来加载核心类,这里类似)
(2)DexClassLoader
用来加载dex文件以及包含dex的压缩文件(apk和jar文件)
其构造函数为:

public DexClassLoader(String dexPath, String optimizedDirectory, String LibrarySearchPath, ClassLoader parent){ ... }

dexPath为dex文件路径,可以为多个,用:分隔
optimizedDirectory为解压的dex文件存储路径
LibrarySearchPath:包含C/C++库的路径集合
parent:父类加载器

(3)PathClassLoader
加载系统类和应用程序类,一般用来加载已经安装的apk文件。其构造方法为:

public PathClassLoader(String dexPath, String LibrarySearchPath, ClassLoader parent)

这个类加载器的构造方法相对于DexClassLoader少了一个参数optimizedDirectory,即dex文件解压后的路径设置没了,其默认值为/data/dalvik-cache,可见,PathClassLoader用来加载已经安装的apk文件的dex文件。(在android系统当中,已经安装的apk文件的dex文件会放在/data/dalvik-cache当中)。

ClassLoader的继承关系

查看加载一个activity的调用过程中涉及到的classloader,查看方法为在activity的oncreate当中,打印该类的加载器以及所有的父类加载器(写个while循环就可以了)。

dalvik.system.PathClassLoader[
    DexPathList[
        [zip file"/data/app/com.example.will.hotfixtest-2/base.apk"],
        nativeLibraryDirectories=
        [/data/app/com.example.will.hotfixtest-2/lib/x86,                              /data/app/com.example.will.hotfixtest-2/base.apk!/lib/x86, 
        /vendor/lib, 
        /system/lib]
     ]
 ]
java.lang.BootClassLoader@a52455e

可以看到涉及到PathClassLoader和BootClassLoader,PathClassLoader在上面已经提到过,这是专门用来加载已经安装的apk文件中的dex文件的,而BootClassLoader是用来加载一些常用类的,跟java的boostrapClassLoader差不多,所以这里出现并不意外。

ClassLoader的继承关系

顶层:ClassLoader(抽象类)
第二层(全部都继承ClassLoader) BootClassLoader BaseDexClassLoader SecureClassLoader

第三层:继承BaseDexClassLoader(DexClassLoader, PathClassLoader, IMemoryDexClassLoader); 继承自SecureClassLoader(URLClassLoader)

这里的SecureClassLoader与URLClassLoader跟java的classLoader是一样的。
BootClassLoader不能说是属于第二层,他是ClassLoader的继承类。

BaseDexClassLoader:ClassLoader的实现类。

IMemoryDexClassLoader:用于加载内存中的dex文件
PathClassLoader与DexClassLoader前面已经提过了。

ClassLoader的加载过程

android中的dex文件的加载同样遵循双亲委托模式。

BootClassLoader的创建

Zygote进程的Zygote入口中创建

PathClassLoader的创建

在SystemServer中采用工厂模式创建。

后面这三个小点涉及到了android的源码,目前感觉以自己的水平还是不适合去研究源码,所以跳过了。希望这些对后面热修复相关知识的学习有一些帮助。

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注