Android性能优化(2):常见内存泄漏与优化(二)

本文深入探讨Android虚拟机,从Dalvik到ART的区别,包括启动流程、内存管理和性能优化。此外,介绍了Android Profiler、MAT和LeakCanary等内存分析工具的使用,帮助开发者识别和解决内存泄漏问题。
摘要由CSDN通过智能技术生成


Android性能优化(1):常见内存泄漏与优化(一)一文中,我们详细阐述了Java虚拟机工作原理和Android开发中常见的内存泄漏及其优化方法,本文将在此基础上继续学习Android虚拟机发展历程、Dalvik/ART的运行时堆、Dalvik/ART启动流程以及常见的内存分析工具的特点和使用方法,包括Android Profiler、MAT、LeakCanary等。

1. Android虚拟机:Dalvik和ART

 Dalvik是Google特别设计专门用于Android平台的虚拟机,它位于Android系统架构的Android的运行环境(Android Runtime)中,是Android移动设备平台的核心组成部分之一。类似于传统的JVMDalvik虚拟机是在Android操作系统上虚拟出来的一个“设备”,用来运行Android应用程序(APP),主要负责堆栈管理、线程管理、安全及异常管理、垃圾回收、对象的生命周期管理等。在Android系统中,每一个APP对应着一个Dalvik虚拟机实例。Dalvik虚拟机支持.dex(即"Dalvik Executable")格式的Java应用程序的运行,.dex是专为Dalvik设计的一种压缩格式,它是在.class字节码文件的基础上经过DEX工具压缩、优化后得到的,适用于内存和处理器速度有限的系统。在Android 4.4以前的系统中,Android系统均采用Dalvik作为运行Android应用的虚拟机,但随着Dalvik的不足逐渐暴露,到Android 5.0以后的系统使用ART虚拟机完全取代了Dalvik虚拟机。ART虚拟机在性能上做了很多优化,比如采用预编译(AOT,Ahead Of Time compilation)取代JIT编译器、支持64位CPU、改进垃圾回收机制等等,从而使得Android系统运行更为流畅。下图展示了Android系统架构和DVM架构:
在这里插入图片描述
 Android虚拟机的使用,使得Android应用和Linux内核分离,从而使得Android系统更加稳定可靠,也就是说,即便是某个Android程序被嵌入了恶意代码,也不会直接影响系统的正常运行。接下来,我们就从分析JVM、Dalvik、ART三者之间的关系,来进一步了解它们。

1.1 JVM与Dalvik区别

 在Android 4.4以前,Android中的所有Java程序都是运行在Dalvik虚拟机上的,每个Android应用进程对应着一个独立的Dalvik虚拟机实例并在其解释下执行。虽然Dalvik虚拟机也是用来运行Java程序,但是它并没有遵守Java虚拟机规范来实现,是Google为Android平台特殊设计且运行在Android 运行时库的虚拟机,因此Dalvik虚拟机并不是一个Java虚拟机。它们的主要区别如下:

  • (1) 基于的架构不同

JVM基于栈架构,Dalvik虚拟机基于寄存器架构。JVM基于栈则意味着需要去栈中读写数据,所需更多的指令会更多(主要是load/store指令),这样会导致速度变慢,对于性能有限的移动设备,显然是不合适的;Dalvik虚拟机基于寄存器实现,则意味着它的指令会更加紧凑、简洁,因为虚拟机在复制数据时不需要使用大量的出入栈指令,但由于需要指定源地址和目标地址,所以基于寄存器的指令会比基于栈的指令要大,当然,由于指令数量的减少,总的代码数不会增加多少。下图的一段Java程序代码,展示了在JVM和Dalvik虚拟机中字节码的表现形式:
在这里插入图片描述

Java字节码以单字节(1 byte)为单元,JVM使用的指令只占1个单元;Dalvik字节码以双字节(2 byte)为单元,Dalvik虚拟机使用的指令占1个单元或2个单元。因此,在上面的代码中JVM字节码占11个单元=11字节,Dalvik字节码占6个单元=12字节(其中,mul-int/lit8指令占2单元)。

  • (2) 执行的字节码文件不同

JVM运行的.class文件,Dalvik运行的是.dex(即Dalvik Executable)文件。在Java程序中,Java类会编译成一个或多个.class文件,然后打包到.jar文件中,.jar文件中的每个.class文件里面包含了该类的常量池、类信息、属性等。当JVM加载该.jar文件时,会加载里面的所有的.class文件,JVM的这种加载方式很慢,对于内存有限的移动设备并不合适;.dex文件是在.class文件的基础上,经过DEX工具压缩和优化后形成的,通常每一个.apk文件中只包含了一个.dex,这个.dex文件将所有的.class里面所包含的信息全部整合在一起了,这样做的好处就是减少了整体的文件尺寸(去除了.class文件中相同的冗余信息),同时减少了I/O操作加快了类的查找速度。下图展示了.jar和.dex的对比差异:
在这里插入图片描述

  • (3) 在内存中的表现形式差异

Dalvik经过优化,允许在有限的内存中同时运行多个进程,或说同时运行多个Dalvik虚拟机的实例。在Android中每一个应用都运行在一个Dalvik虚拟机实例中,每一个Dalvik虚拟机实例都运行在一个独立的进程空间中,因此都对应着一个独立的进程,独立的进程可以防止在虚拟机崩溃时所有程序都被关闭。而对于JVM来说,在其宿主OS的内存中只运行着一个JVM的实例,这个JVM实例中可以运行多个Java应用程序(进程),但是一旦JVM异常崩溃,就会导致运行在其中的所有程序被关闭。

  • (4) Dalvik拥有Zygote进程与共享机制

 在Android系统中有个一特殊的虚拟机进程--Zygote,它是虚拟机实例的孵化器。它在Android系统启动的时候就会产生,完成虚拟机的初始化、库的加载、预制类库和初始化操作。如果系统需要一个新的虚拟机实例,他会迅速复制自身,以最快的速度提供给系统。对于一些只读的系统库,所有的虚拟机实例都和Zygote共享一块区域。 Dalvik虚拟机拥有预加载-共享的机制,使得不同的应用之间在运行时可以共享相同的类,因此拥有更高的效率。而JVM则不存在这个共享机制,不同的程序被打包后都是彼此独立的,即便它们在包里使用了相同的类,运行时的都是单独加载和运行,无法进行共享。
在这里插入图片描述

1.2 Dalvik与ART区别

ART虚拟机被引入于Android 4.4,用来替换Dalvik虚拟机,以缓解Dalvik虚拟机的运行机制导致Android应用运行变慢的问题。在Android 4.4中,可以选择使用Dalvik还是ART,而从Android 5.0开始,Dalvik被完全删除,Android系统默认采用ART。Dalvik与ART的主要区别如下:

  • (1) ART运行机制优于Dalvik

 对于运行在Dalvik虚拟机实例中的应用程序而言,在每一次重新运行的时候,都需要将字节码通过JIT(Just-In-Time)编译器编译成机器码,这会使用应用程序的运行效率降低,虽然Dalvik虚拟机已经被做过很多优化(.dex文件->.odex文件),但由于这种先翻译再执行的机制仍然无法有效解决Dalvik拖慢Android应用运行的事实。而在ART中,系统在安装应用程序时会进行一次AOT(Ahead Of Time compilication,预编译),即将字节码预先编译成机器码并存储在本地,这样应用程序每次运行时就不需要执行编译了,运行效率会大大提高。
在这里插入图片描述

  • (2) 支持的CPU架构不同

 Dalvik是为32位CPU设计的,而ART支持64位并兼容32位的CPU。

 Dalvik虚拟机的运行时堆使用标记--清除(Mark--Sweep)算法进行GC,它由两个Space以及多个辅助数据结构组成,两个Space分别是Zygote Space(Zygote Heap)Allocation Space(Active Heap)Zygote Space用来管理Zygote进程在启动过程中预加载和创建的各种对象,Zygote Space中不会触发GC,应用进程和Zygote进程之间会共享Zygote Space。Zygote进程在fork第一个子进程之前,会把Zygote Space分为两个部分,原来被Zygote进程使用的部分仍然叫Zygote Space,而剩余未被使用的部分被称为Allocation Space,以后fork的子进程相关的所有的对象都会在Allocation Space上进行分配和释放。需要注意的是,Allocation Space不是进程共享的,在每个进程中都独立拥有一份。下图展示了Dalvik虚拟机的运行时堆结构:

 与Dalvik的GC不同,ART采用了多种垃圾收集方案,每个方案会运行不同的垃圾收集器,默认是采用了CMS(Concurrent Mark-Sweep)方案,该方案主要使用了sticky-CMS和patial-CMS。根据不同的CMS方案,ART的运行时堆的空间划分也会不同,默认是由4个Space和多个辅助数据结构组成,4个Space分别是Zygote SpaceAllocation SpaceImage Space以及Large Object Space,其中,Zygote SpaceAllocation Space和Dalvik的一样,Image Space用来存放一些预加载类,Large Object Space用来分配一些大对象(默认大小为12Kb)。需要注意的是,Zygote SpaceImage Space是进程间共享的。下图展示了ART采用标记–清除算法的堆划分:


ART虚拟机的不足:

  • 安装时间变长。应用在安装的时候需要预编译,从而增大了安装时间。
  • 存储空间变大。ART引入AOT技术后,需要更多的空间存储预编译后的机器码。

 因此,从某些程度上来说,ART虚拟机是利用了“空间换时间”,来提高Android应用的运行速度。为了缓解上述的不足,Android7.0(N)版本中的ART加入了即时编译器JIT,作为AOT的一个补充,在应用程序安装时并不会将字节码全部编译成机器码,而是在运行中将热点代码编译成机器码,具体来说,就是当我们第一次运行应用相关程序后,JIT提供了一套追踪机制来决定哪一部分代码需要在手机处于idle状态和充电的时候来编译,并将编译得到的机器码存储到本地,这个追踪技术被称为Profile Guided Compilcation

1.3 Dalvik/ART的启动流程

 从剖析Android系统的启动过程一文可知,init进程(pid=1)是Linux系统的用户进程,是所有用户进程的父进程。当Android系统在启动init进程后,该进程会孵化出一堆用户守护进程、ServiceManager服务以及Zygote进程等等,其中,Zygote进程是Android系统启动的第一个Java进程,或者说是虚拟机进程,因为它持有Dalvik或ART的实例。Zygote进程是所有Java进程的父进程,每当系统需要创建一个应用程序时,Zygote进程就会fork自身,并快速地创建和初始化一个虚拟机实例,用于应用程序的运行。下面我们就从Android9.0源码的角度,来分析Zygote进程中的Dalvik或ART虚拟机实例是如何创建的。

 首先,根据init.rc的启动服务的命令,将运行/system/bin/app_process可执行程序来启动zygote进程,该可执行程序对应的源文件为../base/cmds/app_process/app_main.cpp,也就是说,当从init进程发起启动Zygote进程后,会调用app_main.cppmain函数进入Zygote进程的启动流程。app_main.cpp的main函数源码如下:

//app_main.cpp$main函数
int main(int argc, char* const argv[])
{
   
    ...
    // (1) 创建AppRuntime对象
    AppRuntime runtime(argv[0], computeArgBlockSize(argc, argv));
    
    ...
	// (2) 解析执行init.rc的启动服务的命令传入的参数
    // 解析后:zygote = true
    //        startSystemServer = true
    //        niceName = zygote (当前进程名称)
    bool zygote = false;
    bool startSystemServer = false;
    bool application = false;
    String8 niceName;
    String8 className;
    while (i < argc) {
   
        const char* arg = argv[i++]
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值