0
点赞
收藏
分享

微信扫一扫

ClassLoader源码解析

Java中的所有类,必须被装载到jvm中才能运行,这个装载工作是由jvm中的类装载器完成的,类装载器所做的工作实质是把类文件从硬盘读取到内存中,JVM在加载类的时候,都是通过ClassLoader的loadClass()方法来加载class的,loadClass使用双亲委派模式。

官方给出ClassLoader功能翻译为:
类加载器是负责加载类的对象。ClassLoader类是一个抽象类。给定类的二进制名称,类加载器应尝试查找或生成构成该类定义的数据。一种典型的策略是将名称转换为文件名,然后从文件系统中读取该名称的“类文件”。

ClassLoader分类
ClassLoader从继承关系图:

Android中的ClassLoader主要分为BootClassLoader、PathClassLoader和DexClassLoader这三种类型(BootClassLoader位于ClassLoader同文件下)。

BootClassLoader

BootClassLoader部分代码:

class BootClassLoader extends ClassLoader {

    private static BootClassLoader instance;
    @FindBugsSuppressWarnings("DP_CREATE_CLASSLOADER_INSIDE_DO_PRIVILEGED")
    public static synchronized BootClassLoader getInstance() {
        if (instance == null) {
            instance = new BootClassLoader();
        }

        return instance;
    }

    public BootClassLoader() {
        super(null);
    }
    ...
}

BootClassLoader 继承自ClassLoader抽象类,实现方式为单例模式,需要注意的是BootClassLoader的访问修饰符是默认的,只有在同一个包中才可以访问,所以我们在应用程序中是无法直接调用到的。比如系统提供的String、Handler
这样的类是由BootClassLoader来加载的,这样就可以获取这个类的classLoader。

            String.class.getClassLoader();
            Handler.class.getClassLoader()

PathClassLoader类代码:

public class PathClassLoader extends BaseDexClassLoader {
    public PathClassLoader(String dexPath, ClassLoader parent) {
        super((String)null, (File)null, (String)null, (ClassLoader)null);
        throw new RuntimeException("Stub!");
    }

    public PathClassLoader(String dexPath, String librarySearchPath, ClassLoader parent) {
        super((String)null, (File)null, (String)null, (ClassLoader)null);
        throw new RuntimeException("Stub!");
    }
}

PathClassLoader继承自BaseDexClassLoader,要在加载类需要传入指定要加载的路径,PathClassLoader构造方法中各个参数的含义:

在Activity或者Fragment等组件中直接可以获得classLoader对象,得到的是PathClassLoader,它可以通过context获取到。

public class ContextWrapper extends Context {
    @UnsupportedAppUsage
    Context mBase;

    @Override
    public ClassLoader getClassLoader() {
        return mBase.getClassLoader();
    }
}

DexClassLoader类代码:

public class DexClassLoader extends BaseDexClassLoader {
    public DexClassLoader(String dexPath, String optimizedDirectory, String librarySearchPath, ClassLoader parent) {
        super((String)null, (File)null, (String)null, (ClassLoader)null);
        throw new RuntimeException("Stub!");
    }
}

DexClassLoader 也是继承自BaseDexClassLoader ,相比较PathClassLoader而言,DexClassLoader的构造方法中多了一个参数optimizedDirectory。

optimizedDirectory:Android系统将dex文件进行优化后所生成的ODEX文件的存放路径,该路径必须是一个内部存储路径。PathClassLoader中使用默认路径“/data/dalvik-cache”,而DexClassLoader则需要我们指定ODEX优化文件的存放路径。

上述三种ClassLoader中,PathClassLoader的parent为BootClassLoader,DexClassLoader的parent同样为BootClassLoader。

加载类过程:

首先加载类是通过ClassLoader中的loadClass方法加载,ClassLoader部分代码:

    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) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    c = findClass(name);
                }
            }
            return c;
    }

    @Override
    protected Class<?> loadClass(String className, boolean resolve)
           throws ClassNotFoundException {
        Class<?> clazz = findLoadedClass(className);

        if (clazz == null) {
            clazz = findClass(className);
        }

        return clazz;
    }

    protected final Class<?> findLoadedClass(String name) {
        ClassLoader loader;
        if (this == BootClassLoader.getInstance())
            loader = null;
        else
            loader = this;
        return VMClassLoader.findLoadedClass(loader, name);
    }

loadClass方法中,首先调用findLoadedClass(String)方法检查这个类是否被Jvm加载过,findLoadedClass为native方法。如果找到缓存,则直接返回类,如果没有缓存,则去找parent父类加载器去加载类(注意这个类不是父类,而是parent的真实类型。如PathClassLoader的parent是BootClassLoader)。如果父类不为null,则去层层往上找parent类加载器来加载这个类。如果父加载器为null,类加载器装载虚拟机内置的加载器调用findClass(String)方法装载类。这个机制叫双亲委派机制。

  • 什么是双亲委派机制?

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

双亲委派机制的作用

1.避免重复加载,当父类加载器已经加载了该类的时候,就没有必要子类加载器再加载一次了。

首先,类加载器是按照级别层层往下加载的,当下层的加载器去加载某一个类时,有可能上层的加载已经加载过的,比如FrameWork层的加载被BootClassLoader加载过,下层不用再去加载了。

2.安全性考虑,防止核心API库被随意篡改。
系统类加载器已经加载过了FrameWork层了类,如果我们自己再写一个系统级别的类,创建包java.lang,创建一个自己的String类,类加载器去加载这个类覆盖了原本的java.lang下的String,那么这个时候使用String整个应用就出问题了。

package java.lang;

class String {

    @NonNull
    @Override
    public String toString() {
        return "";
    }

    @Override
    public boolean equals(@Nullable Object obj) {
        return false;
    }
}

而因为双亲委派机制,加载这个String类之前,调用parent的BootClassloader,判断已经加载过String类,这个类其实不会再去找了,解决了被篡改的问题。

类加载的动态性体现:
一个应用程序总是由n多个类组成,Java程序启动时,并不是一次把所有的类全部加载后再运行,它总是先把保证程序运行的基础类一次性加载到jvm中,其它类等到jvm用到的时候再加载,这样的好处是节省了内存的开销,因为java最早就是为嵌入式系统而设计的,内存宝贵,这是一种可以理解的机制,而用到时再加载这也是java动态性的一种体现。

类的加载过程:
  1. 装载:通过“类全路径名”查找并加载类的二进制数据
  2. 链接:把类的二进制数据合并到JRE中

验证:确保被加载类的正确性,确保加载内容不危害虚拟机;
准备:为类的静态变量分配内存,并将其初始化为默认值;
解析:把类中的符号引用转换为直接引用;

  1. 初始化
    静态代码块执行初始化操作
类加载分类

1、启动类加载器 bootstrap classloader :加载jre/lib/rt.jar
2、扩展类加载器 extension classloader :加载jre/lib/ext/*.jar
3、应用程序类加载器 application classloader:加载classpath上指定的类库
双亲委派机制是指当一个类加载器收到一个类加载请求时,该类加载器首先会把请求委派给父类加载器。每个类加载器都是如此,只有在父类加载器在自己的搜索范围内找不到指定类时,子类加载器才会尝试自己去加载。
双亲委派模型工作工程:
  1.当Application ClassLoader 收到一个类加载请求时,他首先不会自己去尝试加载这个类,而是将这个请求委派给父类加载器Extension ClassLoader去完成。
  2.当Extension ClassLoader收到一个类加载请求时,他首先也不会自己去尝试加载这个类,而是将请求委派给父类加载器Bootstrap ClassLoader去完成。
  3.如果Bootstrap ClassLoader加载失败(在<JAVA_HOME>\lib中未找到所需类),就会让Extension ClassLoader尝试加载。
  4.如果Extension ClassLoader也加载失败,就会使用Application ClassLoader加载。
  5.如果Application ClassLoader也加载失败,就会使用自定义加载器去尝试加载。
  6.如果均加载失败,就会抛出ClassNotFoundException异常。

参考
深度分析Java的ClassLoader机制
Android ClassLoader源码解析
ClassLoader源码解析

举报

相关推荐

0 条评论