Android 运行时涉及三层容易被混为一谈的概念:JVM/Dalvik/ART 的执行模型差异、应用进程/线程与虚拟机实例的关系,以及 ART TI 的作用。下面分三节讲清。


一、执行模型:JVM 基于栈,Dalvik/ART 基于寄存器

Java 虚拟机基于栈:基于栈的机器必须用指令来载入和操作栈上数据,所需指令更多;它运行的是 Java 字节码(Java 类会被编译成一个或多个 .class 字节码文件)。Dalvik 虚拟机基于寄存器,运行的是自定义的 .dex 字节码格式——Java 类被编译成 .class 文件后,会经过 dx 工具将所有 .class 文件转换成一个 .dex 文件,Dalvik 再从其中读取指令和数据;常量池已被修改为只使用 32 位的索引,以简化解释器。进程模型上,一个应用对应一个虚拟机实例、一个进程:所有 Android 应用的线程都对应一个 Linux 线程,运行在自己的沙盒中,不同应用在不同进程里运行,每个 Android 应用都被赋予一个独立的 Linux PID(app_*)。

三个容易踩的坑

  • 别把 .dex 当 .class 读。Dalvik/ART 执行的是 dx 合并后的 .dex,常量池已改成 32 位索引,分析字节码时要按 dex 格式而非 JVM 的 class 格式看。
  • 基于栈 vs 基于寄存器影响指令数。JVM 基于栈需要更多载入/操作栈的指令,Dalvik/ART 基于寄存器,这决定了同一段逻辑两边的指令密度不同。
  • 进程与虚拟机一一对应。一个应用一个虚拟机实例一个进程,静态变量属于该进程,换进程就是另一份。

适用边界:理解执行模型差异,是看懂后续「进程 / 线程 / 运行时」关系的前提;排查「为什么换进程配置丢失」「为什么 ART 上表现和 JVM 不同」时先回到这一层。


二、进程、线程与虚拟机实例:三个词不要混用

Android 里每个应用默认跑在独立的 Linux 进程里,进程内再跑 ART(或历史上的 Dalvik)虚拟机。Linux 进程是内核调度和内存隔离的单位,线程是进程内的执行流,共享同一地址空间;Dalvik/ART 线程通常一对一映射到 Linux pthread。不要把虚拟机实例、应用进程和 Java 线程三个词混用:多进程为了隔离和保活,多线程为了并发,代价完全不同。

三个容易踩的坑

  • 一个应用可以多个进程android:process 会再 fork,Application 会再走一遍,内存不共享。
  • 线程共享堆。Java 线程不是新的 Linux 进程,锁和泄漏都在同一块内存里。
  • 虚拟机不等于进程。进程是内核概念,ART 只是进程里解释和编译字节码的运行时。

适用边界:设计多进程、查共享内存失效或对比线程与进程开销时用这组区别。普通单进程 App 不必拆进程;出现「换个进程配置丢失」时先看是不是独立 process,而不是线程没同步。


三、ART TI:运行时给调试与性能工具的官方入口

ART TI 是 Android Runtime 的工具接口,给调试器、采样器和插桩一个稳定入口,而不绑死内部 JIT 结构。通过它可以得到线程、堆和编译信息,供 Android Studio 与系统 tracing 使用。对框架开发者,它是观测运行时的正规途径;对应用开发者,日常不必直接调用,但能解释为何某些探针在新系统上仍可用。版本间 API 会变,系统应用和工具链要跟 ROM;误用会让运行时变慢或不稳定。

两个容易踩的坑

  • 应用层优先官方 Profiler。日常不必直接调用 ART TI,用 Android Studio 官方 Profiler 即可。
  • 它是运行时基础设施,不是业务 SDK。把它放在「系统如何被观察」的知识里,比抄一段未文档化的符号更安全;了解 TI 有助于读 ART 源码和做深度性能工具。

附:运行时关键概念速查

概念 本质 注意
JVM 基于栈,执行 .class 字节码 指令数多于寄存器机
Dalvik/ART 基于寄存器,执行 dx 合并的 .dex 常量池 32 位索引简化解释器
Linux 进程 内核调度与内存隔离单位 一个应用一进程,独立 PID
线程 进程内执行流,共享地址空间 一对一映射 Linux pthread
虚拟机实例 进程里解释/编译字节码的运行时 ≠ 进程
ART TI 运行时观测官方入口 版本会变,应用层用官方 Profiler