• 如果您觉得本站非常有看点,那么赶紧使用Ctrl+D 收藏吧

Android虚拟机与ClassLoader类加载

互联网 diligentman 7天前 8次浏览

一、ART 和 Dalvik

JVM与Dalvik

Android应用程序运行在Dalvik/ART虚拟机,并且每一个应用程序对应有一个单独的Dalvik虚拟机实例。Dalvik虚拟机实则也算是一个Java虚拟机,只不过它执行的不是class文件,而是dex文件。

Dalvik虚拟机与Java虚拟机共享有差不多的特性,差别在于两者执行的指令集是不一样的,前者的指令集是基本寄存器的,而后者的指令集是基于堆栈的。
Android虚拟机与ClassLoader类加载

基于栈的虚拟机

对于基于栈的虚拟机来说,每一个运行时的线程,都有一个独立的栈。栈中记录了方法调用的历史,每有一次方法调用,栈中便会多一个栈桢。最顶部的栈桢称作当前栈桢,其代表着当前执行的方法。基于栈的虚拟机通过操作数栈进行所有操作。
Android虚拟机与ClassLoader类加载

字节码指令

Android虚拟机与ClassLoader类加载
ICONST_1 : 将int类型常量1压入操作数栈;

ISTORE 0 : 将栈顶int类型值存入局部变量0;

IADD : 执行int类型的加法 ;

寄存器

寄存器是CPU的组成部分。寄存器是有限存贮容量的高速存贮部件,它们可用来暂存指令、数据和位址。
Android虚拟机与ClassLoader类加载

基于寄存器的虚拟机

基于寄存器的虚拟机中没有操作数栈,但是有很多虚拟寄存器。其实和操作数栈相同,这些寄存器也存放在运行时栈中,本质上就是一个数组。与JVM相似,在Dalvik VM中每个线程都有自己的PC和调用栈,方法调用的活动记录以帧为单位保存在调用栈上。
Android虚拟机与ClassLoader类加载
与JVM版相比,可以发现Dalvik版程序的指令数明显减少了,数据移动次数也明显减少了

没有了jvm虚拟机中的局部变量变,没有了数据在局部变量表和操作数栈之间过来过去。做的事情少,移动次数少。相当于把局部变量表和操作数栈合并了。

ART与Dalvik

Dalvik虚拟机执行的是dex字节码,解释执行。从Android 2.2版本开始,支持JIT即时编译(Just In Time)
在程序运行的过程中进行选择热点代码(经常执行的代码)进行编译或者优化

Dalvik是解释执行结合即时执行

运行时编译,编译成机器码,字节码还是需要虚拟机的翻译,翻译成机器能跑的机器码,多一个翻译的过程,本地的机器码,是可以直接在机器跑的。

而ART(Android Runtime) 是在 Android 4.4 中引入的一个开发者选项,也是 Android 5.0 及更高版本的默认 Android 运行时ART虚拟机执行的是本地机器码。Android的运行时从Dalvik虚拟机替换成ART虚拟机,并不要求开发者将自己的应用直接编译成目标机器码,APK仍然是一个包含dex字节码的文件。

ART看作一个Dalvik升级版本的虚拟机

dex2oat

那么,ART虚拟机执行的本地机器码是从哪里来?
安装的时候过来的
安装的时候执行dexopt的操作把dex文件从apk中提取出来变成Odex文件
ART的步骤变了,安装的时候有一个dex2oat的操作,把字节码编译成当前跑的机器的对应的机器码,所以5.0,6.0安装的时候会变慢

aot和jit对应(前者是运行之中编译,后者运行之前编译)

Dalvik下应用在安装的过程,会执行一次优化,将dex字节码进行优化生成odex文件。而Art下将应用的dex字节码翻译成本地机器码的最恰当AOT时机也就发生在应用安装的时候。ART 引入了预先编译机制(Ahead Of Time),在安装时,ART 使用设备自带的 dex2oat 工具来编译应用,dex中的字节码将被编译成本地机器码。
Android虚拟机与ClassLoader类加载
到了7.0,8.0,9.0之后又变了没有那么慢了,变成了混编

Android N的运作方式

ART 使用预先 (AOT) 编译,并且从 Android N混合使用AOT编译,解释和JIT。 混合了这三个

1、最初安装应用时不进行任何 AOT 编译(安装又快了),运行过程中解释执行,对经常执行的方法进行JIT,经过 JIT 编译的方法将会记录到Profile配置文件中。

2、当设备闲置和充电时,编译守护进程会运行,根据Profile文件对常用代码进行 AOT 编译。待下次运行时直接使用
Android虚拟机与ClassLoader类加载
.art机器码文件
先判断类在不在art中,在的话就不去dex中找了直接执行,如果不在就去dex中找然后解释执行,像是缓存

JIT编译的代码只对这次运行有效

Dalvik的dex就是dex
ART的odex就变成了机器码

二、ClassLoader

Android虚拟机与ClassLoader类加载

Android虚拟机与ClassLoader类加载
InMemoryDexClassLoader是andorid 8.0出现的
主要了解PathClassLoader和DexClassLoader

还有一个BootClassLoader ,用于加载Android Framework层class文件
String.class.getClassLoader() 就能得知String这个类被BootClassLoader 加载
Activity也是BootClassLoader 加载
AppCompatActivity不是系统的类,手机里没有这个类,是引用来的第三方的类,android官方开发的类,是PathClassLoader加载的

class BootClassLoader extends ClassLoader {}

PathClassLoader 是Android 应用程序类加载器,整个程序的类加载器,任意的上下文都可以调用getClassLoader()方法,得到的是整个程序的ClassLoader就是PathClassLoader

系统帮我们做了加载类

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

责任链设计模式
PathClassLoader ——-》parent:BootClassLoader

不是父类BaseDexClassLoader,而是PathClassLoader 里面的parent成员是BootClassLoader

PathClassLoader 加载自己的类
BootClassLoader 加载系统的类

双亲委托机制

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

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

2、安全性考虑,防止核心API库被随意篡改。
先用c = parent.loadClass(name, false); BootClassLoader去找,找到就直接去用了

findClass

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        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;
    }

Class c = pathList.findClass(name, suppressedExceptions);
pathList

private final DexPathList pathList;

构造方法实例化的

    public BaseDexClassLoader(ByteBuffer[] dexFiles, ClassLoader parent) {
        // TODO We should support giving this a library search path maybe.
        super(parent);
        this.pathList = new DexPathList(this, dexFiles);
    }

DexPathList类:
一个dex就是一个Element对象,可能多个dex,所以数组

private Element[] dexElements;

pathList.findClass

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

流程:找自己的类通过PathClassLoader
但是PathClassLoader没有重新findClass
他的父类重写了
Android虚拟机与ClassLoader类加载
apk文件里面的dex文件
Android虚拟机与ClassLoader类加载

三、热修复

bug的Demo类 修复后放到前面,在前面找到后就不管后面的了
热修复要保证类没有被加载过没有被使用过,再执行热修复,插入补丁包,加载过就有缓存了就不会从dex中找了,所以必须在用之前修复
每次启动都要热修复
Android虚拟机与ClassLoader类加载
怎么查到数组中去,要用反射

热修复流程:
1.获取到当前应用的PathClassLoader
2.反射获取到DexPathList属性对象pathList
3.反射修改pathList的dexElements
    a.把补丁包patch.dex转化为Element[ ](patch)
    b.获取pathList的dexElements属性(old)
    c.patch+old合并,并反射赋值给pathList的dexElements

Android虚拟机与ClassLoader类加载


程序员灯塔
转载请注明原文链接:Android虚拟机与ClassLoader类加载
喜欢 (0)