一、工厂的诞生:Dalvik 与 Java 虚拟机的差异之谜
想象 Android 系统是一个大型电子设备工厂,每个应用都是工厂里的独立车间。Dalvik 虚拟机就是管理这些车间运转的 "智能调度系统",而它的 "表哥"Java 虚拟机则在另一个大型工厂(Java 生态)里工作。
1.1 不同的生产流水线:指令集的秘密
Java 虚拟机的生产线是 "基于堆栈" 的:就像用托盘传递零件,每次取放都需要先放到托盘(堆栈)上,再从托盘取走。对应的指令如load
和store
就像 "把零件放到托盘" 和 "从托盘取零件",虽然灵活但步骤多。
java
arduino
// Java字节码示例(基于堆栈)
iload_0 // 从堆栈加载第0个局部变量
iload_1 // 加载第1个局部变量
iadd // 执行加法,结果放回堆栈
Dalvik 虚拟机的生产线是 "基于寄存器" 的:就像工人直接用手传递零件,寄存器就是工人的手,可以直接操作零件。指令长度更长(2-6 字节),但减少了来回搬运的步骤。
plaintext
arduino
// Dalvik字节码示例(基于寄存器)
add-int v0, v1, v2 // 直接用寄存器v1+v2,结果存v0
invoke-virtual {v0}, Ljava/lang/String;.length:()I // 调用方法
1.2 打包方式的智慧:Dex 文件的压缩魔法
Java 工厂的每个零件(类)单独打包成.class
文件,就像每个零件用独立盒子装。而 Dalvik 工厂把多个零件(类)合并打包成.dex
文件,就像把同类零件放进一个大箱子,重复的标签(字符串)只贴一次。
plaintext
scss
// Dex文件生成流程
Java代码(.java) → javac → Java字节码(.class)
→ dx工具 → Dalvik字节码(.dex) → dexopt优化 → 优化的Odex文件
二、工厂的启动:Zygote 母工厂的复制魔法
2.1 母工厂的初始化:Zygote 的启动流程
Android 系统启动时会先创建一个 "母工厂"Zygote,它预加载了所有应用共享的核心资源(如 Java 类库、系统资源)。当需要启动一个新应用(如微信)时,Zygote 会通过 "复制" 机制创建子工厂,就像用复印机快速克隆出车间。
c++
rust
// Zygote启动核心代码(简化版)
void AndroidRuntime::start(const char* className, bool startSystemServer) {
// 1. 启动虚拟机
if (startVm(&mJavaVM, &env) != 0) return;
// 2. 找到启动类(如ZygoteInit)
jclass startClass = env->FindClass(slashClassName);
// 3. 调用main方法启动
jmethodID startMeth = env->GetStaticMethodID(startClass, "main", "([Ljava/lang/String;)V");
env->CallStaticVoidMethod(startClass, startMeth, strArray);
}
2.2 复制工厂的魔法:COW 机制的节能技巧
Zygote 复制出子工厂时,并不会立即复制所有资源,而是采用 "写时复制(COW)" 策略:就像多个车间先共享同一套蓝图,直到某个车间需要修改蓝图时才单独复制。这样大大减少了内存占用和启动时间。
plaintext
arduino
// 内存共享示意图
Zygote进程 ←─┬─ 共享Java核心库
System Server进程 ←─┼─ 共享Android核心库
应用进程 ←─┴─ 共享系统资源
三、工厂的内存管理:仓库管理员的烦恼
3.1 三类仓库的分工
- Java 对象仓库(Java Heap) :存放 Java 对象,大小有限制(如 16M-48M),由虚拟机自动管理。就像一个容量固定的货架,管理员(GC)会定期清理过期商品。
- 图片仓库(Bitmap Memory) :专门存放图片数据,在 Android 3.0 前放在 Native 仓库,之后并入 Java 仓库,方便统一管理。
- Native 仓库(Native Heap) :存放 C/C++ 代码分配的内存,无大小限制,但滥用会导致系统内存紧张,就像无节制扩张的仓库会挤爆工厂。
3.2 仓库清理工的进化:垃圾收集的升级
在 Android 2.3(GingerBread)之前,垃圾收集像 "全厂停工大扫除":所有生产线(线程)都要停止,清理时间可能超过 100ms,导致应用卡顿。
plaintext
c
// 旧版GC日志(Stop-the-world)
D/dalvikvm: GC_CONCURRENT freed 2049K, 65% free 3571K/9991K
之后的 GC 像 "分区轮流清理":大部分时间生产线继续运转,只暂停部分区域,清理时间缩短到 5ms 以内。
plaintext
arduino
// 新版GC日志(并发GC)
D/dalvikvm: GC_CONCURRENT paused 2ms+2ms // 仅暂停几毫秒
四、工厂的效率优化:JIT 即时编译的秘密
从 Android 2.2 开始,Dalvik 引入了 "熟练工人"JIT(即时编译):当某个生产流程(代码片段)频繁使用时,JIT 会把它编译成更高效的机器码,就像熟练工人记住了最优操作步骤。
c++
ini
// JIT编译核心逻辑(简化版)
void dvmInterpret(Thread* self, const Method* method, JValue* pResult) {
if (gDvm.executionMode == kExecutionModeJit) {
// 使用JIT编译后的代码执行
stdInterp = dvmMterpStd;
} else {
// 解释执行
stdInterp = dvmInterpretStd;
}
(*stdInterp)(self, &interpState);
}
与传统 Java 虚拟机(以方法为单位优化)不同,Dalvik 的 JIT 基于 "执行路径" 优化,就像工人不仅记住单个步骤,还记住了整个流程的最优顺序。
五、工厂的跨部门协作:JNI 注册的订单系统
当 Java 车间需要调用 C/C++ 车间的功能(如硬件加速)时,需要通过 JNI(Java Native Interface)建立 "订单系统"。
5.1 订单注册流程
-
Java 层下订单 :通过
System.loadLibrary("nanosleep")
加载 C 库。 -
C 层接单 :在
JNI_OnLoad
函数中注册订单处理函数。 -
订单匹配:将 Java 方法名和签名与 C 函数绑定。
java
java
// Java层代码示例
public class ClassWithJni {
static {
System.loadLibrary("nanosleep"); // 加载C库
}
private native int nanosleep(long seconds, long nanoseconds); // 声明Native方法
}
c
运行
arduino
// C层代码示例
static jint shy_luo_jni_ClassWithJni_nanosleep(JNIEnv* env, jobject clazz, jlong seconds, jlong nanoseconds) {
struct timespec req;
req.tv_sec = seconds;
req.tv_nsec = nanoseconds;
return nanosleep(&req, NULL); // 调用系统函数
}
// 订单表(方法映射)
static const JNINativeMethod method_table[] = {
{"nanosleep", "(JJ)I", (void*)shy_luo_jni_ClassWithJni_nanosleep}
};
// 注册订单
jint JNI_OnLoad(JavaVM* vm, void* reserved) {
JNIEnv* env;
if (vm->GetEnv((void**)&env, JNI_VERSION_1_4) == JNI_OK) {
jniRegisterNativeMethods(env, "shy/luo/jni/ClassWithJni", method_table, NELEM(method_table));
}
return JNI_VERSION_1_4;
}
5.2 订单处理的核心机制
当 Java 方法调用 Native 方法时,虚拟机会通过Method
结构体判断方法类型:如果是 Native 方法,直接调用 C 函数;如果是 Java 方法,则由解释器执行。
c++
rust
// 方法调用核心逻辑
void dvmCallMethodV(Thread* self, const Method* method, Object* obj, bool fromJni, JValue* pResult, va_list args) {
if (dvmIsNativeMethod(method)) {
(*method->nativeFunc)(self->curFrame, pResult, method, self); // 调用Native函数
} else {
dvmInterpret(self, method, pResult); // 解释执行Java方法
}
}
六、工厂的线程管理:工人团队的协作模式
每个 Dalvik 线程对应一个 Linux 线程,就像工厂里的每个工作组对应一个实际的生产线。
6.1 Java 层创建线程
java
typescript
// Java层创建线程
new Thread(new Runnable() {
@Override
public void run() {
// 线程任务
}
}).start();
c++
scss
// 底层实现(简化版)
static void Dalvik_java_lang_VMThread_create(const u4* args, JValue* pResult) {
Object* threadObj = (Object*)args[0];
dvmCreateInterpThread(threadObj, (int)stackSize); // 创建Dalvik线程
}
6.2 Native 层创建线程
c++
arduino
// Native层创建线程
status_t Thread::run(const char* name, int32_t priority, size_t stack) {
if (mCanCallJava) {
return createThreadEtc(_threadLoop, this, name, priority, stack, &mThread);
}
}
七、ART 虚拟机:工厂的智能化升级
虽然文档主要介绍 Dalvik,但 ART(Android Runtime)是 Android 4.4 引入的下一代虚拟机,就像工厂升级了智能生产线:
- 提前编译(AOT) :不再边运行边编译,而是安装时就把代码编译成机器码,像提前把所有生产流程写成详细手册。
- 更高效的 GC:引入分代垃圾收集,针对不同生命周期的对象采用不同策略,像把仓库分为 "高频区" 和 "低频区" 分别管理。
- 优化的内存管理:减少内存碎片,提高分配效率,像重新规划了仓库的货架布局。
总结:从 Dalvik 到 ART 的技术进化
Dalvik 虚拟机就像一个不断进化的智能工厂,通过寄存器指令集、Dex 文件优化、JIT 编译等技术提升效率;Zygote 复制机制和 COW 技术减少了资源消耗;JNI 机制实现了跨语言协作。而 ART 则是这个工厂的智能化升级版,通过提前编译和更高效的内存管理,让 Android 应用运行得更快、更流畅。理解这些原理,就能像熟练的工程师一样优化应用性能,解决内存问题,成为 Android 开发的高手。