Android虚拟机与类加载机制

一.Android虚拟机

1.初代虚拟机Dalvik

与JVM的不同?

①Dalvik执行dex文件

DVM也是实现了JVM规范的一个虚拟器,默认使用CMS垃圾回收器(这篇文章中有讲解CMS),但是与JVM运行 Class 字节码不同,DVM执行的是Dex文件。
那么class文件和dex文件的区别是什么呢?
可以这么说:

  • 对于class:one File,one Class
  • 对于dex:one File,many classes
②DVM的指令集是基于寄存器的

除了处理的文件不同,还有一点不同是:DVM的指令集是基于寄存器的,JVM的指令集是基于堆栈的。
说的清楚点,就是把JVM的局部变量表和操作数栈合并为了一个虚拟寄存器。如图
同样的一段程序
在这里插入图片描述

在这里插入图片描述
上图是JVM,基于堆栈。下图是DVM,基于寄存器
在这里插入图片描述
与JVM相比,DVM程序的指令数明显减少,数据移动次数也明显减少了。
其他的地方和JVM,基本相同,比如一个线程有一个栈,一个方法是一个栈帧等等。

2.ART

ART是Dalvik的升级版。在了解两者的区别之前,我先详细介绍下Dalvik

①详细介绍下Dalvik

Dalvik虚拟机执行的是dex字节码,解释执行,从Android2.2开始支持JIT即时编译。

JIT是什么意思呢?可以这么理解,它会找到一次程序执行时执行次数比较多的代码(比如循环),将其直接翻译成机器码,供本手机直接执行,而不用再通过字节码交给虚拟机执行了。我画了一幅图,可以帮助理解(图中Dalvid应该是Dalvik,在这里纠正一下)
在这里插入图片描述
值得一提的是。JIT只是这次运行才有效,下一次执行就另外一回事,所以这也是Android N的虚拟机产生的原因之一

以上是对Dalvik的介绍

②详细介绍下ART

ART是Android5.0之后的默认虚拟机。它执行的直接就是本地机器码,而不是dex文件。这个机器码是程序(APK文件)安装的时候翻译出来的。也就是预先编译机制(AOT),与JIT机制对应的。所以在那个时期,应用的安装都比较慢。

Dalvik下应用在安装的过程,会执行一次优化,将dex字节码进行优化生成odex文件。而ART下将应用的dex字节码翻译成本地机器码的最恰当AOT时机也就发生在应用安装的时候。ART引入了预先编译机制(Ahead Of Time),在安装时,ART使用设备自带的dex2oat 工具来编译应用,dex中的字节码将被编译成本地机器码。
在这里插入图片描述

3.Android N的运作方式

从Android N之后,虚拟机又变了,它将解释执行,JIT,AOT编译混合使用了。具体是什么情况呢?

  • ①最初安装应用时不进行任何AOT编译(安装又快了),运行过程中解释执行,对经常执行的方法进行JIT,经过JIT编译的方法将会记录到Profile配置文件中。
  • ②当设备闲置和充电时,编译守护进程会运行,根据Profile文件对常用代码进行AOT编译。待下次运行时直接使用。

那么有人会问了,JIT不是都编译了吗,为啥AOT还要编译一次呢?我认为有两个原因,一个是JIT不能持久化,只是针对一次程序运行。再一个是记录到配置文件中的是方法的信息,而不是方法本身。我认为在AOT编译之前要根据这些信息找到这些类,然后再编译成机器码。

二.Android类加载器

1.介绍

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

2.Android中的类加载器

有几个比较重要的类。分别是

  • BootclassLoader
    用于加载Android Framework层class文件。比如String,Activity类的加载
  • PathclassLoader
    用于Android应用程序类加载器。可以加载指定的dex,以及jar、zip、apk中的classes.dex。我们自己写的类一般都是被这个加载器加载的
  • DexclassLoader
    用于加载指定的dex,以及jar、zip、apk中的classes.dex

他们之间的关系是这样的
在这里插入图片描述

3.双亲委托机制

让我们看一下一个类是怎么被加载的。我们直接进入PathClassLoaderloadClass方法看

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

在这里插入图片描述

①首先从缓存里面找

看看这个类是否已经加载过了。因为类加载需要IO等一些比较耗时的操作,所以能从缓存中加载就从缓存中找。

②如果缓存中没有,就从父类加载器中找。

在这里插入图片描述

需要注意的一点是,此父非彼父,之前我们说的父类是继承关系的父类,也就是PathClassLoader的父类BaseDexClassLoader。但是这里的parent不是BaseDexClassLoader对象,如图,是parent的定义
在这里插入图片描述
这个parent是父类加载器,是BootClassLoader。在PathClassLoader构建的时候有一个构造方法,传进来BootClassLoader对象,然后给parent赋的值。
如图
在这里插入图片描述

  • 这就好比是父亲和师傅。自己的身体是父亲给的,但是拜师学艺后基本与父亲断了联系,遇到问题先找师傅,师傅也如同父亲一样。遇到问题先问问师傅有没有遇到过,如果师傅可以处理的话就不用自己处理了,如果师傅不能处理,就得靠自己了。

具体到这里的程序就是遇到一个类,先问问师傅(parent)有没有加载过,或者师傅能不能加载了,如果师傅能加载就不用本人加载,如果师傅做不到就自己出马。(这里,本人是PathClassLoader,父亲是其父类BaseDexClassLoader,师傅是BootClassLoaderBootClassLoader的对象在PathClassLoader中的命名是parent。)
先看看parent是否可以加载,不行的话再自己加载。

这就是双亲委托机制。


双亲委托机制的好处?
  • 1、避免重复加载,当父加载器已经加载了该类的时候,就没有必要子ClassLoader再加载一次。
    比如String这个类,像这种核心类,父类加载器也就是BootClassLoader已经加载过了,PathClassLoader就没必要再加载一次了。
  • 2、安全性考虑,防止核心API库被随意篡改。
    比如我们自己也弄一个String类,它属于我们自己定义的javalang包下,而不是JDK里面的,如图
    在这里插入图片描述

如果我们有双亲委托机制的话,就会加载JDK里面的String,能正常使用,如果没有双亲委托机制,那么我们就会用自己定义的这个String来替换掉系统的String,也就是核心API库被随意篡改了


OK,让我们继续。

③父类加载器不行,就自己加载

自己加载就是执行findClass方法,这里的findClass方法在PathClassLoader的父类中有,根据多态,也是执行的父类的findClass方法,让我们进入看一下
在这里插入图片描述
继续找pathList对象,定义在这里
在这里插入图片描述
那么这个DexPathList是啥呢?进入其构造方法中看一下
在这里插入图片描述
啥也不管,就看红框的部分。发现它给dexElements成员赋了值

值得一提的是splitDexPath方法。dexPath可能传入的是/a/a.dex;/a/b.dexsplitDexPath方法就是把这个字符串以分号隔开,分成一个List集合,本例就是分成含/a/a.dex/a/b.dex两个元素的List集合,可以理解为一个元素就是一个dex文件
dexElements是一个数组,也可以理解为一个元素就是一个dex文件
在这里插入图片描述

我们知道了DexPathList是啥(主要是里面的dexElements数组),然后再看它的findClass方法,因为是调用了它的findClass方法
在这里插入图片描述
发现它是从Element数组里面一个一个地取出dexfor循环),然后完成加载。

用张图总结就是

在这里插入图片描述
PathClassLoader中没有实现findClass方法,所以从其父类BaseDexClassLoader中找findClass方法,发现它调用了DexPathList类的findClass方法,所以进去看DexPathList类的findClass方法,发现它是从dexElements数组中以循环的形式一个一个地取出dex文件然后进行加载。dexElements是在DexPathList对象构建的时候就已经赋值了(也就是在构造方法中)

三.热修复原理

学习了上面的知识后,就很容易明白热修复的原理了。

比如我的某一个类出bug了

在这里插入图片描述

那么我可以写出更改后的class文件,将其打包成dex文件作为补丁包,如图在这里插入图片描述然后再把它插到dexElements数组的最前面,如图

在这里插入图片描述

这样的话,系统会先加载前面的Demo1.class,等到后面加载到有Bug的Demo1.class的时候,会发现前面已经加载过一次Demo1.class了,所以就不会加载这次有Bug的Demo1.class了,这就完成了热修复。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值