Java基础-类加载机制(ClassLoader)

2021-08-12  本文已影响0人  涛涛123759

Android知识总结

一、JVM 的类加载架构

ClassLoader

二、类加载器的特性

每个ClassLoader都维护了一份自己的名称空间, 同一个名称空间里不能出现两个同名的类。
为了实现java安全沙箱模型顶层的类加载器安全机制, java默认采用了 双亲委派的加载链 结构。

双亲委派的加载链

2.1、双亲委托模式

某个类加载器在加载类时,首先将加载任务委托给父类加载器,依次递归,如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务或者没有父类加载器时,才自己去加载。

每个类加载器都有自己的加载缓存,当一个类被加载了以后就会放入缓存,等下次加载的时候就可以直接返回了。

当前classLoader的缓存中没有找到被加载的类的时候,委托父类加载器去加载,父类加载器采用同样的策略,首先查看自己的缓存,然后委托父类的父类去加载,一直到BootClassLoader。

双亲委托机制特点:

  • 因为这样可以避免重复加载,当父亲已经加载了该类的时候,就没有必要子ClassLoader再加载一次。

  • 考虑到安全因素,我们试想一下,如果不使用这种委托模式,那我们就可以随时使用自定义的String来动态替代java核心api中定义类型,这样会存在非常大的安全隐患,而双亲委托的方式,就可以避免这种情况,因为String已经在启动时被加载,所以用户自定义类是无法加载一个自定义的ClassLoader。(沙箱安全机制)

    //parent 是父类 BootClassLoader
    private final ClassLoader parent;

    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException{
        // 检查class是否有被加载
        Class c = findLoadedClass(name);
        if (c == null) {
            long t0 = System.nanoTime();
            try {
                if (parent != null) {
                    //如果parent不为null,则调用parent的loadClass进行加载
                    c = parent.loadClass(name, false);
                } else {
                    //parent为null,则调用BootClassLoader进行加载
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
            }
            if (c == null) {
                // 如果都找不到就自己查找
                long t1 = System.nanoTime();
                c = findClass(name);
            }
        }
        return c;
    }

因此我们自己创建的ClassLoader: new PathClassLoader("/sdcard/xx.dex", getClassLoader()); 并不仅仅只能加载 xx.dex中的class。
值得注意的是: c = findBootstrapClassOrNull(name);
按照方法名理解,应该是当parent为null时候,也能够加BootClassLoader加载的类。
new PathClassLoader("/sdcard/xx.dex", null),能否加载Activity.class?
但是实际上,Android当中的实现为:(Java不同)

    private Class findBootstrapClassOrNull(String name) {
        return null;
    }

2.1、findClass

可以看到在所有父ClassLoader无法加载Class时,则会调用自己的 findClass 方法。 findClass 在ClassLoader中的定义为:

protected Class<?> findClass(String name) throws ClassNotFoundException {
    throw new ClassNotFoundException(name);
}

其实任何ClassLoader子类,都可以重写loadClass 与 findClass 。一般如果你不想使用双亲委托,则重写loadClass 修改其实现。而重写 findClass 则表示在双亲委托下,父ClassLoader都找不到Class的情况下,定义自己如何去查找一个Class。而我们的PathClassLoader 会自己负责加载 MainActivity 这样的程序中自己编写的类,利用双亲委托父ClassLoader加载Framework中的 Activity 。说PathClassLoader 并没有重写 loadClass ,因此我们可以来看看PathClassLoader中的 findClass 是如何实现的。最终在BaseDexClassLoader中调用。

    public BaseDexClassLoader(String dexPath, File optimizedDirectory,String
            librarySearchPath, ClassLoader parent) {
        super(parent);
        //创建DexPathList
        this.pathList = new DexPathList(this, dexPath, librarySearchPath,
                optimizedDirectory);
    }
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
        //查找指定的class
        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;
    }

实现非常简单,从 pathList 中查找class。继续查看 DexPathList

    public DexPathList(ClassLoader definingContext, String dexPath,
                       String librarySearchPath, File optimizedDirectory) {
        //.........
        // splitDexPath 实现为返回 List<File>.add(dexPath)
        // makeDexElements 会去 List<File>.add(dexPath) 中使用DexFile加载dex文件返回 Element数组
        this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,
                suppressedExceptions, definingContext);
        //.........
    }

    public Class findClass(String name, List<Throwable> suppressed) {
        //从element中获得代表Dex的 DexFile
        for (Element element : dexElements) {
            DexFile dex = element.dexFile;
            if (dex != null) {
                //查找class
                Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed);
                if (clazz != null) {
                    return clazz;
                }
            }
        }
        if (dexElementsSuppressedExceptions != null) {
            suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
        }
        return null;
    }

三、Android 中 CloassLoader示意图

任何一个 Java 程序都是由一个或多个 class 文件组成,在程序运行时,需要将 class 文件加载到 JVM 中才可以使用,负责加载这些 class 文件的就是 Java 的类加载机制。ClassLoader 的作用简单来说就是加载 class 文件,提供给程序运行时使用。每个 Class 对象的内部都有一个 classLoader 字段来标识自己是由哪个 ClassLoader 加载的。

class Class<T> {
   ... 
  private transient ClassLoader classLoader;
   ...
 }
CloassLoader

从图中可以看出ClassLoader是一个抽象类,而它的具体实现类主要有:

  • ClassLoader
    ClassLoader是一个抽象类,其中定义了ClassLoader的主要功能;
  • BootClassLoader
    BootClassLoader是ClassLoader的内部类,用于预加载preload()常用类以及一些系统Framework层级需要的类;
  • BaseDexClassLoader
    BaseDexClassLoader继承ClassLoader,是抽象类ClassLoader的具体实现类,PathClassLoader和DexClassLoader都继承它
  • PathClassLoader
    PathClassLoader用于Android应用程序类加载器。如果是加载非系统应用程序类,则会加载data/app/目录下的dex文件以及jar、zip、apk中的classes.dex。
  • DexClassLoader
    DexClassLoader可以加载自定义的dex文件以及jar、zip、apk中的classes.dex,,也支持从SD卡进行加载。

四、类加载示意图

类加载示意图

小福利

在线源码阅读:
ANDROID社区

AndroidXRef

上一篇 下一篇

猜你喜欢

热点阅读