Java虚拟机原理(下)-Dalvik vs ART-探秘Android虚拟机内在机制

2024-05-26 10:36

本文主要是介绍Java虚拟机原理(下)-Dalvik vs ART-探秘Android虚拟机内在机制,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!


Android系统作为移动端主流平台,其高效的虚拟机无疑是其核心竞争力之一。今天,就让我们一起剥开Dalvik和ART虚拟机的外衣,深入解析它们的工作原理和优缺点,帮助你全面把握Android系统的运行机制。


正文导览

  1. Dalvik和ART虚拟机的发展历程
  2. Dalvik虚拟机的工作原理和特点
  3. ART虚拟机的工作原理和优势
  4. 两者的性能和效率对比分析
  5. 如何根据需求选择合适的虚拟机

一、Dalvik和ART的发展历史


1、 Dalvik孕育与兴起


Dalvik在2007年随着Android平台的发布而首次亮相,并在随后的几年中不断更新和改进。

它基于开源的Java虚拟机,但做了很多改进和优化,专门为搭载有限资源的移动设备量身定制,如:

  • Register-based执行,减少内存占用
  • .dex字节码格式,减小APK体积
  • JIT即时编译器,提升运行效率

Dalvik虚拟机(DVM)与传统的Java虚拟机(JVM)有几个关键的区别:

  • 设计目标:Dalvik被设计为适合移动设备,具有较低的内存占用和高效的电池使用。
  • 执行格式:Dalvik使用.dex文件格式,这是从Java字节码转换而来的一种压缩形式,专为Dalvik优化。
  • 垃圾回收:Dalvik采用了一套适合移动设备的垃圾回收机制,以适应有限的资源。

(1)、Dalvik的主要功能和作用包括

  • 执行Android应用程序:Dalvik虚拟机执行.dex格式的应用程序代码。
  • 内存管理:Dalvik提供了一套适合移动设备的内存管理策略。
  • 垃圾回收:Dalvik的垃圾回收机制优化了移动设备的资源使用。

(2)、Dalvik的优点

  • 资源占用低:Dalvik设计时考虑了移动设备的资源限制,占用较少的内存和处理能力。

  • 灵活性:Dalvik允许更多的定制和优化,以适应不同的硬件和软件环境。


(3)、Dalvik的缺点

  • 性能限制:由于依赖运行时编译,Dalvik的性能可能不如AOT编译的ART。

  • 内存管理:虽然针对移动设备进行了优化,但在某些情况下,Dalvik的内存管理可能不如ART高效。


2 、ART继任登场


伴随硬件性能的飞速提升,Dalvik的一些先天缺陷逐渐凸显,主要表现为应用启动缓慢、无法实现应用级别的JIT等。为了持续提升性能,Google从Android 4.4开始引入全新的ART虚拟机。

ART本质上是一款Ahead-Of-Time (AOT)编译器,它在应用安装时就预先编译字节码为机器码,这使得应用的启动和运行速度都得到极大提升。Android 8.0及更高版本已经将ART设为默认虚拟机。


(1)、ART的主要改进包括

  • Ahead-of-Time(AOT)编译:ART采用了AOT编译技术,将应用程序代码在安装时编译成机器码,从而提高了执行效率。
  • 性能提升:由于AOT编译,ART在运行时不需要进行即时编译(JIT),这减少了运行时的开销,提高了性能。
  • 内存管理:ART改进了内存分配和垃圾回收机制,提供了更好的内存管理。

(2)、ART的主要功能和作用包括

  • AOT编译:ART在安装时将应用程序代码编译成机器码,提高了启动速度和运行效率。
  • 性能优化:ART通过减少运行时编译的需要,提高了应用程序的性能。
  • 内存管理:ART提供了改进的内存管理机制,包括更有效的垃圾回收。

(3)、ART的优点

  • 性能提升:ART通过AOT编译提供了更好的性能。

  • 内存管理:ART的内存管理更为高效,特别是在处理大型应用程序时。


(4)、ART的缺点

  • 安装时间:由于AOT编译,应用程序的安装时间可能会更长。
  • 兼容性问题:某些旧的Android应用程序可能需要额外的工作才能在ART上运行。

二、Dalvik虚拟机工作原理


Dalvik虚拟机通过其独特的设计和优化措施,为Android应用提供了高效的运行环境。然而,随着Android系统的发展,Dalvik最终被ART(Android RunTime)所取代,后者提供了更高的性能和更好的内存管理。尽管如此,了解Dalvik的工作原理对于理解Android应用开发的历史和演变仍然非常重要。

下面我们来看看 Dalvik虚拟机的工作原理:

1、基于寄存器的架构

Dalvik虚拟机采用的是基于寄存器的架构,不同于传统的基于栈的Java虚拟机。这种设计使得Dalvik虚拟机的执行效率更高,因为它减少了指令的数量和复杂性。


2、.dex文件格式

Dalvik虚拟机使用.dex文件格式来存储和执行应用程序代码。.dex格式是专门为Dalvik设计的压缩格式,它由dx工具从Java的.class文件转换而来,优化了存储空间和访问速度。


3、Zygote进程

Dalvik虚拟机有一个特殊的Zygote进程,它作为虚拟机实例的孵化器。在系统启动时,Zygote进程会创建并完成虚拟机的初始化、库的加载和预置类库的初始化操作。当系统需要一个新的虚拟机实例时,Zygote可以迅速复制自身,以最快的速度提供给系统。


4、内存共享

Dalvik虚拟机允许多个虚拟机实例共享一些只读的系统库,这意味着所有虚拟机实例都和Zygote共享一块内存区域。这有助于减少内存占用并提高效率。


5、JIT编译技术

自Android 2.2开始,Dalvik虚拟机支持JIT(Just-In-Time,即时编译技术)。这意味着虚拟机可以在运行时对代码进行编译,进一步提高执行效率。


6、优化措施

Dalvik虚拟机在安装到移动设备时,可执行文件可能会被修改以进一步优化性能。这包括调整数据的端序、内联一些函数和简化结构体,以及短路掉一些不必要的操作。


7、代码监控与优化

当Android启动时,Dalvik VM监视所有的程序(APK),并创建依存关系树,为每个程序优化代码并存储在Dalvik缓存中。这意味着第一次加载应用时可能会较慢,因为需要生成缓存文件,但之后加载会更快。


8、快速翻译器

Dalvik还提供了一个快速翻译器(Fast Interpreter)来强化功能,这有助于提高代码的解释执行速度。


9、与Java SE/ME的区别

Dalvik虚拟机不支持Java SE或Java ME类库,它使用自己建立的类库,这是Apache Harmony Java的一个子集。这使得Dalvik能够为Android提供定制化的运行时环境。


10、许可与版权

Dalvik是基于Apache License 2.0发布的,Google声称Dalvik是一个清洁室(clean room)的实现,不是基于标准Java运行环境的改进,因此不受标准Java运行环境版权许可的限制。


三、ART虚拟机的运行机制


1 、AOT静态编译机制

在Android开发中,AOT(Ahead-Of-Time)编译是一种在应用安装之前,将Java代码编译成机器码的技术。AOT编译可以提高应用的启动速度和性能,因为它减少了应用运行时的即时编译(JIT)需求。

从Android Studio 3.3开始,Android引入了D8 Dexer来替代旧的Dex编译器,并且从Android 9(Pie)开始,Android引入了新的AOT编译器,使得AOT编译成为默认行为。


下面是一个简单的Android Java代码示例,以及如何在Android Studio中构建和理解AOT编译的运行过程:

(1)、创建Android项目

打开Android Studio,创建一个新的Android项目。


(2)、编写Java代码

在MainActivity.java中编写示例代码:

package com.example.aotdemo;import android.os.Bundle;
import android.widget.TextView;import androidx.appcompat.app.AppCompatActivity;public class MainActivity extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);TextView textView = findViewById(R.id.text_view);textView.setText("Hello, AOT World!");}
}

(3)、 构建APK

在Android Studio中,你可以通过点击Build菜单然后选择Build Bundle(s) / APK(s)来构建APK。对于AOT编译,你可以选择构建一个Release版本的APK。


(4)、理解AOT编译过程

当你构建Release版本的APK时,以下步骤发生:

  • D8 Dexer:首先,D8将Java字节码转换为Dalvik字节码,这是一种中间表示形式,专为Android设计。

  • AOT编译器:然后,Android的AOT编译器将Dalvik字节码编译成机器码。这个过程发生在应用安装之前,因此当应用运行时,大部分代码已经是机器码,减少了运行时的JIT编译需求。


(5)、 安装APK

将构建的APK安装到Android设备或模拟器上。


(6)、运行应用

运行应用,你将看到"Hello, AOT World!"的文本显示在屏幕上。


(7)、 查看编译结果

要查看AOT编译的结果,可以使用adb命令行工具来查看设备上的ELF文件,这些文件包含了编译后的机器码:

adb shell
cd /data/app/
ls -l <package_name>

替换<package_name>为你的应用包名。你将看到一些以.oat结尾的文件,这些文件包含了AOT编译后的机器码。


(8)、注意事项

  • AOT编译是Android系统自动处理的,开发者通常不需要手动干预。
  • AOT编译可以提高应用的性能,特别是在应用启动时。
  • 由于AOT编译在安装前进行,因此它可以减少应用运行时的资源消耗。

通过上述步骤,你可以在Android Studio中构建一个应用,并理解AOT编译的基本概念和运行过程。


2 、Ahead-Of-Time编译器

Ahead-Of-Time(AOT)编译器是一种静态编译器,它在程序执行之前将高级语言代码(如Java或C/C++)转换为机器码。这种编译方式允许编译器进行深入的分析和优化,因为编译器有足够的时间来执行这些操作,并且不需要在运行时进行即时编译(JIT)。


以下是AOT编译器在编译过程中执行的一些关键优化步骤:

(1)、内联(Inlining)

内联是一种优化技术,它涉及将函数调用的代码直接嵌入到调用点,从而消除函数调用的开销。这在调用小型函数时特别有效,因为函数调用的开销可能比函数本身执行的时间还要长。AOT编译器会分析函数调用的上下文,并决定是否内联这些函数。


(2)、去虚拟化(Devirtualization)

在面向对象的编程中,虚拟函数调用(通过虚函数表)可能会引入运行时开销。AOT编译器可以分析程序的类型信息,并在可能的情况下将虚拟函数调用转换为直接函数调用,从而消除虚函数表的查找过程。


(3)、寄存器分配(Register Allocation)

寄存器分配是编译过程中的一个关键步骤,它涉及将程序中的变量分配给CPU的寄存器。AOT编译器会尝试优化寄存器的使用,以减少内存访问的需要,从而提高程序的执行速度。


(4)、死代码消除(Dead Code Elimination)

死代码消除是一种优化技术,它涉及识别并移除程序中永远不会执行的代码。这可以减少生成的机器码的大小,提高程序的效率。


(5)、常量传播(Constant Propagation)

常量传播涉及在编译时将表达式中的常量值传播到它们被使用的每个地方。这可以简化运行时的计算,并可能使进一步的优化成为可能。


(6)、循环优化(Loop Optimization)

循环是许多程序中性能的关键部分。AOT编译器可以执行各种循环优化,如循环展开(将循环的多次迭代合并为一个迭代),循环融合(将多个循环合并为一个循环),以及循环不变代码的移动。


(7)、指令调度(Instruction Scheduling)

指令调度涉及重新排列指令的顺序,以优化CPU流水线的使用。这可以减少指令执行的等待时间,提高程序的执行效率。


(8)、代码生成(Code Generation)

在这个阶段,AOT编译器将优化后的中间表示(IR)转换成目标机器的指令。这个过程涉及选择最合适的机器指令来实现高级操作,并确保生成的代码符合目标平台的架构要求。


(9)、链接(Linking)

最后,AOT编译器将生成的机器码与库和其他依赖项链接在一起,生成最终的可执行文件。

AOT编译器的这些优化可以显著提高程序的性能,尤其是在启动时间和运行时性能方面。然而,这些优化也可能增加编译时间,并可能导致生成的可执行文件体积增大。此外,由于AOT编译是在没有运行时信息的情况下进行的,因此某些优化可能不如JIT编译那样精确。尽管如此,AOT编译仍然是许多高性能应用程序的首选方法,特别是在嵌入式系统、移动设备和游戏开发中。


3 、支持多种GC算法


ART可以选择标记-清除、标记-整理或并发标记扫描等多种GC算法,并根据运行时状况进行切换。


(1)、ART中常见的几种GC算法


  • 标记-清除(Mark-Sweep)

    标记-清除是最基础的垃圾收集算法之一。它分为两个阶段:

    • 标记阶段:垃圾收集器遍历所有可达对象,并标记它们为活跃的。
    • 清除阶段:垃圾收集器再次遍历堆内存,清除所有未被标记的对象。

这种算法简单,但有两个主要问题:一是它不能处理循环引用,二是它会产生内存碎片。


  • 标记-整理(Mark-Compact)

    为了解决标记-清除算法中的问题,标记-整理算法在标记阶段之后增加了一个整理阶段:

    • 整理阶段:垃圾收集器移动活跃对象,使它们紧密排列,从而减少内存碎片。

      这种算法可以减少内存碎片,但会增加GC的暂停时间,因为对象需要被移动。


  • 并发标记扫描(Concurrent Mark-Sweep or Concurrent Marking)

    并发标记扫描算法尝试减少垃圾收集对应用程序性能的影响,通过在应用程序运行时并发地执行标记阶段:

    • 并发标记阶段:垃圾收集器在应用程序运行时并发地遍历对象图,标记活跃对象。
    • 暂停标记阶段:在应用程序暂停时,完成标记过程,并执行清除或整理。

这种算法可以减少GC引起的应用程序暂停时间,但实现复杂,需要处理并发访问和修改对象的问题。


  • 分代收集(Generational Collection)

    ART还实现了分代收集策略,它基于这样一个观察:大多数对象都是短暂存在的。分代收集将堆内存分为几个区域,每个区域代表不同的对象生命周期(年轻代、老年代等):

    • 年轻代收集:频繁地对年轻代进行垃圾收集,因为大多数新创建的对象都会很快变成垃圾。

    • 老年代收集:较少地对老年代进行垃圾收集,因为存活下来的对象通常活得更久。


  • 增量收集(Incremental Collection)

    增量收集算法尝试将垃圾收集工作分散到多个小步骤中,以减少每次GC的暂停时间。


  • 增量步骤

    垃圾收集器在多个小的、增量步骤中执行标记、清除或整理工作。这种方法可以减少单次GC引起的延迟,但可能会增加整体的GC时间。


(2)、运行时切换

ART可以根据应用程序的行为和性能需求动态地在不同的GC算法之间切换。例如:

  • 当检测到内存使用率较高或GC暂停时间过长时,ART可能会从标记-清除切换到标记-整理,以减少内存碎片。
  • 在应用程序负载较低时,ART可能会使用并发标记扫描,以减少对应用程序性能的影响。
  • 对于具有大量短暂对象的应用程序,ART可能会采用分代收集策略。

ART的垃圾收集器是高度可配置的,开发者可以通过设置特定的调试选项或使用Android Profiler等工具来监控和调整GC行为,以优化应用程序的性能。


4、 BCP 技术性能优化

BCP(Background Compact Profiling)是Android Runtime (ART) 引入的一项技术,它主要目的是在后台对Android应用程序的内存使用进行分析和优化,从而提高应用程序的性能和响应速度。以下是BCP的一些关键功能和作用:

(1)、内存分析

BCP可以在应用程序运行时,对内存中的活动对象进行分析,识别出哪些对象是活跃的,哪些对象是可以被回收的垃圾。这种分析是在后台进行的,不会影响应用程序的正常使用。


(2)、内存优化

通过分析内存使用情况,BCP可以识别出内存中的碎片和浪费,然后通过垃圾收集(GC)来回收不再使用的对象,释放内存空间。这有助于减少内存使用,防止内存泄漏。


(3)、减少GC暂停时间

BCP通过在后台进行垃圾收集,可以减少应用程序运行时的GC暂停时间。这对于提高应用程序的响应性和用户体验非常重要,因为用户不喜欢看到应用程序在执行任务时出现卡顿。


(4)、内存碎片整理

随着应用程序的运行,内存中的对象可能会被删除,导致内存碎片化。BCP可以识别这些碎片,并在后台进行内存整理,使得内存空间更加连续,从而提高内存的使用效率。


(5)、应用性能监控

BCP不仅可以优化内存使用,还可以监控应用程序的性能。它可以帮助开发者了解应用程序在运行时的行为,识别性能瓶颈和潜在问题。


(6)、支持热更新

虽然BCP本身不直接提供热更新功能,但它可以与热更新机制相结合,使得热更新后的代码可以在后台进行优化,从而在用户不感知的情况下提高更新后代码的性能。


( 7)、提高系统稳定性

通过及时回收不再使用的对象和优化内存使用,BCP有助于提高整个系统的稳定性。它减少了因内存不足导致的应用程序崩溃或系统错误。


(8)、后台执行

BCP的所有操作都是在后台执行的,这意味着它不会干扰用户当前的操作或应用程序的运行。这种非侵入式的优化对于提升用户体验至关重要。


BCP是ART中一项重要的技术,它通过在后台对内存进行分析和优化,显著提高了Android应用程序的性能和稳定性。它减少了垃圾收集的暂停时间,优化了内存使用,并支持了热更新机制,使得应用程序可以在不重启的情况下进行更新和优化。这些功能共同作用,为用户提供了更加流畅和响应迅速的Android体验。


四、Dalvik vs ART的效率对比


相较于Dalvik,ART在很多方面都有了质的提升:

  • 应用启动速度 - 由于AOT编译,ART应用启动速度快2-4倍
  • 执行效率 - 优化的机器码使ART执行效率高30%以上
  • 内存使用 - 更好的GC算法令ART内存使用更高效
  • 电池续航 - ART对电池续航性能略有提升

当然,AOT编译的静态系统也带来了一些缺点,比如占用更多存储空间、热更新部署不便等。总的来说,ART在绝大多数场景下都表现更佳。


五、如何根据需求选择合适的虚拟机


对于普通的Android应用,用户无需过多考虑虚拟机的选择,只需遵循Google的最新推荐即可,目前就是使用ART。

而对于某些嵌入式或专用场景的Android设备,如Android TV、Android Auto、IoT等,由于它们存储和运行环境的特殊性,倒可能需要考虑回退到Dalvik虚拟机。

此外,如果您的应用对存储空间极为敏感,也可以考虑暂时使用Dalvik,但这种做法并不推荐,因为它牺牲了性能换取的存储空间优势并不太值。


展望未来

通过本文,相信您已经对Android两大虚拟机有了全面的理解。不过,技术发展日新月异,虚拟机的优化和创新从未停止脚步。比如说,未来Android或许会加入iOS的Bitcode技术;谷歌也在积极研究机器学习算法在ART优化中的应用等。

虚拟机作为系统的核心基础,我们当然有必要时刻关注其最新发展动向。如您还有任何疑问或心得,也欢迎在评论区继续交流讨论。

这篇关于Java虚拟机原理(下)-Dalvik vs ART-探秘Android虚拟机内在机制的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1004225

相关文章

JVM 的类初始化机制

前言 当你在 Java 程序中new对象时,有没有考虑过 JVM 是如何把静态的字节码(byte code)转化为运行时对象的呢,这个问题看似简单,但清楚的同学相信也不会太多,这篇文章首先介绍 JVM 类初始化的机制,然后给出几个易出错的实例来分析,帮助大家更好理解这个知识点。 JVM 将字节码转化为运行时对象分为三个阶段,分别是:loading 、Linking、initialization

Spring Security 基于表达式的权限控制

前言 spring security 3.0已经可以使用spring el表达式来控制授权,允许在表达式中使用复杂的布尔逻辑来控制访问的权限。 常见的表达式 Spring Security可用表达式对象的基类是SecurityExpressionRoot。 表达式描述hasRole([role])用户拥有制定的角色时返回true (Spring security默认会带有ROLE_前缀),去

浅析Spring Security认证过程

类图 为了方便理解Spring Security认证流程,特意画了如下的类图,包含相关的核心认证类 概述 核心验证器 AuthenticationManager 该对象提供了认证方法的入口,接收一个Authentiaton对象作为参数; public interface AuthenticationManager {Authentication authenticate(Authenti

Spring Security--Architecture Overview

1 核心组件 这一节主要介绍一些在Spring Security中常见且核心的Java类,它们之间的依赖,构建起了整个框架。想要理解整个架构,最起码得对这些类眼熟。 1.1 SecurityContextHolder SecurityContextHolder用于存储安全上下文(security context)的信息。当前操作的用户是谁,该用户是否已经被认证,他拥有哪些角色权限…这些都被保

Spring Security基于数据库验证流程详解

Spring Security 校验流程图 相关解释说明(认真看哦) AbstractAuthenticationProcessingFilter 抽象类 /*** 调用 #requiresAuthentication(HttpServletRequest, HttpServletResponse) 决定是否需要进行验证操作。* 如果需要验证,则会调用 #attemptAuthentica

Spring Security 从入门到进阶系列教程

Spring Security 入门系列 《保护 Web 应用的安全》 《Spring-Security-入门(一):登录与退出》 《Spring-Security-入门(二):基于数据库验证》 《Spring-Security-入门(三):密码加密》 《Spring-Security-入门(四):自定义-Filter》 《Spring-Security-入门(五):在 Sprin

Java架构师知识体认识

源码分析 常用设计模式 Proxy代理模式Factory工厂模式Singleton单例模式Delegate委派模式Strategy策略模式Prototype原型模式Template模板模式 Spring5 beans 接口实例化代理Bean操作 Context Ioc容器设计原理及高级特性Aop设计原理Factorybean与Beanfactory Transaction 声明式事物

Java进阶13讲__第12讲_1/2

多线程、线程池 1.  线程概念 1.1  什么是线程 1.2  线程的好处 2.   创建线程的三种方式 注意事项 2.1  继承Thread类 2.1.1 认识  2.1.2  编码实现  package cn.hdc.oop10.Thread;import org.slf4j.Logger;import org.slf4j.LoggerFactory

深入探索协同过滤:从原理到推荐模块案例

文章目录 前言一、协同过滤1. 基于用户的协同过滤(UserCF)2. 基于物品的协同过滤(ItemCF)3. 相似度计算方法 二、相似度计算方法1. 欧氏距离2. 皮尔逊相关系数3. 杰卡德相似系数4. 余弦相似度 三、推荐模块案例1.基于文章的协同过滤推荐功能2.基于用户的协同过滤推荐功能 前言     在信息过载的时代,推荐系统成为连接用户与内容的桥梁。本文聚焦于

JAVA智听未来一站式有声阅读平台听书系统小程序源码

智听未来,一站式有声阅读平台听书系统 🌟&nbsp;开篇:遇见未来,从“智听”开始 在这个快节奏的时代,你是否渴望在忙碌的间隙,找到一片属于自己的宁静角落?是否梦想着能随时随地,沉浸在知识的海洋,或是故事的奇幻世界里?今天,就让我带你一起探索“智听未来”——这一站式有声阅读平台听书系统,它正悄悄改变着我们的阅读方式,让未来触手可及! 📚&nbsp;第一站:海量资源,应有尽有 走进“智听