虚拟机与ClassLoader

ART与Dalvik

什么是Dalvik

Dalvik是Google公司自己设计用于Android平台的Java虚拟机。支持已转换为.dex(Dalvik Excutable)格式的Java应用程序的运行,.dex格式是专为Dalvik应用设计的一种压缩格式,适合内存和处理器速度有限的系统。
JVM的指令集是基于栈的(通用性更好、即跨平台性更好)
Dalvik的指令集是基于寄存器的(执行效率更好)。

什么是ART

Android Runtime,Android4.4中引入的一个开发者选项,也是Android5.0及更高版本的默认模式。在应用安装的时候Ahead-Of-Time(AOT)预编译字节码到机器语言,这一机制叫Ahead-Of-Time(AOT)预编译。应用程序安装会变慢,但是执行将更有效率,启动更快。

  • 在Dalvik下,应用运行需要解释执行,常用热点代码通过即时编译器(JIT)将字节码转换为机器码,安装慢了,但执行效率会提高
  • JIT:让频繁需要运行的代码(热点代码),在运行时编译成机器码。
  • ART占用空间比Dalvik大(字节码变成机器码),空间换时间
  • 预编译也可以明显改善电池续航,因为应用程序每次运行时不用重复编译了,从而减少了CPU的使用频率,降低了能耗。

Dexopt与DexAot

ART会执行AOT,但针对Dalvik开发的应用也能在ART环境中运作

dexopt(针对Dalvik)

对dex文件进行验证和优化为odex(Optimized dex)文件
.dex --dexopt--> .odex

dex2oat(针对ART)

在安装时对dex文件执行dexopt优化之后再将odex进行AOT提前编译操作,编译为OAT可执行文件(机器码)。
.dex --dex2oat--> .oat(ELF File)

编译优化

ClassLoader

Java类加载器

Java中的ClassLoader继承关系

Android类加载器

Android中的ClassLoader的继承关系
  • BootClassLoader
    用于加载Android Framework层的class文件,如Activity.class等
//BootClassLoader
ClassLoader classLoader = Activity.class.getClassLoader();
  • PathClassLoader
    用于Android应用程序类加载器。可以加载指定的dex,以及jar、zip、apk中的classes.dex。例如:自己写的代码都是PathClassLoader加载的。
//PathClassLoader
ClassLoader classLoader = MainActivity.this.getClassLoader();
public PathClassLoader(String dexPath, String libraryPath, ClassLoader parent){
    super(dexPath, null, libraryPath, parent);
}
  • DexClassLoader
    加载指定的dex,以及jar、zip、apl中的classes.dex。Android中并没有使用这个类加载器。
public DexClassLoader(String dexPath, String optimizedDirectory, String libraryPath, ClassLoader parent){
    super(dexPath, new File(optimizedDirectory), libraryPath, parent);
}
  • 与PathClassLoader的唯一区别就是多了个optimizedDirectory,意为加载dex时会进行优化,并保存在优化路径下。
    而PathClassLoader也会优化,会存到/data/dalvik-cache
  • optimizedDirectory参数不能传sdcard的目录,只能传data/data/packagename 这样的私有目录,建议使用context.getCodeCacheDir()
  • 网上有博客的错误引导,其实PathClassLoader和DexClassLoader功能相同,区别唯一,并不是PathClassLoader只能加载已安装的apk的dex文件。
  • inMemoryDexClassLoader
    Android 8.0后加入的类加载器,用于加载内存中的dex文件。

双亲委托机制

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

注意:是父·类加载器,不是父类·加载器,也就是说,是ClassLoader类中的parent属性(ClassLoader是类加载的终极祖先父类,所以它有这个属性,就代表所有子子孙孙有这个属性可以用来判断父·类加载器存不存在),而不是类定义中的父类(例如PathClassLoader、DexClassLoader的父类是BaseDexClassLoader)。

protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException {
    // First, check if the class has already been loaded
    //(1)先判断这个类是否被加载过
    Class<?> c = findLoadedClass(name);
    if (c == null) {
        try {
            //(2)如果没被加载,先让父加载器进行加载
            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
        }
        //(3)如果父加载器加载失败,则自身进行加载
        if (c == null) {
            // If still not found, then invoke findClass in order
            // to find the class.
            c = findClass(name);
        }
    }
    return c;
}

总结

  1. 除了顶层的类加载器外,其他的类加载器都有自己的父类加载器,在加载类时首先判断这个类是否被加载过,如果已经加载则直接返回。
  2. 如果未被加载过,则先尝试让父加载器进行加载,最终所有加载请求都会传递给顶层的加载器中。
  3. 当父加载器发现未找到所需的类而无法完成加载请求时,子加载器的findClass方法中进行加载。
    4.父类加载是赋值一般在构造函数中传入。

Android加载Class的过程

BaseDexClassLoader的findClass

public BaseDexClassLoader(String dexPath, File optimizedDirectory, String librarySearchPath, ClassLoader parent){
    super(parent);
    //pathList是在构造函数中new出来的
    this.pathList = new DexPathList(this, dex, librarySearchPath, null);
    
    if(reporter != null){
        reporter.report(this.pathList.getDexPath());
    }
}

@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
    // First, check whether the class is present in our shared libraries.
    if (sharedLibraryLoaders != null) {
        for (ClassLoader loader : sharedLibraryLoaders) {
            try {
                return loader.loadClass(name);
            } catch (ClassNotFoundException ignored) {
            }
        }
    }
    // Check whether the class in question is present in the dexPath that
    // this classloader operates on.
    List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
    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;
}

内部调用了DexPathList的findClass方法

#DexPathList
private Element[] dexElements;
......

DexPathList(ClassLoader definingContext, String dexPath, String librarySearchPath, File optimizedDirectory, boolean isTrusted) {
        ......
        this.definingContext = definingContext;

        ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();
        // save dexPath for BaseDexClassLoader
        this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,                         suppressedExceptions, definingContext, isTrusted);

        this.nativeLibraryDirectories = splitPaths(librarySearchPath, false);
        this.systemNativeLibraryDirectories =
                splitPaths(System.getProperty("java.library.path"), true);
        List<File> allNativeLibraryDirectories = new ArrayList<>(nativeLibraryDirectories);
        allNativeLibraryDirectories.addAll(systemNativeLibraryDirectories);

        this.nativeLibraryPathElements = makePathElements(allNativeLibraryDirectories);

        if (suppressedExceptions.size() > 0) {
            this.dexElementsSuppressedExceptions =
                suppressedExceptions.toArray(new IOException[suppressedExceptions.size()]);
        } else {
            dexElementsSuppressedExceptions = null;
        }
    }



......
public Class<?> findClass(String name, List<Throwable> suppressed) {
        for (Element element : dexElements) {
            Class<?> clazz = element.findClass(name, definingContext, suppressed);
            if (clazz != null) {
                return clazz;
            }
        }

        if (dexElementsSuppressedExceptions != null) {
            suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
        }
        return null;
    }

  • dexElements是什么
    是一个Element数组,Element中包含一个DexFile
    DexFile就代表一个Dex文件,里面的native(C/C++)函数 来进行Dex的加载工作
  • dexElements是在构造函数中创建的:通过遍历传入的file列表,判断.dex结尾,通过loadDexFile(file, optimizedDirectory)方法,
  • 类加载的过程,就是遍历dexElements这个数组调用了DexFileloadClassBinaryName方法,最终调用native方法defineClassNative
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,616评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,020评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,078评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,040评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,154评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,265评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,298评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,072评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,491评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,795评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,970评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,654评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,272评论 3 318
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,985评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,223评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,815评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,852评论 2 351

推荐阅读更多精彩内容