1,编译模式
1.1 概念
在Android早期的版本中,应用程序的运行环境是需要依赖Dalvik虚拟机的。不过,在后来的版本(大概是4.x版本),Android的运行环境却换到了 Android Runtime,其处理应用程序执行的方式完全不同于 Dalvik,Dalvik 是依靠一个 Just-In-Time (JIT) 编译器去解释字节码。
不过,Dalvik模式下,开发者编译后的应用代码需要通过一个解释器在用户的设备上运行,这一机制并不高效,但让应用能更容易在不同硬件和架构上运 行。ART 则完全改变了这套做法,在应用安装时就预编译字节码到机器语言,这一机制叫 Ahead-Of-Time (AOT)编译。在移除解释代码这一过程后,应用程序执行效率更高、启动也更快。
1.2 AOT优点
下面是AOT编译方式的一些优点:
1.2.1 预先编译
ART 引入了预先编译机制,可提高应用的性能。ART 还具有比 Dalvik 更严格的安装时验证。在安装时,ART 使用设备自带的 dex2oat 工具来编译应用。该实用工具接受 DEX 文件作为输入,并为目标设备生成经过编译的应用可执行文件,该工具能够顺利编译所有有效的 DEX 文件。
1.2.2 垃圾回收优化
垃圾回收 (GC) 可能有损于应用性能,从而导致显示不稳定、界面响应速度缓慢以及其他问题。ART模式从以下几个方面优化了垃圾回收的策略:
- 只有一次(而非两次)GC 暂停
- 在 GC 保持暂停状态期间并行处理
- 在清理最近分配的短时对象这种特殊情况中,回收器的总 GC 时间更短
- 优化了垃圾回收的工效,能够更加及时地进行并行垃圾回收,这使得 GC_FOR_ALLOC 事件在典型用例中极为罕见
- 压缩 GC 以减少后台内存使用和碎片
1.2.3 开发和调试方面的优化
支持采样分析器 一直以来,开发者都使用 Traceview 工具(用于跟踪应用执行情况)作为分析器。虽然 Traceview 可提供有用的信息,但每次方法调用产生的开销会导致 Dalvik 分析结果出现偏差,而且使用该工具明显会影响运行时性能ART 添加了对没有这些限制的专用采样分析器的支持,因而可更准确地了解应用执行情况,而不会明显减慢速度。支持的版本从KitKat (4.4)版本开始,为 Dalvik 的 Traceview 添加了采样支持。
支持更多调试功能 ART 支持许多新的调试选项,特别是与监控和垃圾回收相关的功能。例如,查看堆栈跟踪中保留了哪些锁,然后跳转到持有锁的线程;询问指定类的当前活动的实例数、请求查看实例,以及查看使对象保持有效状态的参考;过滤特定实例的事件(如断点)等。
优化了异常和崩溃报告中的诊断详细信息 当发生运行时异常时,ART 会为您提供尽可能多的上下文和详细信息。ART 会提供 java.lang.ClassCastException、java.lang.ClassNotFoundException 和 java.lang.NullPointerException 的更多异常详细信息(较高版本的 Dalvik 会提供 java.lang.ArrayIndexOutOfBoundsException 和 java.lang.ArrayStoreException 的更多异常详细信息,这些信息现在包括数组大小和越界偏移量;ART 也提供这类信息)。
1.3 垃圾回收
ART 提供了多个不同的 GC 方案,这些方案运行着不同垃圾回收器,默认的GC方案是 CMS(并发标记清除),主要使用粘性 CMS 和部分 CMS。粘性 CMS 是 ART 的不移动分代垃圾回收器。它仅扫描堆中自上次 GC 后修改的部分,并且只能回收自上次 GC 后分配的对象。除 CMS 方案外,当应用将进程状态更改为察觉不到卡顿的进程状态(例如,后台或缓存)时,ART 将执行堆压缩。
除了新的垃圾回收器之外,ART 还引入了一种基于位图的新内存分配程序,称为 RosAlloc(插槽运行分配器)。此新分配器具有分片锁,当分配规模较小时可添加线程的本地缓冲区,因而性能优于 DlMalloc(内存分配器)。
内存分配器的相关知识可以参考:内存分配器
同时,与 Dalvik 相比,ART的 CMS垃圾回收也带来了其他方面的改善,如下:
-
与 Dalvik 相比,暂停次数从 2 次减少到 1 次。Dalvik 的第一次暂停主要是为了进行根标记,即在 ART 中进行并发标记,让线程标记自己的根,然后马上恢复运行。
-
与 Dalvik 类似,ART GC 在清除过程开始之前也会暂停 1 次。两者在这方面的主要差异在于:在此暂停期间,某些 Dalvik 环节在 ART 中并发进行。这些环节包括 java.lang.ref.Reference 处理、系统弱清除(例如,jni 弱全局等)、重新标记非线程根和卡片预清理。在 ART 暂停期间仍进行的阶段包括扫描脏卡片以及重新标记线程根,这些操作有助于缩短暂停时间。
-
相对于 Dalvik,ART GC 改进的最后一个方面是粘性 CMS 回收器增加了 GC 吞吐量。不同于普通的分代 GC,粘性 CMS 不移动。系统会将年轻对象保存在一个分配堆栈(基本上是 java.lang.Object 数组)中,而非为其设置一个专属区域。这样可以避免移动所需的对象以维持低暂停次数,但缺点是容易在堆栈中加入大量复杂对象图像而使堆栈变长。
ART GC 与 Dalvik 的另一个主要区别在于 ART GC 引入了移动垃圾回收器。使用移动 GC 的目的在于通过堆压缩来减少后台应用使用的内存。目前,触发堆压缩的事件是 ActivityManager 进程状态的改变。当应用转到后台运行时,它会通知 ART 已进入不再“感知”卡顿的进程状态。此时 ART 会进行一些操作(例如,压缩和监视器压缩),从而导致应用线程长时间暂停。
目前,Android的ART正在使用的两个移动 GC 是同构空间压缩和半空间压缩,它们的区别如下:
- 半空间压缩:将对象在两个紧密排列的碰撞指针空间之间进行移动。这种移动 GC 适用于小内存设备,因为它可以比同构空间压缩稍微多节省一点内存,额外节省出的空间主要来自紧密排列的对象,这样可以避免 RosAlloc/DlMalloc 分配器占用开销。
- 同构空间压缩通过将对象从一个 RosAlloc 空间复制到另一个 RosAlloc 空间来实现。这有助于通过减少堆碎片来减少内存使用量。这是目前非低内存设备的默认压缩模式。相比半空间压缩,同构空间压缩的主要优势在于应用从后台切换到前台时无需进行堆转换。
2,类加载器
2.1 类加载器分类
目前,Android的类加载器从下到上主要分为BootstrapClassLoader(根类加载器)、 ExtensionClassLoader (扩展类加载器)和 AppClassLoader(应用类加载器)三种。
- 根类加载器:该加载器没有父加载器。它负责加载虚拟机的核心类库,如java.lang.*等。例如java.lang.Object就是由根类加载器加载的。根类加载器从系统属性sun.boot.class.path所指定的目录中加载类库。根类加载器的实现依赖于底层操作系统,属于虚拟机的实现的一部分,它并没有继承java.lang.ClassLoader类。
- 扩展类加载器:它的父加载器为根类加载器。它从java.ext.dirs系统属性所指定的目录中加载类库,或者从JDK的安装目录的jre/lib/ext子目录(扩展目录)下加载类库,如果把用户创建的JAR文件放在这个目录下,也会自动由扩展类加载器加载。扩展类加载器是纯Java类,是java.lang.ClassLoader类的子类。
- 系统类加载器:也称为应用类加载器,它的父加载器为扩展类加载器。它从环境变量classpath或者系统属性java.class.path所指定的目录中加载类,它是用户自定义的类加载器的默认父加载器。系统类加载器是纯Java类,是java.lang.ClassLoader类的子类。
父子加载器并非继承关系,也就是说子加载器不一定是继承了父加载器。
2.2 双亲委托模式
所谓双亲委托模式,指的是某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,依次递归,如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务时,才自己去加载。
因为这样可以避免重复加载,当父亲已经加载了该类的时候,就没有必要子 ClassLoader 再加载一次。如果不使用这种委托模式,那我们就可以随时使用自定义的类来动态替代一些核心的类,存在非常大的安全隐患。
举个例子,事实上,java.lang.String这个类并不会被我们自定义的classloader加载,而是由bootstrap classloader进行加载,为什么会这样?实际上这就是双亲委托模式的原因,因为在任何一个自定义ClassLoader加载一个类之前,它都会先 委托它的父亲ClassLoader进行加载,只有当父亲ClassLoader无法加载成功后,才会由自己加载。
2.3 Android的类加载器
下面是Android类加载器的模型图: 下面看一下DexClassLoader,DexClassLoader 重载了 findClass 方法,在加载类时会调用其内部的 DexPathList 去加载。DexPathList 是在构造 DexClassLoader 时生成的,其内部包含了 DexFile,涉及的源码如下。
···
public Class findClass(String name) {
for (Element element : dexElements) {
DexFile dex = element.dexFile;
if (dex != null) {
Class clazz = dex.loadClassBinaryName(name, definingContext);
if (clazz != null) {
return clazz;
}
}
}
return null;
}
···
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15