Android ClassLoader的前世今生

2020-12-17  本文已影响0人  feifei_fly

一直以来我对Classloader理解都比较模糊,究竟什么是ClassLoader。针对下面几点疑,对ClassLoader做了一下梳理,记录在此。

一、ClassLoader 做了什么事情,它为什么会存在

我们知道Android app是运行在(dalvik/art)虚拟机中,而java程序运行在JVM虚拟机中。

虚拟机 出现时机 特点
jvm java 虚拟机
dalvik android 5.0之前 JIT(just in time)
art android 5.0之后 AOT(ahead of time)

1、JVM 架构

dalvik和art都是从jvm演化而来的,我们先从JVM的整体架构看起

image

VJM 整体被分为三个子系统:
(1)类加载器子系统 (2)运行时数据区 (3)执行引擎

1.1、 类加载器子系统

image

Java类的动态加载功能是由类加载器子系统处理。当它在运行时(不是编译时)首次引用一个类时,加载、链接并初始化该类文件。

1.1.1、 类的加载由此组件完成,通常就是我们所说的classloader

加载过程主要完成三件事情:

1.1.3、 链接

类文件被加载完之后,还不能使用,必须要经过链接的过程

1.2、运行时数据区

运行时数据区域被划分为5个主要组件:

1.3、执行引擎

分配给运行时数据区的字节码将由执行引擎执行。
执行引擎读取字节码并逐段执行。

1.3.1、解释器

解释器能快速的解释字节码,但执行却很慢。 解释器的缺点就是,当一个方法被调用多次,每次都需要重新解释。

1.3.2、编译器

JIT编译器消除了解释器的缺点。执行引擎利用解释器转换字节码,但如果是重复的代码则使用JIT编译器将全部字节码编译成本机代码。本机代码将直接用于重复的方法调用,这提高了系统的性能。

1.3.3、垃圾回收器

收集并删除未引用的对象。可以通过调用"System.gc()"来触发垃圾回收,但并不保证会确实进行垃圾回收。

1.3.4、Java本地接口 (JNI)

本地接口的作用是为了融合不同的编程语言为Java所用。它的初衷是为了融合C/C++程序,Java诞生的时候是C/C++横行的时候,要想立足,必须要有一个聪明的、睿智的调用C/C++程序,于是就在内存中专门开辟了一块区域处理标记为native的代码,它的具体做法是Native Method Stack中登记native方法,在Execution Engine执行时加载加载native libraries

小结:ClassLoader存在的意义

编译完的class存储在磁盘上,必须加载到内存,经过链接和初始化之后,才能被使用。ClassLoader 就是负责完成将类从磁盘文件(.class、.dex)加载到内存中的。
java中类是动态加载的,一个类在被使用之前,才从磁盘加载到内存中,经过链接和初始化之后被使用。
出于效率的考虑,系统会提前将一部分常用的类提前加载到内存中,剩下的class 随用随加载(动态加载)

二、类的加载方式

既然ClassLoader是每个类被使用前的必经之路,为什么我们平时基本接触不到ClassLoader呢?

类的状态分为隐式装载和显示装载两种方式。大部分场景都会采用隐式加载的方式来加载class。

一般我们调用new 新建对象时,或调用类的静态属性/方法时 都会触发类的隐式加载。这是有JVM自动完成的,我们感知不到。

隐式加载

new Student()

但是一些特殊情况,我们利用反射来使用类时,就会通过Class.forname()等方法显式加载一个类
显式加载

Class.forname("类的全限定名")
Classloader.loadClass("类的全限定名")

三、Android中的ClassLoader 有哪些?

Android中ClassLoader主要有四个类:ClassLoader、BootClassLoader、BaseDexClassLoader、PathClassLoader、DexClassLoader


image

SecureClassLoader 和URLClassLoader 是将.jar 加载成class,在Android中无法直接加载.jar,所以SecureClassLoader和URLClassLoader 没有用。

3.1、ClassLoader

ClassLoader 是一个抽象类,其他所有ClassLoader的基类

以下是JVM 中ClassLoader的一个介绍,Android虚拟机dalvik/art 是从JVM演进而来的,大概思想是一直的。

ClassLoader主要有四个主要方法
- defineClass(byte[] , int ,int),将byte字节流解析为JVM能够识别的Class对象(直接调用这个方法生成的Class对象还没有resolve,这个resolve将会在这个对象真正实例化时resolve)
- resolveClass(),手动调用这个使得被加到JVM的类被链接
- loadClass() 运行时可以通过调用此方法动态加载一个类
- findClass() 通过类名去加载对应的Class对象,当我们实现自定义的classLoader通常是重写这个方法,根据传入的类名找到对应字节码的文件,并通过调用defineClass解析出Class对象。

我们看一下Android中的ClassLoader

public abstract class ClassLoader{
  @Deprecated
    protected final Class<?> defineClass(byte[] b, int off, int len)
        throws ClassFormatError
    {
        throw new UnsupportedOperationException("can't load this type of class file");
    }


   protected final void resolveClass(Class<?> c) {
    }
    
       @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        return Class.classForName(name, false, null);
    }
    
     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;
    }
}

在Android的ClassLoader中,

##Class.java
@FastNative
static native Class<?> classForName(String className, boolean shouldInitialize,
        ClassLoader classLoader) throws ClassNotFoundException;
 protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
            // First, check if the class has already been loaded
            //(1)查看当前的ClassLoader 有没有加载过此类,如果加载过,则直接返回Class
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                try {
                    if (parent != null) {
                        //(2)当前的ClassLoader未加载过该Class,则交给父ClassLoader去加载clsss
                        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.
                    //(3)最后才是 自己尝试加载class
                    c = findClass(name);
                }
            }
            return c;
    }

3.1.1、双亲代理模式:

双亲代理模式分三个过程:ClassLoader在加载字节码的时候,首先会询问当前的classloader是否已经载 过子类,如果已经加载过,直接返回不再重复加载。如果没有查询parent 是否加载过子类,如果加载过那么返回parent加载过的字节码文件。如果没有加载过,最终再由子classloader去完成真正的加载。

双亲代理模式的优点:

提高类加载的效率.

这个过程的好处就是如果我们一个类被树中任意一个classloader节点加载过,那么我们在以后的整个生命周期中都不会再被去重新的加载,大大提高了类加载的效率。

类加载的共享功能

比如我们的framework层级的类,一旦被我们顶层的classLoader加载过可以缓存在内存里面,以后任何地方用到,都不需要重新加载

类加载隔离功能

由不同Classloader加载的类肯定不是同一个类。可以避免用户自己写一些代码冒充Android系统中的核心类库,举例:一些系统层级的类会在初始化的时候就被加载,如java.lang.string在应用启动之前就被系统加载好的,如果在一个应用里面能够简单的用自己定义的string类把系统的替换掉,那就存在很大的安全问题。

备注:

3.2、BootClassLoader

BootClassLoader 是所有最顶端的ClassLoader,所以其复写了loadClass()方法,没有双亲代理的过程。

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

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        return Class.classForName(name, false, null);
    }


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

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

        return clazz;
    }

    @Override
    public Enumeration<URL> getResources(String resName) throws IOException {
        return findResources(resName);
    }
}

3.3、BaseDexClassLoader

BaseDexClassLoader 是android 中ClassLoader系统中的基石,它实现了类的查找和加载过程,是PathClassLoader和DexClassLoader的基类。

   public BaseDexClassLoader(String dexPath, File optimizedDirectory,
            String librarySearchPath, ClassLoader parent, boolean isTrusted){
                
            }

BaseDexClassLoader的构造函数包含以下几个重要参数

BaseDexClassLoader 是如何在本类中查找类的。

BaseDexClassLoader的findClass()方法 会把查找dex的任务委托给DexPathList类来实现。

public class BaseDexClassLoader extends ClassLoader {
    // 需要加载的dex列表
    private final DexPathList pathList;
    // dexPath要加载的dex文件所在的路径,optimizedDirectory是odex将dexPath
    // 处dex优化后输出到的路径,这个路径必须是手机内部路劲,libraryPath是需要
    // 加载的C/C++库路径,parent是父类加载器对象
    public BaseDexClassLoader(String dexPath, File optimizedDirectory,
            String libraryPath, ClassLoader parent) {
        super(parent);
        this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
        // 使用pathList对象查找name类
        Class c = pathList.findClass(name, suppressedExceptions);
        return c;
    }
}

查看DexPathList的实现代码,在内部会类加载器加载路径查找dex文件,然后将它们解析成Element对象.Element对象代表的是dex文件或资源文件,它里面保存了文件对象。

static class Element {
    private final File file;
    private final boolean isDirectory;
    private final File zip;
    private final DexFile dexFile;

    private ZipFile zipFile;
    private boolean initialized;

    // file文件,是否是目录,zip文件通常都是apk或jar文件,dexFile就是.dex文件
    public Element(File file, boolean isDirectory, File zip, DexFile dexFile) {
        this.file = file;
        this.isDirectory = isDirectory;
        this.zip = zip;
        this.dexFile = dexFile;
    }
}

在DexPathList类构造的时候会首先将dexPath变量内容分隔成多个文件路径,并且根据路径查找Android中的dex和资源文件,将它们解析后存放到Element数组中。

/*package*/ final class DexPathList {
    private static final String DEX_SUFFIX = ".dex";
    private final ClassLoader definingContext;
    // 
    private final Element[] dexElements;
    // 本地库目录
    private final File[] nativeLibraryDirectories;

    public DexPathList(ClassLoader definingContext, String dexPath,
            String libraryPath, File optimizedDirectory) {
        // 当前类加载器的父类加载器
        this.definingContext = definingContext;
        ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();
        // 根据输入的dexPath创建dex元素对象
        this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,
                                           suppressedExceptions);
        if (suppressedExceptions.size() > 0) {
            this.dexElementsSuppressedExceptions =
                suppressedExceptions.toArray(new IOException[suppressedExceptions.size()]);
        } else {
            dexElementsSuppressedExceptions = null;
        }
        this.nativeLibraryDirectories = splitLibraryPath(libraryPath);
    }
}

makeDexElements就是把前面dexPath里面解析到的路径下的文件全部遍历一遍,如果是dex文件或apk和jar文件就会查找它们内部的dex文件,将所有这些dex文件都加入到Element数组中,完成加载路径下面的所有dex解析。

private static Element[] makeDexElements(ArrayList<File> files, File optimizedDirectory,
                                         ArrayList<IOException> suppressedExceptions) {
    ArrayList<Element> elements = new ArrayList<Element>();
    // 所有从dexPath找到的文件
    for (File file : files) {
        File zip = null;
        DexFile dex = null;
        String name = file.getName();
        // 如果是文件夹,就直接将路径添加到Element中
        if (file.isDirectory()) {
            elements.add(new Element(file, true, null, null));
        } else if (file.isFile()){
            // 如果是文件且文件名以.dex结束
            if (name.endsWith(DEX_SUFFIX)) {
                try {
                    // 直接从.dex文件生成DexFile对象
                    dex = loadDexFile(file, optimizedDirectory);
                } catch (IOException ex) {
                    System.logE("Unable to load dex file: " + file, ex);
                }
            } else {
                zip = file;

                try {
                    // 从APK/JAR文件中读取dex文件
                    dex = loadDexFile(file, optimizedDirectory);
                } catch (IOException suppressed) {
                    suppressedExceptions.add(suppressed);
                }
            }
        } else {
            System.logW("ClassLoader referenced unknown path: " + file);
        }

        if ((zip != null) || (dex != null)) {
            elements.add(new Element(file, false, zip, dex));
        }
    }

    return elements.toArray(new Element[elements.size()]);
}

3.4、PathClassLoader

PathClassLoader继承自BaseDexClassLoader,实现也都在BaseDexClassLoader中。PathClassLoader 默认从/data/dalvik-cache 读取dex

public class PathClassLoader extends BaseDexClassLoader {
    public PathClassLoader(String dexPath, ClassLoader parent) {
        super(dexPath, null, null, parent);
    }
    public PathClassLoader(String dexPath, String librarySearchPath, ClassLoader parent) {
        super(dexPath, null, librarySearchPath, parent);
    }
}

Android系统中的ClassLoader 默认都是以PathClassLoader为父ClassLoader。

3.5、DexClassLoader

DexClassLoader可以加载jar/apk/dex,可以从SD卡中加载未安装的apk

    //dexPath :dex路径
    //optimizedDirectory :制定输出dex优化后的odex文件,可以为null
    //libraryPath:动态库路径(将被添加到app动态库搜索路径列表中)
    //parent:制定父类加载器,以保证双亲委派机制从而实现每个类只加载一次。
    public DexClassLoader(String dexPath, String optimizedDirectory,
            String libraryPath, ClassLoader parent) {
        super(dexPath, new File(optimizedDirectory), libraryPath, parent);
    }

3.6、DexClassLoader 与PathClassLoader的区别

DexClassLoader和与PathClassLoader 都是BaseDexClassLoader的子类,主要功能都在BaseDexClassLoader中,DexClassLoader比PathClassLoader 多了一个optimizedDirectory参数。

但是从android-26(8.0)开始 BaseDexClassLoader中optimizedDirectory 被废弃了,没有用了。

  /**
     * Constructs an instance.
     * Note that all the *.jar and *.apk files from {@code dexPath} might be
     * first extracted in-memory before the code is loaded. This can be avoided
     * by passing raw dex files (*.dex) in the {@code dexPath}.
     *
     * @param dexPath the list of jar/apk files containing classes and
     * resources, delimited by {@code File.pathSeparator}, which
     * defaults to {@code ":"} on Android.
     * @param optimizedDirectory this parameter is deprecated and has no effect since API level 26.
     * @param librarySearchPath the list of directories containing native
     * libraries, delimited by {@code File.pathSeparator}; may be
     * {@code null}
     * @param parent the parent class loader
     */
    public BaseDexClassLoader(String dexPath, File optimizedDirectory,
            String librarySearchPath, ClassLoader parent) {
        this(dexPath, librarySearchPath, parent, null, false);
    }

因此 在android-16之前

PathClassLoader只能加载系统安装的apk,而DexClassLoader可以加载jar/apk/dex,可以从SD卡中加载未安装的apk

而在android-26之后 PathClassLoader和DexCassLoader 没有区别,两者都能加载dex,jar和apk,也都能加载sdcard上未安装的apk

3.7、classLoader的继承关系

一个普通android程序启动后,它会用到哪些ClassLoader 谁是谁的父ClassLoader

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        ClassLoader loader = this.getClassLoader();
        while (loader != null){
            Log.d("test","Class_Loader"+loader);
            loader = loader.getParent();
        }


    }

}

输出内容:

2020-12-11 16:41:01.941 8154-8154/com.sogou.iot.testtouch1 D/test: Class_Loaderdalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/com.sogou.iot.testtouch1-sojMcBcZW1_0iU5Fv9sK6g==/base.apk"],nativeLibraryDirectories=[/data/app/com.sogou.iot.testtouch1-sojMcBcZW1_0iU5Fv9sK6g==/lib/arm64, /system/lib64, /vendor/lib64]]]
2020-12-11 16:41:01.941 8154-8154/com.sogou.iot.testtouch1 D/test: Class_Loaderjava.lang.BootClassLoader@847820d

所以一个常规Android应用只会用到两个ClassLoader

PathClassLoader->BootClassLoader

小彩蛋:

S1 AI Recorder:/data/dalvik-cache/arm64 # ls
system@app@ApnSettingsPlugin@ApnSettingsPlugin.apk@classes.art 
system@app@ApnSettingsPlugin@ApnSettingsPlugin.apk@classes.dex 
system@app@ApnSettingsPlugin@ApnSettingsPlugin.apk@classes.vdex 
system@app@Bluetooth@Bluetooth.apk@classes.art                 
system@app@Bluetooth@Bluetooth.apk@classes.dex                 
system@app@Bluetooth@Bluetooth.apk@classes.vdex                
system@app@BluetoothMidiService@BluetoothMidiService.apk@classes.art
system@app@BluetoothMidiService@BluetoothMidiService.apk@classes.dex
system@app@BluetoothMidiService@BluetoothMidiService.apk@classes.vdex
system@app@BrowserXposed@BrowserXposed.apk@classes.art         
system@app@BrowserXposed@BrowserXposed.apk@classes.dex         
system@app@BrowserXposed@BrowserXposed.apk@classes.vdex   
S1 AI Recorder:/data/app/com.sogou.iot.testtouch1-sojMcBcZW1_0iU5Fv9sK6g==/oat/arm64 # ls -l
total 2592
-rw-r--r-- 1 system all_a81   45696 2020-12-11 16:40 base.odex
-rw-r--r-- 1 system all_a81 2602050 2020-12-11 16:41 base.vdex

四、BootClassLoader和PathClassLoader是什么时候加载的。

4.1、 BootClassLoader 最早是在何时被创建的

从Zygote进程开始说起

Zygote进程启动时 会调用ZygoteInit.main()方法,调用preload()方法,最终调会用preloadClasses()

frameworks/base/core/java/com/android/internal/os/ZygoteInit.java


public static void main(String argv[]) {
   ...
        try {
             ...
                preload(bootTimingsTraceLog);
             ... 
        }
    }

preloadClasses()从/system/etc/preloaded-classes文件中读取需要预加载的类,逐个调用Class.forName进行加载。

private static void preloadClasses() {
        final VMRuntime runtime = VMRuntime.getRuntime();
        InputStream is;
        try {
            is = new FileInputStream(PRELOADED_CLASSES);//1
        } catch (FileNotFoundException e) {
            Log.e(TAG, "Couldn't find " + PRELOADED_CLASSES + ".");
            return;
        }
        ...
        try {
            BufferedReader br
                = new BufferedReader(new InputStreamReader(is), 256);//2

            int count = 0;
            String line;
            while ((line = br.readLine()) != null) {//3
                line = line.trim();
                if (line.startsWith("#") || line.equals("")) {
                    continue;
                }
                  Trace.traceBegin(Trace.TRACE_TAG_DALVIK, line);
                try {
                    if (false) {
                        Log.v(TAG, "Preloading " + line + "...");
                    }
                    Class.forName(line, true, null);//4
                    count++;
                } catch (ClassNotFoundException e) {
                    Log.w(TAG, "Class not found for preloading: " + line);
                } 
        ...
        } catch (IOException e) {
            Log.e(TAG, "Error reading " + PRELOADED_CLASSES + ".", e);
        } finally {
            ...
        }

/system/etc/preloaded-classes文件中,预置了一下需要提前加载的类,如下

android.app.ApplicationLoaders
android.app.ApplicationPackageManager
android.app.ApplicationPackageManager$OnPermissionsChangeListenerDelegate
android.app.ApplicationPackageManager$ResourceName
android.app.ContentProviderHolder
android.app.ContentProviderHolder$1
android.app.ContextImpl
android.app.ContextImpl$ApplicationContentResolver
android.app.DexLoadReporter
android.app.Dialog
android.app.Dialog$ListenersHandler
android.app.DownloadManager
android.app.Fragment

Class.forName()首次被调用,对BootClassLoader进行了初始化。
libcore/ojluni/src/main/java/java/lang/Class.java


  @CallerSensitive
    public static Class<?> forName(String name, boolean initialize,
                                   ClassLoader loader)
        throws ClassNotFoundException
    {
        if (loader == null) {
            loader = BootClassLoader.getInstance();//1
        }
        Class<?> result;
        try {
            result = classForName(name, initialize, loader);//2
        } catch (ClassNotFoundException e) {
            Throwable cause = e.getCause();
            if (cause instanceof LinkageError) {
                throw (LinkageError) cause;
            }
            throw e;
        }
        return result;
    }

4.2、PathClassLoader 最早在何时被创建

PathClassLoader的创建也是从Zygote进程说起。
Zygote进程启动SyetemServer进程时会调用ZygoteInit的handleSystemServerProcess方法,

frameworks/base/core/java/com/android/internal/os/ZygoteInit.java

 private static void handleSystemServerProcess(
            ZygoteConnection.Arguments parsedArgs)
            throws Zygote.MethodAndArgsCaller {

    ...
        if (parsedArgs.invokeWith != null) {
           ...
        } else {
            ClassLoader cl = null;
            if (systemServerClasspath != null) {
                cl = createPathClassLoader(systemServerClasspath, parsedArgs.targetSdkVersion);//1
                Thread.currentThread().setContextClassLoader(cl);
            }
            ZygoteInit.zygoteInit(parsedArgs.targetSdkVersion, parsedArgs.remainingArgs, cl);
        }
    }
    
     static PathClassLoader createPathClassLoader(String classPath, int targetSdkVersion) {
      String libraryPath = System.getProperty("java.library.path");
      return PathClassLoaderFactory.createClassLoader(classPath,
                                                      libraryPath,
                                                      libraryPath,
                                                      ClassLoader.getSystemClassLoader(),
                                                      targetSdkVersion,
                                                      true /* isNamespaceShared */);
    }

PathClassLoaderFactory的createClassLoader方法,创建出第一个PathClassLoader

public static PathClassLoader createClassLoader(String dexPath,
                                                    String librarySearchPath,
                                                    String libraryPermittedPath,
                                                    ClassLoader parent,
                                                    int targetSdkVersion,
                                                    boolean isNamespaceShared) {
        PathClassLoader pathClassloader = new PathClassLoader(dexPath, librarySearchPath, parent);
      ...
        return pathClassloader;
    }

由此可知:

  • BootClassLoader 主要加载一些常用的需要预加载的类
  • PathClassLoader 负责Android FrameWork 和App中的类的加载

五、参考文章:

https://www.jianshu.com/p/52c38cf2e3d4

https://blog.csdn.net/itachi85/article/details/78276837

https://blog.csdn.net/renjingjingya0429/article/details/88525915

插件化原理

上一篇下一篇

猜你喜欢

热点阅读