【Android进阶笔记】虚拟机,Android工程师面试题及答案

1.1. 与 JVM 的区别

1.1.1. 架构区别

【JVM 基于栈】

  • 零地址指令为主,执行过程依赖于操作栈。指令数更多,指令集更小,编译器易实现。
  • 设计和实现更简单,适用于资源受限的系统。
  • 不需要硬件支持,可移植性好,更好实现跨平台。

【DVM 基于寄存器】

  • 多地址指令为主,执行过程依赖于寄存器。指令数更少,指令集更大,执行性能好。
  • 没有大量的出入栈指令,且指令更紧凑简洁。
  • 指令集架构完全依赖硬件,可移植性差,难跨平台。

【指令对比】

int x = 4; int y = 2; 计算 (x + y) * (x - y)

// JVM 字节码指令(基于栈)
0: iload_1 // 从局部变量1号槽位读取变量x的值到操作数栈 [4
1: iload_2 // 从局部变量2号槽位读取变量y的值到操作数栈 [4,2
2: iadd // 栈顶两个元素相加并保存到操作数栈(x+y) [6
3: iload_1 // 从局部变量1号槽位读取变量x的值到操作数栈 [6,4
4: iload_2 // 从局部变量2号槽位读取变量y的值到操作数栈 [6,4,2
5: isub // 栈顶两个元素相减并保存到操作数栈(x-y) [6,2
6: imul // 栈顶两个元素相乘并保存到操作数栈 [12

// DVM 字节码指令(基于寄存器)
0000: add-int v0, v3, v4 // 将v3和v4寄存器的值相加并保存到v0寄存器(x+y)
0002: sub-int v1, v3, v4 // 将v3和v4寄存器的值相减并保存到v1寄存器(x-y)
0004: mul-int/2addr v0, v1 // 将v0和v1寄存器的值相乘并保存到v0寄存器

1.1.2. 字节码区别

  • Java 类被编译成一个或多个 .class 文件。
  • 每个 .class 文件里面包含了该类的常量池、类信息、属性等信息。

【JVM 执行 class 字节码】

  • 多个 .class 文件打包成 .jar 文件。
  • 加载 .jar 文件时会加载里面的全部 .class 文件,比较慢。
  • .jar 文件是一个打包压缩文件,I/O 操作频繁,类的查比较慢。

【DVM 执行 dex 字节码】

  • 多个 .class 文件打包成 .dex 文件,并把包含的信息全部整合在一起,并去除冗余信息。
  • 加载 .dex 文件时会加载里面的全部信息。
  • .dex 文件是一个整合文件,I/O 操作少,类的查比较快。

1.1.3. 其他区别

  • DVM 允许在有限的内存中同时运行多个进程。
  • Android 中的每一个应用都运行在一个 DVM 实例中,拥有独立的进程空间,可以防止虚拟机崩溃时所有程序都被关闭。
  • DVM 由 Zygote 创建和初始化。
  • 每当系统需要创建一个应用程序时,Zygote 就会 fock 自身,快速地创建和初始化一个 DVM 实例,用于应用程序的运行。
  • DVM 有共享机制。
  • 不同应用之间在运行时可以共享相同的类,拥有更高的效率。
  • DVM 在2.2版本以前只使用解释器,没有使用 JIT 编译器。
  • 早期的 DVM 每次执行代码都需要解释器将 dex 代码编译成机器码后再处理,效率不高。
  • JIT 编译器会对多次运行的代码(热点代码)进行编译,生成相当精简的本地机器码(NativeCode),下次执行到相同逻辑的时候,直接使用编译之后的本地机器码, 而不需要再编译。

1.2. DVM 运行时堆

1.2.1. COW 策略

COW(copy-on-write)即写时复制。

  • 当多个调用者同时请求一个数据时,会同时获取同一个指针指向该数据。
  • 当任意一个调用者想对数据内容进行修改时,会复制一份数据给该调用者,其他调用者仍然指向原来的数据。
  • 如果全部调用者都只读不写数据,则数据不会被复制,永远只有一份,节省存储空间。

1.2.2. 运行时堆

Android 系统启动后,第一个进程 Zygote 会创建第一个 Dalvik 虚拟机,维护了一个 Zygote 堆。第一个应用程序进程创建(从 Zygote 进程 fork)时,会使用 COW 策略,创建 Active 堆并把 Zygote 堆中的内容复制进去。

  • Zygote 堆:Zygote 进程在启动过程中预加载的类、资源和对象,可以在 Zygote 进程和应用程序进程中长期共享,节约内存。
  • Active 堆:创建以后,为 Zygote 进程和应用程序进程分配内存空间,非进程共享,每个进程独立一份,减少对 Zygote 堆写,减少 COW 的发生。
  • Card Table:用于 DVM Concurrtent GC,当第一次进行垃圾标记后,记录垃圾信息。
  • Heap Bitmap:分为两个,Live Bitmap 用来记录上次 GC 存活的对象,Mark Bitmap 用来记录这次 GC 存活的对象。
  • Mark Stack:在 GC 的标记阶段使用的,用来遍历存活的对象。

1.2.3. 垃圾回收

Zygote 堆中不会触发 GC,Active 堆使用并发标记清除(Concurrent-Mark-Sweep)算法进行 GC。

【Mark 阶段】

通过递归,从 GC Roots 开始标记被引用的对象。为了避免 Stop-The-World,采用 GC 线程和其他线程并发执行,分为两步:

  1. 只标记 GC Roots 对象(在 GC 过程开始的时刻,被全局变量、栈变量、寄存器对象引用的对象)。
  • 这个阶段会短时间 Stop-The-World,防止这些 GC Roots 对象在此过程中再去引用其他对象。
  1. 通过这些 GC Roots 对象的引用关系,找到并标记其他正在使用的对象。
  • 这个阶段并发执行,需要把其他线程对对象的修改记录到 Card Table(未修改为 clean,修改过为 dirty)。
  • 执行结束后,需要再次使用 GC 线程对这些发生修改的对象再次标记(Stop-The-World,量少,速度快)。

【Sweep 阶段】

  • GC 线程和用户线程同时并发执行,同时 GC 线程开始对为标记的区域做清扫,回收所有的垃圾对象。
  • 清除 Live Bitmap 中有,Mark Bitmap 中没有的对象,即为垃圾对象。

【缺点】

  • 对 CPU 资源敏感:在并发阶段,它虽然不会导致用户线程停顿,但会因为占用了一部分线程(或者说 CPU 资源)而导致应用程序变慢,总吞吐量会降低。
  • 无法处理浮动垃圾:在并发清除时,用户线程新产生的垃圾,称为浮动垃圾。

1.3. GC 日志

在 DVM 中每次垃圾收集都会将 GC 日志打印到 logcat 中,具体格式为: <GC_Reason> <Amount_freed>, <Heap_stats>, <External_memory_stats>, <Pause_time>

1.3.1. GC 原因

GC_Reason 指的是引起 GC 的原因,有以下几种。

  • GC_CONCURRENT:当堆开始填充时,并发 GC 可以释放内存。
  • GC_FOR_MALLOC:当堆内存已满时, App尝试分配内存而引起的 GC,系统必须停止 App 并回收内存。
  • GC_HPROF_DUMP_HEAP:当你请求创建 HPROF 文件来分析堆内存时出现的 GC。
  • GC_EXPLICIT:显式的 GC,例如调用 System.gc()(应该避免调用显式的 GC,信任 GC 会在需要时运行)。
  • GC_EXTERNAL_ALLOC:仅适用于 API 级别小于等于 10,且用于外部分配内存的 GC。

1.3.2. 其他字段

Amount_freed:本次 GC 释放内存的大小。

Heap_stats:堆的空闲内存百分比(已用内存 / 堆总内存)。

External_memory_stats:API 小于等于级别 10 的内存分配(已分配的内存 / 引起 GC 的阈值)。

Pause_time:暂停时间,堆越大暂停时间越长。并发暂停时间会显示两个,一个是垃圾收集开始时, 另一个是垃圾收集快要完成时。

1.3.3. 日志示例

D/dalvikvm: GC_CONCURRENT freed 2012K, 63% free 3213K/9291K, external 4501K/5161K, paused 2ms+2ms

本次 GC 原因是 GC_CONCURRENT;释放内存 2012KB;堆空闲内存占比 63%,已用3213KB,总内存为 9291KB;暂停总时长 4ms。


2. ART 虚拟机

ART(Android Runtime)虚拟机于 Android 4.4 发布,Android 5.0 中默认使用,用来代替 Dalvik 虚拟机。

2.1. 与 DVM 的区别

2.1.1. 运行机制区别

【DVM 基于 JIT 运行】

运行程序时使用解释器执行,同时将热点代码通过 JIT 编译器编译成机器码,并缓存到 jit code cache ,再执行时就无需解释直接运行。

【ART 基于 AOT 运行】

  • **Android 7.0 以前:**安装 APK 时把全部字节码进行 AOT 编译成机器码 .oat 文件,并存储到磁盘,程序运行时不需要编译直接使用。
  • **Android 7.0 开始:**安装 APK 时不进行全量编译,运行程序时使用解释器执行。
  • 将热点代码进行 JIT 编译成机器码,并缓存到 jit code cache,再执行时就无需解释直接运行。
  • 把经过 JIT 编译的热点方法记录到 Profile 配置文件中。
  • 当设备闲置和充电时,会启动编译守护进程,根据 Profile 文件把热点方法进行 AOT 编译成机器码 .oat 文件,并存储到磁盘待下次运行时直接使用。

总结

最后为了帮助大家深刻理解Android相关知识点的原理以及面试相关知识,这里放上相关的我搜集整理的Android开发中高级必知必会核心笔记,共计2968页PDF、58w字,囊括Android开发648个知识点,我把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包知识脉络 + 诸多细节。


以上分享【Android开发中高级必知必会核心知识笔记】七大模块整套学习资料均免费分享,需要的小伙伴,我已经上传到GitHub了,大家自取就可以了。白嫖可以,别忘了给我点个关注哈。

【Android开发中高级必知必会核心知识笔记】

网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。希望这份系统化的技术体系对大家有一个方向参考。

网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。希望这份系统化的技术体系对大家有一个方向参考。

2021年虽然路途坎坷,都在说Android要没落,但是,不要慌,做自己的计划,学自己的习,竞争无处不在,每个行业都是如此。相信自己,没有做不到的,只有想不到的。

虽然面试失败了,但我也不会放弃入职字节跳动的决心的!建议大家面试之前都要有充分的准备,顺顺利利的拿到自己心仪的offer。
那么很难做到真正的技术提升。希望这份系统化的技术体系对大家有一个方向参考。

2021年虽然路途坎坷,都在说Android要没落,但是,不要慌,做自己的计划,学自己的习,竞争无处不在,每个行业都是如此。相信自己,没有做不到的,只有想不到的。

虽然面试失败了,但我也不会放弃入职字节跳动的决心的!建议大家面试之前都要有充分的准备,顺顺利利的拿到自己心仪的offer。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值