一、历史背景与设计驱动因素
1.1 移动设备硬件发展的挑战
在Android早期版本(2.2 - 4.4),移动设备普遍存在内存容量小(512MB以下)、CPU性能弱的特点。Dalvik针对这种资源受限环境设计,采用JIT即时编译技术以减少安装包体积和内存占用。随着硬件性能提升(多核CPU、大容量内存普及),用户对应用流畅度和响应速度要求提高,促使Android 5.0引入ART运行时,通过AOT预编译技术彻底改变执行模式。
1.2 应用生态发展的需求
Android应用数量和复杂度快速增长,Dalvik的JIT编译在长时间运行后会出现性能下降,尤其在游戏、视频编辑等高性能需求场景下表现不佳。ART的设计目标是通过预编译优化代码执行效率,同时降低运行时CPU负载,为开发者提供更接近原生应用的性能体验。
1.3 技术演进的必然性
Java虚拟机技术在桌面端已成熟,但移动端需要更适配的执行环境。Dalvik作为过渡方案解决了初期兼容性问题,而ART的出现标志着Android运行时向专业化、定制化方向发展,体现了对移动场景的深度优化。
二、字节码执行核心策略差异
2.1 Dalvik的JIT即时编译
Dalvik的JIT编译器位于dalvik/vm/Jit.cpp
,核心逻辑通过JitCompileMethod
函数实现:
// dalvik/vm/Jit.cpp
bool JitCompileMethod(const Method* method) {
// 检查方法是否符合编译条件(如调用次数阈值)
if (!shouldCompile(method)) {
return false;
}
// 生成中间表示(IR)
IRGenerator irGen(method);
irGen.generateIR();
// 优化IR(简单常量折叠等)
optimizeIR(irGen.getIR());
// 生成机器码
MachineCode* machineCode = generateMachineCode(irGen.getIR());
// 替换原字节码执行路径
replaceBytecodeWithMachineCode(method, machineCode);
return true;
}
JIT采用"解释-编译"混合模式:首次执行时解释执行,当方法调用次数超过阈值后触发编译。这种策略在启动速度和内存占用间取得平衡,但存在编译延迟和优化不足问题。
2.2 ART的AOT预编译机制
ART的AOT编译由dex2oat
工具完成,核心代码位于art/tools/dex2oat/dex2oat.cc
:
// art/tools/dex2oat/dex2oat.cc
int main(int argc, char** argv) {
// 解析命令行参数,获取输入DEX文件和目标架构
CommandLineOptions options;
if (!options.Parse(argc, argv)) {
return -1;
}
// 加载DEX文件
DexFile dexFile(options.input_dex_file);
// 生成中间表示(HGraph)
HGraphBuilder hGraphBuilder(&dexFile);
HGraph* hGraph = hGraphBuilder.Build();
// 多阶段优化(全局常量传播、死代码消除等)
OptimizeHGraph(hGraph);
// 针对目标架构生成机器码
MachineCodeGenerator codeGen(hGraph, options.target_arch);
codeGen.GenerateCode();
// 输出包含机器码的OAT文件
WriteOatFile(options.output_oat_file, codeGen.GetMachineCode());
return 0;
}
AOT在应用安装阶段将DEX字节码编译为机器码,存储在.oat
文件中。执行时直接加载机器码,避免了运行时编译开销,显著提升启动速度和执行效率。
2.3 混合编译的进化(ART 7.0+)
从Android 7.0开始,ART引入JIT+AOT混合模式:
- 安装时进行部分AOT编译(快速路径)
- 运行时通过JIT优化热点代码
- 使用Profile-guided compilation记录运行时信息,在设备空闲时进行全量AOT编译 这种策略结合了AOT的快速启动和JIT的动态优化能力。
三、内存管理架构差异
3.1 Dalvik的堆内存模型
Dalvik采用线性堆内存布局,核心实现位于dalvik/vm/alloc/HeapSource.cpp
:
// dalvik/vm/alloc/HeapSource.cpp
HeapSource* HeapSource::CreateHeapSource() {
// 初始化堆内存大小(默认16MB)
size_t initialSize = kDefaultHeapSize;
// 通过mmap系统调用分配连续内存
void* heapMemory = mmap(nullptr, initialSize, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (heapMemory == MAP_FAILED) {
// 内存分配失败处理
return nullptr;
}
// 创建堆内存管理对象
return new HeapSource(heapMemory, initialSize);
}
采用标记-清除垃圾回收算法,在dalvik/vm/gc/MarkSweep.cpp
实现:
// dalvik/vm/gc/MarkSweep.cpp
void MarkSweepGC::CollectGarbage() {
// 从根集合(栈、静态变量等)开始标记存活对象
MarkRoots();
// 扫描堆内存,清除未标记对象
SweepHeap();
// 压缩内存(可选)
CompactHeapIfNeeded();
}
这种设计在小内存设备上表现良好,但存在内存碎片问题。
3.2 ART的分代式内存管理
ART采用分代式堆内存设计,分为:
- 年轻代(Young Generation)
- 老年代(Old Generation)
- 大对象空间(Large Object Space)
堆初始化代码位于
art/runtime/gc/heap.cc
:
// art/runtime/gc/heap.cc
void Heap::Initialize() {
// 初始化年轻代(默认占堆的1/8)
youngGen_.Initialize(this, kYoungGenRatio * totalHeapSize_);
// 初始化老年代
oldGen_.Initialize(this, totalHeapSize_ - youngGen_.GetCapacity());
// 初始化大对象空间
largeObjectSpace_.Initialize(this);
}
垃圾回收采用分代策略:
- 年轻代使用复制算法(
art/runtime/gc/collector/scavenge.cc
) - 老年代使用标记-压缩算法(
art/runtime/gc/collector/marksweep.cc
) 分代设计有效减少了垃圾回收暂停时间,提升了应用响应性。
四、类加载机制差异
4.1 Dalvik的类加载实现
Dalvik的类加载器在dalvik/vm/ClassPath.cpp
实现:
// dalvik/vm/ClassPath.cpp
ClassObject* ClassPath::FindClass(const char* descriptor) {
// 遍历类路径(DEX文件列表)
for (const DexFile* dexFile : dexFiles_) {
// 在DEX文件中查找类
ClassObject* clazz = dexFile->FindClass(descriptor);
if (clazz != nullptr) {
return clazz;
}
}
return nullptr;
}
采用传统的双亲委托模型,但对DEX文件的处理较为简单,缺乏深度验证机制。
4.2 ART的类加载增强
ART的类加载器体系在art/runtime/class_linker.cc
实现:
// art/runtime/class_linker.cc
std::unique_ptr<Class> ClassLinker::LoadClass(ClassLoader* classLoader, const DexFile& dexFile, uint32_t classDefIndex) {
// 验证类定义合法性
if (!dexFile.IsValidClassDef(classDefIndex)) {
return nullptr;
}
// 解析类引用
ResolveClassReferences(dexFile, classDefIndex);
// 链接类(准备阶段)
LinkClass(classLoader, dexFile, classDefIndex);
// 初始化类(如果需要)
InitializeClassIfNeeded(classLoader, dexFile, classDefIndex);
return std::unique_ptr<Class>(CreateClassObject(classLoader, dexFile, classDefIndex));
}
增加了以下特性:
- 严格的类验证机制
- 支持多DEX文件加载(MultiDex)
- 更高效的类引用解析
五、JNI实现差异
5.1 Dalvik的JNI接口
Dalvik的JNI实现位于dalvik/vm/Jni.c
:
// dalvik/vm/Jni.c
JNIEXPORT jobject JNICALL dvmNewObject(JNIEnv* env, jclass clazz, jmethodID methodID, ...) {
// 检查类是否已初始化
if (!dvmIsClassInitialized(clazz)) {
dvmInitClass(clazz);
}
// 分配对象内存
Object* obj = dvmAllocObject(clazz, ALLOC_DONT_GC);
if (obj == nullptr) {
// 内存分配失败处理
return nullptr;
}
// 调用构造函数
va_list args;
va_start(args, methodID);
dvmCallMethodV((Method*)methodID, obj, args);
va_end(args);
return (jobject)obj;
}
JNI调用直接操作对象内存,缺乏严格的类型检查和安全防护。
5.2 ART的JNI增强
ART的JNI实现位于art/runtime/jni/java_vm_ext.cc
:
// art/runtime/jni/java_vm_ext.cc
jobject JavaVMExt::NewObject(JNIEnv* env, jclass clazz, jmethodID methodID, ...) {
// 验证类和方法的有效性
if (!IsClassInitialized(clazz) || !IsValidMethodID(methodID)) {
ThrowException(env, "Invalid class or method");
return nullptr;
}
// 通过内存分配器创建对象
ObjPtr<mirror::Object> obj = runtime_->GetHeap()->AllocObject<mirror::Object>(clazz);
if (obj == nullptr) {
// 内存不足处理
ThrowOutOfMemoryError(env);
return nullptr;
}
// 调用构造函数
va_list args;
va_start(args, methodID);
InvokeDirectMethod(runtime_, env, methodID, obj, args);
va_end(args);
return env->ToLocal(obj.Ptr());
}
增加了:
- 严格的参数验证
- 异常处理机制
- 内存安全检查
- 线程安全优化
六、调试与性能分析支持
6.1 Dalvik的调试能力
Dalvik通过jdwp
实现调试支持,代码位于dalvik/vm/Jdwp.cpp
:
// dalvik/vm/Jdwp.cpp
void JdwpThread::HandleCommand() {
// 接收调试命令
JdwpPacket packet;
if (!ReceivePacket(&packet)) {
// 接收失败处理
return;
}
// 解析命令类型
switch (packet.GetCommandSet()) {
case JDWP_COMMAND_SET_THREAD:
HandleThreadCommand(packet);
break;
case JDWP_COMMAND_SET_CLASS:
HandleClassCommand(packet);
break;
// 其他命令处理
}
// 发送响应
SendResponsePacket(packet);
}
支持基本的断点调试和线程管理,但缺乏深度性能分析能力。
6.2 ART的调试增强
ART在调试支持上进行了全面升级:
- 集成Systrace进行系统级性能分析
- 支持Perfetto进行详细的运行时追踪
- 增强的JDWP实现(
art/runtime/jdwp/jdwp_thread.cc
)
// art/runtime/jdwp/jdwp_thread.cc
void JdwpThread::HandleCheckCapabilities(JdwpPacket* packet) {
// 检查调试器权限
if (!HasDebugPermission(packet->GetClientID())) {
SendErrorResponse(packet, JDWP_ERROR_INVALID_ACCESS);
return;
}
// 返回支持的功能列表
SendCapabilitiesResponse(packet);
}
提供更丰富的调试接口和安全机制。
七、安全机制差异
7.1 Dalvik的安全设计
Dalvik主要依赖Linux内核的进程隔离机制,自身安全防护较弱。在权限管理方面,通过AndroidManifest.xml
声明权限,但缺乏运行时检查。垃圾回收机制未对内存安全做特殊处理,存在缓冲区溢出风险。
7.2 ART的安全增强
ART在安全方面进行了大量改进:
- SELinux集成:在
art/runtime/selinux/selinux.cc
实现安全上下文管理
// art/runtime/selinux/selinux.cc
bool Selinux::CheckAccess(const std::string& type, const std::string& target, const std::string& perm) {
// 调用SELinux内核接口进行权限检查
return selinux_check_access(type.c_str(), target.c_str(), perm.c_str()) == 0;
}
- 内存安全:增加指针有效性检查和缓冲区溢出防护
- 代码签名验证:在应用安装阶段严格验证签名