JVM与Dalvik

Android应用程序运行在Dalvik/ART虚拟机,并且每一个应用程序对应有一个单独的Dalvik虚拟机实例。Dalvik虚拟机实则也算是一个Java虚拟机,只不过它执行的不是class文件,而是dex文件。Dalvik虚拟机与Java虚拟机共享有差不多的特性,差别在于两者执行的指令集是不一样的,前者的指令集是基本寄存器的,而后者的指令集是基于堆栈的。

那什么是基于栈的虚拟机,什么又是基于寄存器的虚拟机?

基于栈的虚拟机

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

字节码指令

ICONST_1 : 将int类型常量1压入操作数栈;

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

IADD : 执行int类型的加法 ;

基于栈的虚拟机在执行方法时,需要将数据读取到操作数栈,然后再将数据写入栈帧中的局部变量表等操作。

执行过程

(0)PC计数器指向0行,执行ICONST_1指令,将常量1压入操作数栈中

(1)执行ISTORE_0指令,将操作数栈的栈顶元素存储到局部变量表的第1个位置

(2)执行ICONST_2指令,加载数字2到操作数栈

(3)执行ISTORE_1将操作数栈中的数据2存储到局部变量表的第2个位置

(4)执行ILOAD 0将局部变量表中的第1个位置的数字加载到操作数栈中

(5)执行ILOAD 1将局部变量表中的第2个位置的数字加载到操作数栈中

(6)执行IADD 将操作数栈的栈顶的两个数相加,并将所得结果压如栈顶

(7)将操作数栈顶的数据存储到局部变量表的第3个位置

(8)执行RETUREN指令,方法结束。

基于寄存器的虚拟机

寄存器是CPU的组成部分。寄存器是有限存贮容量的高速存贮部件,它们可用来暂存指令、数据和位址

寄存器

基于寄存器的虚拟机中没有操作数栈,但是有很多虚拟寄存器。其实和操作数栈相同,这些寄存器也存放在运行时栈中,本质上就是一个数组。与JVM相似,在Dalvik VM中每个线程都有自己的PC和调用栈,方法调用的活动记录以帧为单位保存在调用栈上。

与JVM版相比,可以发现Dalvik版程序的指令数明显减少了,数据移动次数也明显减少了。

ART与Dalvik

Dalvik虚拟机执行的是dex字节码,解释执行。从Android 2.2版本开始,支持JIT即时编译(Just In Time)

在程序运行的过程中进行选择热点代码(经常执行的代码)进行编译或者优化。

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

那么,ART虚拟机执行的本地机器码是从哪里来?

dex2aot

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

这种在安装时进行编译的操作势必会影响应用的安装速度。

Android N的运作方式

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

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

2、当设备闲置和充电时,编译守护进程会运行,根据Profile文件对常用代码进行 AOT 编译。待下次运行时直接使用。

ClassLoader类加载

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

class Class { ...

private transient ClassLoader classLoader; ...

}

ClassLoader是一个抽象类,而它的具体实现类主要有:

BootClassLoader

用于加载Android Framework层class文件。

PathClassLoader

用于Android应用程序类加载器。可以加载指定的dex,以及jar、zip、apk中的classes.dex

DexClassLoader

用于加载指定的dex,以及jar、zip、apk中的classes.dex

Log.e(TAG, "Activity.class 由:" + Activity.class.getClassLoader() +" 加载"); Log.e(TAG, "MainActivity.class 由:" + getClassLoader() +" 加载");

//输出:

Activity.class 由:java.lang.BootClassLoader@d3052a9 加载

MainActivity.class 由:dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/com.enjoy.enjoyfix-1/base.apk"],nativeLibraryDirectories= [/data/app/com.enjoy.enjoyfix-1/lib/x86, /system/lib, /vendor/lib]]] 加载

它们之间的关系如下:

PathClassLoader 与 DexClassLoader 的共同父类是 BaseDexClassLoader 。

public class DexClassLoader extends BaseDexClassLoader {

public DexClassLoader(String dexPath, String optimizedDirectory,

String librarySearchPath, ClassLoader parent) {

super(dexPath, new File(optimizedDirectory), librarySearchPath, parent);

} }

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

} }

可以看到两者唯一的区别在于:创建 DexClassLoader 需要传递一个 optimizedDirectory 参数,并且会将其创建 为 File 对象传给 super ,而 PathClassLoader 则直接给到null。因此两者都可以加载指定的dex,以及jar、 zip、apk中的classes.dex

PathClassLoader pathClassLoader = new PathClassLoader("/sdcard/xx.dex", getClassLoader());

File dexOutputDir = context.getCodeCacheDir();

DexClassLoader dexClassLoader = new DexClassLoader("/sdcard/xx.dex",dexOutputDir.getAbsolutePath(), null,getClassLoader());

其实, optimizedDirectory 参数就是dexopt的产出目录(odex)。那 PathClassLoader 创建时,这个目录为null,就 意味着不进行dexopt?并不是, optimizedDirectory 为null时的默认路径为:/data/dalvik-cache。

双亲委托机制

可以看到创建 ClassLoader 需要接收一个 ClassLoader parent 参数。这个 parent 的目的就在于实现类加载的双 亲委托。即:

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

protected Class> loadClass(String name, boolean resolve) throws

ClassNotFoundException{

// 检查class是否有被加载

Class c = findLoadedClass(name); if (c == null) {

long t0 = System.nanoTime();

try {

if (parent != null) { //如果parent不为null,则调用parent的loadClass进行加载

c = parent.loadClass(name, false);

} else { //parent为null,则调用BootClassLoader进行加载

c = findBootstrapClassOrNull(name);

}

} catch (ClassNotFoundException e) {

}

if (c == null) {

// 如果都找不到就自己查找

long t1 = System.nanoTime();

c = findClass(name);

} }

return c; }

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

2、安全性考虑,防止核心API库被随意篡改。

findClass

可以看到在所有父ClassLoader无法加载Class时,则会调用自己的 findClass 方法。 findClass 在ClassLoader

中的定义为:

protected Class> findClass(String name) throws ClassNotFoundException {

throw new ClassNotFoundException(name);

}

其实任何ClassLoader子类,都可以重写 loadClass 与 findClass 。一般如果你不想使用双亲委托,则重写 loadClass 修改其实现。而重写 findClass 则表示在双亲委托下,父ClassLoader都找不到Class的情况下,定义 自己如何去查找一个Class。而我们的 PathClassLoader 会自己负责加载 MainActivity 这样的程序中自己编写的

类,利用双亲委托父ClassLoader加载Framework中的 Activity 。说明 PathClassLoader 并没有重写 loadClass ,因此我们可以来看看PathClassLoader中的 findClass 是如何实现的。

public BaseDexClassLoader(String dexPath, File optimizedDirectory,String

librarySearchPath, ClassLoader parent) {

super(parent);

this.pathList = new DexPathList(this, dexPath, librarySearchPath,optimizedDirectory);

}

@Override

protected Class> findClass(String name) throws ClassNotFoundException {

List suppressedExceptions = new ArrayList(); //查找指定的class

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;

实现非常简单,从 pathList 中查找class。继续查看 DexPathList

public DexPathList(ClassLoader definingContext, String dexPath,

String librarySearchPath, File optimizedDirectory) {

//.........

// splitDexPath 实现为返回 List.add(dexPath)

// makeDexElements 会去 List.add(dexPath) 中使用DexFile加载dex文件返回 Element数组

this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,

}

//.........

suppressedExceptions, definingContext);

在DexPathList的构造函数中,通过makeDexElements创建了Element数组,这个数组中包含了Dex文件中的类的信息。

public Class findClass(String name, List suppressed) { //从element中获得代表Dex的 DexFile

for (Element element : dexElements) { DexFile dex = element.dexFile;

if (dex != null) {

//查找class

Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed); if (clazz != null) {

return clazz;

}

} }

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

}

return null;

}

通过遍历Element数组查找需要的类。

热修复

PathClassLoader 中存在一个Element数组,Element类中存在一个dexFile成员表示dex文件,即:APK中有X个 dex,则Element数组就有X个元素。

在 PathClassLoader 中的Element数组为:[patch.dex , classes.dex , classes2.dex]。如果存在Key.class位于 patch.dex与classes2.dex中都存在一份,当进行类查找时,循环获得 dexElements 中的DexFile,查找到了 Key.class则立即返回,不会再管后续的element中的DexFile是否能加载到Key.class了。

因此实际上,一种热修复实现可以将出现Bug的class单独的制作一份fix.dex文件(补丁包),然后在程序启动时,从 服务器下载fix.dex保存到某个路径,再通过fix.dex的文件路径,用其创建 Element 对象,然后将这个 Element 对 象插入到我们程序的类加载器 PathClassLoader 的 pathList 中的 dexElements 数组头部。这样在加载出现Bug的 class时会优先加载fix.dex中的修复类,从而解决Bug。

android虚拟机加载机制,Android虚拟机与类加载机制相关推荐

  1. android动态加载背景,Android六种加载动态图的方式(包括动态背景和加载动态图片)之四...

    参考文章:http://www.cnblogs.com/firecode/archive/2012/11/01/2749774.html 其实这篇文章已经写的很全面,我只是用了一下将它加入到我的dem ...

  2. android studio 加载图片,Android Studio 加载网络图片

    Android Studio是基于gradle的一个Android开发软件,在引用网络图片的时候需要连接第三方库,这里介绍 引用glide的方法. 一.在github页面搜索glide,点击第一个 二 ...

  3. java虚拟机加载类_java虚拟机之类加载机制(一)

    一.首先先说一下java虚拟机的生命周期: 1.执行了system.exit(): 2.程序正常执行结束: 3.程序在执行过程中遇到了异常或错误而终止: 4.由于操作系统出现错误而导致java虚拟机进 ...

  4. android图片资源加载失败,Android图片加载问题分析

    下图是一个客户端图片加载模块常见的处理流程. imagepipeline.png 本文以UniversalImageLoader为例分析了这一流程,然后分析了Fresco的优势和问题,最终推荐大家使用 ...

  5. Android布局加载慢,Android布局优化(四)X2C — 提升布局加载速度200%

    系列文章 前言 在Android布局优化(一)从布局加载原理说起中我们说到了布局加载的两大性能瓶颈,通过IO操作将XML加载到内存中并进行解析和通过反射创建View.这里介绍一种避免运行时通过IO操作 ...

  6. android 底部加载更多,android:ScrollView滑动到底部显示加载更多(示例代码)

    这是效果 主要是onTouchListener监听事件,监视什么时候滑到底部 同时要理解getMeasuredHeight和getHeight的区别 getMeasuredHeight:全部的长度 包 ...

  7. android banner加载布局,Android知识点之图片轮播控件Banner

    Rate this post 在我们来发Android项目时,经常有图片或者广告的轮播功能的需求,下面将介绍一款Android开发时使用的开源图片轮播控件Banner,同时按序讲解如何使用配置这款控件 ...

  8. android svga加载动画,android svga使用

    首先在项目的build.gradle中加入allprojects { repositories { ... maven { url 'https://jitpack.io' } } } 然后在app下 ...

  9. android 漫画加载方案,Android加载长图的多种方案分享

    背景介绍 在某些特定场景下,我们需要考虑加载长图的需求,比如加载一幅<清明上河图>,这个好像有点过分了,那就加载1/2的<清明上河图>吧... 那TMD还不是一样道理. 言归正 ...

  10. android 壁纸加载流程,Android 桌面加载图标过程分析

    桌面应用图标流程 前言 本人工作上碰到这么一个需求,开发一款滤镜引擎,将桌面上所有的图标进行统一的滤镜化,这就需要了解一下整个桌面去取图标的过程,了解了整个过程,找到真正拿图标的地方,在真正取图标的地 ...

最新文章

  1. 4篇SCI,1篇A类期刊,这位复旦博士生分享自身科研经验
  2. 全文搜索引擎选 ElasticSearch 还是 Solr?
  3. python下几种打开文件的方式
  4. 多线程编程4 - GCD
  5. linq to sql简单使用
  6. java 调 cmd 没反应
  7. linux 编译查看链接库详情,Linux环境下的编译,链接与库的使用
  8. 关于SQL数据库中cross join 和inner join用法上的区别?
  9. python干货_Python干货整理,从入门说起(7.4)
  10. HIVE--数据倾斜解决办法
  11. leetcode--组合总数
  12. aardio中获取网络图片经GDI处理后保存到本地
  13. cmd jar java_cmd运行jar报错问题
  14. 双向可控硅的工作原理
  15. VPS安装msf教程
  16. 骨干是折腾出来的 读书笔记10
  17. 【计算机网络】湖南科技大学 笔记一
  18. 如何将英文的PDF文档翻译成中文简体?
  19. unity3d 图文并排--动态表情--超链接。
  20. [人工智能-综述-5]:人工智能课程学习的10大基本问题与学习方法的建议

热门文章

  1. python中的轻量级定时任务调度库:schedule
  2. java设计模式之外观模式(门面模式)
  3. 网站如何启用SSL安全证书?IIS7启用新建Https:/
  4. 搭建基于asp.net的wcf服务,ios客户端调用的实现记录
  5. 2019年校招8个大厂心得体会,纯干货分享(大疆、百度、阿里、腾讯...)
  6. 编译php ./configure命令enable和with有什么区别
  7. 文件权限及chmod使用方法
  8. PHP通过GET方法参数为数组请求
  9. CSS使用display:incline与float:left的区别:脱离文档流 参差不齐
  10. Linux负载均衡粘滞会话:IP_HASH Session(nosql mysql 文件共享系统 ) Cookie客户端加密识别用户