Android的ART虚拟机是如何执行java方法的

本文详细阐述了Android系统中启动Java应用的流程,涉及Zygote进程、JNI调用、类加载与链接、ArtMethod的执行入口点,以及QuickCode和Interpreter模式的选择。重点介绍了oat文件和art文件在优化性能和安装体验中的作用。
摘要由CSDN通过智能技术生成

1 虚拟机执行入口

Android ART (Android Runtime) 从操作系统级别启动 Java 应用程序或服务的过程。

void AndroidRuntime::start(const char* className, const Vector<String8>& options, bool zygote)
{

    char* slashClassName = toSlashClassName(className != NULL ? className : "");
    jclass startClass = env->FindClass(slashClassName);
    jmethodID startMeth = env->GetStaticMethodID(startClass, "main",
        "([Ljava/lang/String;)V");
    env->CallStaticVoidMethod(startClass, startMeth, strArray);
}

过程概述

在 Android 系统中,当需要启动一个 Java 应用程序或服务时,操作系统首先需要创建一个运行环境,这通常是通过 zygote 进程实现的。Zygote 是一个特殊的守护进程,它预加载了 Android 运行时和常用类库,用于快速启动新的应用进程。一旦 zygote 准备好,它就会复制自身(通过 fork)来为新的应用创建一个进程。

AndroidRuntime::start 函数

这个函数是启动 Java 主类的关键。它接受一个类名作为参数,并通过 JNI 调用这个类的 main 方法。下面是这个过程的详细说明:

类名处理

char* slashClassName = toSlashClassName(className != NULL ? className : "");

将点分隔的 Java 类名转换为以斜杠分隔的形式,因为 JNI 使用的是斜杠分隔的类路径。

查找类

jclass startClass = env->FindClass(slashClassName);

使用 JNI 环境 env 查找指定的类。这个步骤涉及到类加载器,它会在应用的 APK 文件或其他存储位置查找并加载这个类。

获取方法 ID

jmethodID startMeth = env->GetStaticMethodID(startClass, "main", "([Ljava/lang/String;)V");

获取这个类的 main 方法的方法 ID。这里指定 main 方法必须是静态的,接受一个字符串数组参数,没有返回值(void)。

调用 Java 方法

env->CallStaticVoidMethod(startClass, startMeth, strArray);

调用 main 方法。strArray 是传递给 main 方法的参数,其内容应与 options 相对应。这一步是通过 JNI 桥接完成的。

JNI 到 ART 方法的调用

env->CallStaticVoidMethod 是 JNI 函数的一个指针,它指向 jni_internal.cc::CallStaticVoidMethodV(),这个函数处理具体的方法调用逻辑。

调用 ArtMethod
当 JNI 函数被调用时,它将进一步调用 ArtMethodInvoke() 方法。Invoke() 是 ART 中的一个核心函数,负责执行方法调用。

汇编代码跳转
Invoke() 方法会利用 entry_point_from_compiled_code_ 进行跳转。这通常指向一个汇编代码 art_quick_invoke_stub_internal。这段汇编代码负责将控制权转移给方法的实际执行代码(如果已经编译)或者解释器(如果代码尚未编译)。

2 LinkCode中设置ArtMethod入口点

在Java和Android的运行时环境中,类加载主要分为三个阶段:加载(Loading)、链接(Linking)、初始化(Initialization)。在链接阶段,ClassLinker::LinkCode` 的作用主要集中在准备方法的代码执行,具体包括:

  • 编译方法LinkCode 负责将方法的字节码编译成机器码。在ART中,这通常涉及到AOT编译(Ahead-of-Time),即在应用安装时就将字节码编译成机器代码,或者JIT编译(Just-In-Time),即在应用运行时编译。对于AOT,编译可能发生在应用的安装阶段或者通过后台编译服务。

  • 生成直接引用:除了编译代码外,LinkCode 还需要处理对类成员(如字段和方法)的直接引用。这意味着它将解析方法中使用的所有符号引用,将它们转换为直接引用,以便于在运行时提高执行效率。

  • 方法入口地址设置:编译完成后,LinkCode 会将方法的入口地址设置到方法表中的相应位置。这使得当运行时环境需要调用某个方法时,可以直接通过方法表找到其入口地址并执行。

在 ART 中,每一个函数都对应一个 ARTMethod 结构体。这个结构体包含了不同模式下的调用入口点:ArtMethod有如下2个入口地址
void* entry_point_from_jni_;
void* entry_point_from_quick_compiled_code_;

class ArtMethod {
  struct PtrSizedFields {
    void* data_;//与JNI有关
    void* entry_point_from_quick_compiled_code_;//java方法入口函数地址
  } ptr_sized_fields_;
};

其中entry_point_from_quick_compiled_code_保存的是非jni方法的入口地址,设置entry_point_from_compiled_code_是在Class_Linker.cc::LinkCode()完成的,指向一段Trampoline代码来间接执行。

if (method->IsStatic() && !method->IsConstructor()) {
  method->SetEntryPointFromQuickCompiledCode(GetQuickResolutionStub());
} else if (quick_code == nullptr && method->IsNative()) {
  method->SetEntryPointFromQuickCompiledCode(GetQuickGenericJniStub());
} else if (enter_interpreter) {
  method->SetEntryPointFromQuickCompiledCode(GetQuickToInterpreterBridge());
}

在 Android ART(Android Runtime)中,entry_point_from_quick_compiled_code_ 是方法执行的入口点,它决定了如何执行一个方法。这个入口点根据方法的属性和状态选择不同的执行路径。有四种主要的执行路径入口,每种都适用于特定的场景。以下是每种情况的详细解释:

art_quick_resolution_trampoline

这个入口是用于静态方法的,但不包括构造方法。静态方法由于不依赖于类的具体实例来执行,因此它们在类被加载时就需要被解析和准备好。

  • 条件method->IsStatic() && !method->IsConstructor()
  • 使用场景:当一个静态方法被调用,但尚未被解析(即首次调用或者类刚刚被加载)时,这个入口将会被使用。它负责解析方法的符号引用并链接到具体的代码实现。

art_quick_generic_jni_trampoline

这是为 JNI 方法设置的入口点,用于那些通过 JNI 接口与本地代码交互的方法。如果这些方法没有预编译的机器代码(quick code),则使用这个入口。

  • 条件quick_code == nullptr && method->IsNative()
  • 使用场景:当 JNI 方法没有直接的编译代码时,这个入口负责设置 JNI 环境,并调用相应的本地方法。

art_quick_to_interpreter_bridge

这个入口用于解释执行的情况,通常用于那些不经常执行或者还未被 JIT 编译的方法。

  • 条件enter_interpreter,符合解释器执行的条件
  • 使用场景:当方法不适合直接编译执行,或者编译执行的代价较高(如只执行一次的方法),或者代码仍在动态优化中,这个入口将被用来逐条解释执行字节码。

quick_code

如果以上条件都不满足,并且存在预编译的快速执行代码(quick code),则直接使用这些机器代码执行方法。

  • 条件:其他条件都不符合,并且 oat_method 中有对应的 quick code
  • 使用场景:对于频繁执行的热点代码,ART 会通过 Ahead-Of-Time (AOT) 编译或 Just-In-Time (JIT) 编译生成优化的机器代码。这些代码直接运行在硬件上,提供最佳的执行性能。

3 执行

在 Android ART (Android Runtime) 中,虚拟机通过一系列的桥接代码和入口点来处理 Java 方法的调用。下面图的起始点为invoke
在这里插入图片描述

详见《深入理解Android:Java虚拟机》ART10.2 解释执行

3.1 Quick Code和Interpreter 模式

Quick Code 模式
执行 ARM 汇编指令
在这种模式下,Dalvik 字节码被提前(Ahead-of-Time, AOT)编译成 ARM 汇编指令。这意味着在应用安装时,字节码就已经被转换成了机器代码。
由于代码是预编译的,运行时的性能得到了显著提升,因为不需要在每次应用运行时都进行字节码到机器代码的转换。

Interpreter 模式
由解释器解释执行 Dalvik 字节码:
在这种模式下,Dalvik 字节码在运行时被逐条解释执行。这种方式不需要预编译,可以更快地开始执行应用,但运行速度相对较慢。
这种模式通常用于应用的调试过程中,因为它允许开发者更容易地跟踪和调试代码。

两种模式之间的交互
混合执行:即使是在 quick code 模式中,也有一些类方法可能需要以 Interpreter 模式执行,反之亦然。这种混合执行模式允许 ART 在保持高性能的同时,提供更多的灵活性。

函数桥接:
artInterpreterToCompiledCodeBridge: 这个函数允许从 Interpreter 模式切换到 quick code 模式。
GetQuickToInterpreterBridge: 这个函数允许从 quick code 模式切换到 Interpreter 模式。

如下/art/runtime/runtime.cc代码是检查运行时选项中是否设置了解释执行模式(Opt::Interpret)。如果设置了该选项,代码将调用 GetInstrumentation()->ForceInterpretOnly() 方法,强制 ART 进入只用解释执行的模式。

if (runtime_options.GetOrDefault(Opt::Interpret)) {
    GetInstrumentation()->ForceInterpretOnly();
}

设置 ART 为解释执行模式
从上面的代码片段中可以看出,ART 的解释执行模式是通过检查启动选项 Opt::Interpret 来设置的。如果这个选项被设置为 true,那么 ART 会在整个运行期间强制使用解释器来执行所有的 Java 方法。

为了在实际使用中设置 ART 为解释执行模式,需要在启动 ART 时提供相应的选项。这通常可以通过命令行参数来实现,例如,在启动 Android 设备或启动特定的 Android 应用时,可以指定相关的运行时参数。

adb shell setprop debug.art.interpret-only true
或者在启动应用时指定相关的 VM 参数:

am start --es “android.vm.dex2oat-flags” “–interpreter-only” package.name/Activity
这些命令设置 ART 运行时在处理 Java 方法时只使用解释器,而不进行 JIT 或 AOT 编译

3.2 Execute函数

ART的解释执行主要在解释器部分处理,这通常在 interpreter 目录下的某个文件中实现,如 interpreter/interpreter_common.h 或 interpreter/interpreter.cc。ART 中的 ArtMethod 结构体包含了方法的所有元数据,包括方法名。可以通过这个结构体获取当前方法的名称。

如下函数体的核心功能是通过解释执行或 JIT 编译执行方法,同时处理相关的事件和异常。

 
249  NO_STACK_PROTECTOR
250  static inline JValue Execute(
251      Thread* self,
252      const CodeItemDataAccessor& accessor,
253      ShadowFrame& shadow_frame,
254      JValue result_register,
255      bool stay_in_interpreter = false,
256      bool from_deoptimize = false) REQUIRES_SHARED(Locks::mutator_lock_) {
257    DCHECK(!shadow_frame.GetMethod()->IsAbstract());
258    DCHECK(!shadow_frame.GetMethod()->IsNative());
259  
260    // We cache the result of NeedsDexPcEvents in the shadow frame so we don't need to call
261    // NeedsDexPcEvents on every instruction for better performance. NeedsDexPcEvents only gets
262    // updated asynchronoulsy in a SuspendAll scope and any existing shadow frames are updated with
263    // new value. So it is safe to cache it here.
264    shadow_frame.SetNotifyDexPcMoveEvents(
265        Runtime::Current()->GetInstrumentation()->NeedsDexPcEvents(shadow_frame.GetMethod(), self));
266  
267    if (LIKELY(!from_deoptimize)) {  // Entering the method, but not via deoptimization.
268      if (kIsDebugBuild) {
269        CHECK_EQ(shadow_frame.GetDexPC(), 0u);
270        self->AssertNoPendingException();
271      }
272      ArtMethod *method = shadow_frame.GetMethod();
273  
274      // If we can continue in JIT and have JITed code available execute JITed code.
275      if (!stay_in_interpreter && !self->IsForceInterpreter() && !shadow_frame.GetForcePopFrame()) {
276        jit::Jit* jit = Runtime::Current()->GetJit();
277        if (jit != nullptr) {
278          jit->MethodEntered(self, shadow_frame.GetMethod());
279          if (jit->CanInvokeCompiledCode(method)) {
280            JValue result;
281  
282            // Pop the shadow frame before calling into compiled code.
283            self->PopShadowFrame();
284            // Calculate the offset of the first input reg. The input registers are in the high regs.
285            // It's ok to access the code item here since JIT code will have been touched by the
286            // interpreter and compiler already.
287            uint16_t arg_offset = accessor.RegistersSize() - accessor.InsSize();
288            ArtInterpreterToCompiledCodeBridge(self, nullptr, &shadow_frame, arg_offset, &result);
289            // Push the shadow frame back as the caller will expect it.
290            self->PushShadowFrame(&shadow_frame);
291  
292            return result;
293          }
294        }
295      }
296  
297      instrumentation::Instrumentation* instrumentation = Runtime::Current()->GetInstrumentation();
298      if (UNLIKELY(instrumentation->HasMethodEntryListeners() || shadow_frame.GetForcePopFrame())) {
299        instrumentation->MethodEnterEvent(self, method);
300        if (UNLIKELY(shadow_frame.GetForcePopFrame())) {
301          // The caller will retry this invoke or ignore the result. Just return immediately without
302          // any value.
303          DCHECK(Runtime::Current()->AreNonStandardExitsEnabled());
304          JValue ret = JValue();
305          PerformNonStandardReturn(self,
306                                   shadow_frame,
307                                   ret,
308                                   instrumentation,
309                                   accessor.InsSize(),
310                                   /* unlock_monitors= */ false);
311          return ret;
312        }
313        if (UNLIKELY(self->IsExceptionPending())) {
314          instrumentation->MethodUnwindEvent(self,
315                                             method,
316                                             0);
317          JValue ret = JValue();
318          if (UNLIKELY(shadow_frame.GetForcePopFrame())) {
319            DCHECK(Runtime::Current()->AreNonStandardExitsEnabled());
320            PerformNonStandardReturn(self,
321                                     shadow_frame,
322                                     ret,
323                                     instrumentation,
324                                     accessor.InsSize(),
325                                     /* unlock_monitors= */ false);
326          }
327          return ret;
328        }
329      }
330    }
331  
332    ArtMethod* method = shadow_frame.GetMethod();
333  
334    DCheckStaticState(self, method);
335  
336    // Lock counting is a special version of accessibility checks, and for simplicity and
337    // reduction of template parameters, we gate it behind access-checks mode.
338    DCHECK_IMPLIES(method->SkipAccessChecks(), !method->MustCountLocks());
339  
340    VLOG(interpreter) << "Interpreting " << method->PrettyMethod();
341  
342    return ExecuteSwitch(
343        self, accessor, shadow_frame, result_register, /*interpret_one_instruction=*/ false);
344  }

这段代码是 Android ART (Android Runtime) 的一部分,用于执行 Java 方法。这个函数体的核心功能是通过解释执行或 JIT 编译执行方法,同时处理相关的事件和异常。273行可以插入打印函数名的方法以便用于打印当前执行的 Java 方法的名称。

以下是对该代码段的详细解释:

  • 函数定义和参数

  • Thread* self: 当前线程的引用。

  • const CodeItemDataAccessor& accessor: 用于访问方法的代码项,包括局部变量和指令。

  • ShadowFrame& shadow_frame: 代表当前方法的栈帧,用于管理局部变量和方法执行状态。

  • JValue result_register: 用于存储方法返回值的变量。

  • bool stay_in_interpreter: 指示是否应保持在解释器中执行,即使存在 JIT 编译的代码。

  • bool from_deoptimize: 表示此方法调用是从优化代码中退化回来的。

  • 检查方法属性
    这里使用 DCHECK 确保当前方法不是抽象的也不是本地的,因为这些类型的方法不能直接通过解释器执行。

  • 缓存事件通知需求

    设置是否需要在 Dex PC(程序计数器)改变时通知,这是性能优化的一部分,避免频繁检查。

  • JIT 编译执行
    如果环境允许,尝试使用 JIT 编译的代码执行方法,而不是继续在解释器中执行。 如果有 JIT 编译的代码,那么执行 JIT 编译的代码,并在执行前后进行适当的栈帧管理。

  • 方法进入和退出事件
    如果存在方法进入事件的监听器,或者需要强制弹出栈帧,则触发相应的事件。

  • 异常处理和非标准退出
    如果在执行过程中发生异常或需要进行非标准退出(例如由于强制弹出栈帧),则处理这些情况并返回相应的结果。

  • 继续解释执行
    如果没有使用 JIT 编译的代码执行,则继续使用解释器执行方法。
    ExecuteSwitch是执行解释操作的函数,它负责按照字节码指令逐一执行。

4 oat和art文件

oat文件本质上是一个ELF文
件,它将OAT文件格式内嵌在ELF文件里

可以将art文件看作是很多对象通过类似序列化的方法保存到文件里而得来的。当art文件通过mmap加载到内存时,这些文件里的信息就能转换成对象直接使用。

如果在dex2oat时不生成art文件的话,那么上述这些对象只能等到程序运行时才创建,如此将耗费定的运行时间。考虑到boot包含的内容非常多(13个jar包,14个dex文件),所以在Android 7.0中,boot镜像必须生成art文件。而对app来说,默认只生成oat文件。其art文件会根据profile的情况由系统的后台服务择机生成。这样能减少安装的时间,提升用户体验。

  • 30
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Android Java虚拟机ART是一种全新的虚拟机,它是Android Lollipop操作系统中默认的运行时环境,相比于旧版的Dalvik虚拟机,它能够提供更好的性能和体验。 ART的最大特点是在使用前将字节码转换为机器码,这样可以在运行时减少解释和编译的时间,从而提高应用程序的响应速度。此外,在ART中也引入了一些新的技术,例如预编译、AOT和热编译等,这些都能够优化应用程序的启动速度和运行效率。 在深入研究ART之前,必须先了解Java虚拟机(JVM)的基本概念和原理。JVM是一种运行Java程序的虚拟机,将Java源代码转换为字节码,再由虚拟机解释执行。同样地,ART也采用相同的原理来实现应用程序的运行,只不过它将字节码转换为机器码,从而提高了运行速度和效率。 因此,熟悉Java虚拟机ART的工作原理,能够帮助开发者更好地理解和优化应用程序的性能。此外,对于一些需要高效运行的应用场景(例如游戏、图像处理等),ART也能够提供更好的运行环境,提高应用程序的稳定性和响应能力。 总之,深入理解Android Java虚拟机ART对于开发者来说非常重要,尤其是在需要优化应用程序性能和响应速度的情况下。只有深入了解ART的原理和特点,才能更好地应用它来提高应用程序的运行效率。 ### 回答2: Android Java虚拟机ARTAndroid系统中最新的运行时环境。相较于旧有的Dalvik虚拟机ART采用预编译技术,将应用程序字节码一次性编译为本地机器码,提高了应用程序的运行效率和响应速度,同时也降低了资源消耗。因此,深入理解Android Java虚拟机ART对于Android开发者来说是非常必要的。 深入学习ART,我们需要了解其内部运作机制,包括Dex编译、ClassLoader、Garbage Collection等关键概念。ART采用了AOT和JIT两种编译方式,也采用了一些新的优化方法,如Profile Guided Optimization(PGO)、Image Space Estimation等,以提高应用程序的可执行性和启动时间。 ART的ClassLoader实现了一种高效的动态加载技术,它使得应用程序可以在运行时动态更新代码库、插件包等,从而大大扩展了应用程序的功能和灵活性。同时ART的ClassLoader也是构建Android虚拟化环境的基础,它可以从不同的应用程序中加载开放的类,并为每个应用程序提供一个独立的执行环境。 最后,ART的Garbage Collection机制实现了一种全新的分代收集算法,将耗费大量时间的垃圾回收操作分散到不同的虚拟机堆内,从而大幅度提高了应用程序的性能和响应速度。 总之,深入理解Android Java虚拟机ART对于Android开发者来说十分关键,它将为我们提供更为深入的开发思路和方法,使我们的应用程序更加高效,同时也为我们的Android应用程序开发添上浓墨重彩的一笔。 ### 回答3: Android Java虚拟机ART (Android Runtime)是安卓4.4系统及以上版本中的默认虚拟机。相比原先的Dalvik虚拟机ART可实现更高的性能和更好的系统稳定性。 ART的核心思想是AOT( Ahead of Time)编译。它在应用程序安装的时候就将应用程序代码转换成本地机器指令并编译成机器代码,以C/C++库的形式保存在设备上。相比Dalvik,在应用程序的执行过程中省去了JIT编译的时间和运算,能够提高应用程序的运行速度。 除此之外,ART还有几个重要的特点: 1. 超低功耗:ART的AOT编译技术使得应用执行时可以直接使用本地机器指令,减少了CPU的时间浪费,使得应用程序的功耗更低。 2. 内存占用减少: ART允许应用程序在运行时进行类加载,实现更高效的内存管理。相比Dalvik虚拟机ART在处理内存和垃圾回收时能够更好地利用系统资源,减少了应用程序所占用的内存。 3. 支持快速应用开发:通过使用ART虚拟机可以通过模块形式快速开发出具有更好体验的应用程序。 总之,深入理解Android Java虚拟机ART需要着重理解ART AOT编译原理、内存管理机制、以及对快速应用开发的支持。这些特点的综合优势使得安卓应用程序能够实现更快的运行速度、更低的功耗、更快的开发效率和更好的用户体验。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值