Android了解虚拟机

Android虚拟机包括Dalivk(2.2-4.4),ART(5.0+)。从JVM开始分析
Java虚拟机:
虚拟机所管理的内存将会包括以下几个运行时数据区域:

  1. 方法区
  2. 本地方法栈
  3. 虚拟机栈
  4. 程序计数器

介绍:
程序计数器:
多线程是通过线程轮流切换,占用CPU时间,在任何确定时刻,一个CPU(对于多核处理器是一个内核)只会执行一条线程中的指令。
因此,为了切换后能执行正确的位置,需要程序计数器,是线程私有的。
此内存区域是唯一一个没规定OutOfMemoryError情况的区域。
JAVA虚拟机栈:
也是线程私有,存方法中的局部变量。
方法执行的同时会创建一个栈帧用于存储局部变量表、操作栈、动态链接、方法出口等。
每个方法被调用直至完成的过程,对应栈帧由入栈到出栈的过程。
两种异常:1.StackOverflowError,线程请求的栈深度大于虚拟机所允许的深度抛出。2.OutOfMemoryError,虚拟机栈扩展时无法申请到足够空间抛出。
natvie栈:
本地方法栈与虚拟机栈类似,区别是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,本地方法栈为虚拟机使用到的native方法服务。
也会抛出以上2种异常。
堆:
存放对象的实例。
GC管理的主要区域,利用分代收集算法,堆中分为新生代,老年代,持久代。
如果从内存分配的角度看,线程共享的JAVA堆中可能会划分出多个线程私有的分配缓冲区。
方法区:
是各个线程共享的内存区域,用于存储被虚拟机加载的类信息,常量,静态变量等
对象访问
引用类型:使用句柄和直接指针。

android的Dalvik:
与JVM不同,JVM是栈机(stack machine),而Dalvik VM是基于寄存器的架构。
寄存器:在CPU内,其读写速度跟CPU的运行速度基本匹配,节省存储空间,提高指令的执行速度。但因为性能优越,所以造价昂贵,一般好的CPU也就只有几MB的2级缓存,1级缓存更小。
对比:
基于堆栈的指令很紧凑,例如,Java虚拟机使用的指令只占一个字节,因而称为字节码。
基于寄存器的指令由于需要指定源地址和目标地址,因此需要占用更多的指令空间。
一般而言,执行同样的功能,前者需要更多的指令(主要是load和store指令),而后者需要更多的指令空间。
需要更多指令意味着要多占用CPU时间,而需要更多指令空间意味着数据缓冲(d-cache)更易失效。

总的来说,基于stack的机器必须使用指令来载入stack上的数据,或使用指令来操纵数据,因此与基于寄 存器的机器相比,需要的指令更多。
然而,在寄存器的指令必须编码源和目的地寄存器,因此往往指令更大。

Dalvik虚拟机的内存大体上可以分为Java Object Heap、Bitmap Memory和Native Heap三种。
Java Object Heap:
分配Java对象的,也就是我们在代码new出来的对象。Dalvik虚拟机在启动的时候,可以通过-Xms和-Xmx选项来指定Java Object Heap的最小值和最大值。
Java Object Heap的最大值也就是我们平时所说的Android应用程序进程能够使用的最大内存。
Bitmap Memory
也称为External Memory,它是用来处理图像的。
在android3.1之前,它是Native Heap中分配的,但是这部分内存同样计入Java Object Heap中,也就是说,Bitmap占用的内存和Java Object占用的内存加起来不能超过Java Object Heap的最大值。这就是为什么我们在调用BitmapFactory相关的接口来处理大图像时,会抛出一个OutOfMemoryError异常的原因:
在3.1之后,Bitmap Memory直接由Java Object Heap分配内存。
Native Heap
Native Heap就是在Native Code中使用malloc等分配出来的内存,这部分内存是不受Java Object Heap的大小限制的,也就是它可以自由使用,当然它是会受到系统的限制。

android4.4+的ART:
4.4及以上,区别Dalvik的地方,见下面。
JIT(即时编译)和AOT(运行前编译)
AOT本质上是一种静态编译,它是相对于JIT而言的,也就是说,AOT是在程序运行前进行编译,而JIT是在程序运行时进行编译。
在2.2版本-4.4是Dalvik,添加了JIT编译器。
无论AOT,还是JIT,最终的目标都是将解释语言编译为本地机器语言,而本地机器语言都是基于寄存器来执行的,因此,在某种程度来讲,基于寄存器的指令更有利于进行AOT编译以及优化。

ART和Dalvik
1.Android Runtime (ART) 是运行 Android 5.0(API 级别 21)及更高版本的设备的默认运行时。 此运行时提供了多种可改善 Android 平台和应用的性能和流畅度的功能。
2.可以通过调用 System.getProperty(“java.vm.version”) 来验证正在使用哪种运行时。 如果使用的是 ART,则该属性值将是 “2.0.0” 或更高。测试了酷派一款4.2.2的机器对应属性值是”1.6.0”,魅族5.1.0对应属性值是”2.1.0”.
ART特点:
1.提前(AOT)编译;可以提高应用程序的性能,安装时执行比 Dalvik 更严格的字节代码验证;安装时使用dex2oat工具编译app,此工具接收dex文件并生成编译后的可执行app.注意有些后期处理工具(尤其是执行模糊处理的工具)产生的无效文件,在Dalvik编译通过,但是在ART不通过。
2.改进垃圾回收GC.改进方式:
先分析下Dalvik的垃圾回收过程:当Dalvik开始GC时,整个程序的线程就会挂起;之所以要挂起所有线程是确保所有程序没有进行任何变更,与此同时GC会隐藏所有处理过的对象。挂起所有线程优先级很高,内存紧张的时候就会频繁执行这个动作,这样就会造成丢帧,界面卡顿的现象。
Dalvik特点:
GC时挂起所有线程,大而连续的空间紧张,内存碎片化严重。
ART中GC会要求程序在分配空间的时候标记自身的堆栈,这个过程非常短,不需要挂起所有程序的线程。
ART里会有一个独立的Large Objects Space(LOS)供Bitmap使用,从而提高了GC的管理效率和整体性能
通过moving collector来压缩内存,使内存空间更加紧凑

  1. 更少的内存碎片
  2. 更短更少的中断和阻塞
  3. 更低的内存使用率
  4. 提供专门的采样分析器添加到了traceView工具中,性能影响更低。

部分适合 Dalvik 的技术并不适用于 ART。
在 Android Runtime (ART) 上验证应用行为
解决垃圾回收 (GC) 问题:
在 Dalvik 中,应用常常发现显式调用 System.gc() 非常有用,可促进垃圾回收 (GC)。对 ART 而言这种做法的必要性低得多
JNI 问题
ART 的 JNI 比 Dalvik 的 JNI 更为严格一些.使用 CheckJNI 模式来捕获常见问题是一种特别实用的方法。
ART 的 JNI 会在多种情况下引发错误,而 Dalvik 则不然。
预防堆栈大小问题
Dalvik 具有单独的原生代码堆栈和 Java 代码堆栈,并且默认的 Java 堆栈大小为 32KB,默认的原生堆栈大小为 1MB。 ART 具有统一的堆栈以改善局部性。
对象模型更改
ART 会在安装时执行比 Dalvik 更严格的字节代码验证。

MultiDex
65,536代表的是单个 Dalvik Executable (dex) 字节码文件内的代码可调用的引用总数。其中包括 Android 框架方法、内容库方法以及您自己代码中的方法。
android会把每一个类的方法id检索起来,存在一个链表结构里面。但是这个链表的长度是用一个short类型来保存的, short占两个字节(保存-2的15次方到2的15次方-1,即-32768~32767),最大保存的数量就是65536。
Android 5.0 之前版本的 Dalvik 可执行文件分包支持.
默认情况下,Dalvik 限制应用的每个 APK 只能使用单个 classes.dex 字节码文件。要想绕过这一限制,您可以使用 Dalvik 可执行文件分包支持库,它会成为您的应用主要 DEX 文件的一部分,然后管理对其他 DEX 文件及其所包含代码的访问。
Android 5.0 及更高版本的 Dalvik 可执行文件分包支持
ART 在应用安装时执行预编译,扫描 classes(..N).dex 文件,并将它们编译成单个 .oat 文件,供 Android 设备执行。
规避 64K 限制
通过 ProGuard 移除未使用的代码
检查您的应用的直接和传递依赖项
局限性
如果辅助 dex 文件较大,可能导致应用无响应 (ANR) 错误
由于存在 Dalvik linearAlloc 错误(问题 22586),使用 Dalvik 可执行文件分包的应用可能无法在运行的平台版本低于 Android 4.0(API 级别 14)的设备上启动。
Dalvik linearAlloc 限制(问题 78035),如果使用 Dalvik 可执行文件分包配置的应用发出非常庞大的内存分配请求,可能会在运行时发生崩溃。
系统对于主 dex 文件在 Dalvik 运行时中执行时需要哪些类有着复杂的要求。

MultiDex实现原理
下面从DEX自动拆包和动态加载两方面来分析。
1.Dex 拆分
dex拆分步骤分为:
1)自动扫描整个工程代码得到main-dex-list;
2)根据main-dex-list对整个工程编译后的所有class进行拆分,将主、从dex的class文件分开;
3)用dx工具对主、从dex的class文件分别打包成 .dex文件,并放在apk的合适目录。
怎么自动生成 main-dex-list? Android SDK 从 build tools 21 开始提供了 mainDexClasses 脚本来生成主 dex 的文件列表。查看这个脚本的源码,可以看到它主要做了下面两件事情:
1)调用 proguard 的 shrink 操作来生成一个临时 jar 包;
2)将生成的临时 jar 包和输入的文件集合作为参数,然后调用com.android.multidex.MainDexListBuilder 来生成主 dex 文件列表。
2.Dex加载
因为Android系统在启动应用时只加载了主dex(Classes.dex),其他的 dex 需要我们在应用启动后进行动态加载安装。 MultiDex.install(context)注要工作是:从 apk 中提取出所有的从属dex(classes2.dex,classes3.dex,…),然后通过反射依次安装加载从属dex,并合并到放在BaseDexClassLoader的DexPathList的 Element数组。

=====分割线======================================
题外话:
Android为什么会选择Java而不是C/C++来作为应用程序开发语言,就是为了能够让开发远离内存问题,而将精力集中在业务上,开发出更多更好的APP来,从而迎头赶超iOS。当然,Android系统内存也存在大量的C/C++代码,这只要考虑性能问题,毕竟C/C++程序的运行性能整体上还是优于运行在虚拟机之上的Java程序的。不过,为了避免出现内存问题,在Android系统内部的C++代码,大量地使用了智能指针来自动管理对象的生命周期。选择Java来作为Android应用程序的开发语言,可以说是技术与商业之间一个折衷,事实证明,这种折衷是成功的。

Zygote进程
是由init进程启动起来的,也就是在系统启动的时候启动的。Zygote进程在启动的时候,会创建一个虚拟机实例,并且在这个虚拟机实例将所有的Java核心库都加载起来。每当Zygote进程需要创建一个Android应用程序进程的时候,它就通过复制自身来实现,也就是通过fork系统调用来实现。这些被fork出来的Android应用程序进程,一方面是复制了Zygote进程中的虚拟机实例,另一方面是与Zygote进程共享了同一套Java核心库。这样不仅Android应用程序进程的创建过程很快,而且由于所有的Android应用程序进程都共享同一套Java核心库而节省了内存空间。
更多介绍:
Dalvik虚拟机的启动过程分析:
http://blog.youkuaiyun.com/luoshengyang/article/details/8885792
Dalvik虚拟机的运行过程分析
http://blog.youkuaiyun.com/luoshengyang/article/details/8914953
Dalvik虚拟机JNI方法的注册过程分析
http://blog.youkuaiyun.com/luoshengyang/article/details/8923483
Java进程和线程的创建过程
http://blog.youkuaiyun.com/luoshengyang/article/details/8923484

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值