美文网首页
效能优化笔记 JVM引擎执行总结

效能优化笔记 JVM引擎执行总结

作者: yjy239 | 来源:发表于2021-02-16 23:19 被阅读0次

    前言

    本文继续总结辉哥的第二周二三节课的总结。这两节课主要还是对JVM引擎执行,Gradle进行了初步的介绍和学习。

    注意正文中所有的对虚拟机的讨论都是基于Android 7.0 art虚拟机。

    如果遇到什么问题欢迎来到本文:https://www.jianshu.com/p/084b636c5cc4 互相讨论

    正文

    JVM 引擎执行

    JVM引擎是如何执行的。这里我们以art虚拟机为例子。先来看看整个核心原理图。

    首先JVM执行代码分为如下两种:

    • 1.解释执行,通俗一点的话语就是一遍执行一遍翻译字节码。常常我们会把这种执行方式和JIT(Just In time)联系起来说。JIT是解释执行的核心机制,当某个方法执行的次数达到了阈值(也就是阈值)就会翻译成机器码

    • 2.机器码执行 直接是机器码进行本地执行。其中一个核心的机制,也就是我们常说的AOT(Ahead Of Time), 运行前编译。也就是在安装的时候,把代码经过PMS的DexManager 跨进程执行dex2oat翻译成优化过的本地机器码,可以被机器识别。

    对于ART虚拟机来说,其实整个过程较为复杂,但是值得称赞的是,整个流程的封装设计做的不错,不纠结底层的细节理解起来没有想象的困难。

    说起方法执行,就必须提及一个概念,栈帧。

    ShadowFrame栈帧

    栈帧(Stack Frame)是用于支持虚拟机方法执行和调用的数据结构,他是虚拟机运行时在数据区中虚拟机栈的栈元素。栈帧保存了方法的局部变量表,操作数栈,动态链接和方法返回地址和一些额外信息。

    当一个方法从调用到运行完毕,实际上是一个栈帧从入栈到出栈的过程。

    在编译执行过程中,会在Class文件中保存好Code字段中记录好这个方法需要多少个局部变量,多少操作栈,方法的参数个数。通过这些数据就能决定了在当前虚拟机环境下,当前栈帧内存大小。

    一个线程中虚拟机栈的栈帧可能很长,只有在栈顶的栈帧才是活跃的状态,成为当前栈帧。与这个栈帧关联的方法称为当前方法。

    单单这么说概念,可能会不太明白。这里我们就挑出Android 7.0对应的虚拟机中的对应的数据结构来进一步阐述栈帧为何物:

    机器码执行相关的逻辑较为复杂和抽象,这里来聊聊解释执行中栈帧的原理。

    在ART虚拟机中,栈帧指代的数据结构为ShadowFrame

     private:
      ShadowFrame(uint32_t num_vregs, ShadowFrame* link, ArtMethod* method,
                  uint32_t dex_pc, bool has_reference_array)
          : link_(link), method_(method), result_register_(nullptr), dex_pc_ptr_(nullptr),
            code_item_(nullptr), number_of_vregs_(num_vregs), dex_pc_(dex_pc) {
        // TODO(iam): Remove this parameter, it's an an artifact of portable removal
        DCHECK(has_reference_array);
        if (has_reference_array) {
          memset(vregs_, 0, num_vregs * (sizeof(uint32_t) + sizeof(StackReference<mirror::Object>)));
        } else {
          memset(vregs_, 0, num_vregs * sizeof(uint32_t));
        }
      }
    
    ...
      // Link to previous shadow frame or null.
      ShadowFrame* link_;
      ArtMethod* method_;
      JValue* result_register_;
      const uint16_t* dex_pc_ptr_;
      const DexFile::CodeItem* code_item_;
      LockCountData lock_count_data_;  // This may contain GC roots when lock counting is active.
      const uint32_t number_of_vregs_;
      uint32_t dex_pc_;
      int16_t cached_hotness_countdown_;
      int16_t hotness_countdown_;
    
      // This is a two-part array:
      //  - [0..number_of_vregs) holds the raw virtual registers, and each element here is always 4
      //    bytes.
      //  - [number_of_vregs..number_of_vregs*2) holds only reference registers. Each element here is
      //    ptr-sized.
      // In other words when a primitive is stored in vX, the second (reference) part of the array will
      // be null. When a reference is stored in vX, the second (reference) part of the array will be a
      // copy of vX.
      uint32_t vregs_[0];
    
      DISALLOW_IMPLICIT_CONSTRUCTORS(ShadowFrame);
    };
    
    art-ShadowFrame家族.png

    能看到,ShadowFrame 本质上是被ManagerStack 管理所有的栈帧。

    class PACKED(4) ManagedStack {
     public:
      ManagedStack()
          : top_quick_frame_(nullptr), link_(nullptr), top_shadow_frame_(nullptr) {}
    
      void PushManagedStackFragment(ManagedStack* fragment) {
        // Copy this top fragment into given fragment.
        memcpy(fragment, this, sizeof(ManagedStack));
        // Clear this fragment, which has become the top.
        memset(this, 0, sizeof(ManagedStack));
        // Link our top fragment onto the given fragment.
        link_ = fragment;
      }
    
      void PopManagedStackFragment(const ManagedStack& fragment) {
        DCHECK(&fragment == link_);
        // Copy this given fragment back to the top.
        memcpy(this, &fragment, sizeof(ManagedStack));
      }
    
     ...
    
     private:
      ArtMethod** top_quick_frame_;
      ManagedStack* link_;
      ShadowFrame* top_shadow_frame_;
    };
    

    ManagerStack 托管着一个当前正在运行的ShadowFrame。并能通过ManagedStack中的link 对象知道上一次线程切换时候的帧布局,从而进行切换。

    栈帧的创建与虚拟机的内存划分

    上文说到栈帧属于哪个数据区域。 我们回顾一下c++ 编程中的内存四驱模型:

    • 1.栈区:由编译器自动分配释放,存放函数参数,局部变量。
    • 2.堆区:由编程者手动分配释放,如果不释放则造成内存泄露,只有进程被销毁时,才被OS回收
    • 3.数据区:存放全局变量,静态变量,常量字符等。只能被OS回收
    • 4.代码区域:存放代码的区域

    在java 虚拟机中内存的划分如下:

    • 1.方法区
    • 2.虚拟机栈
    • 3.本地方法栈
    • 4.堆
    • 5.程序计数器

    这一块我们通常称为运行时数据区,也就是JVM内存。

    在这一块内存中又分为共享内存,和线程私有内存:

    • 共享内存: 方法区,堆
    • 线程私有内存:虚拟机栈,本地方法栈,程序计数
    art-java内存划分.png

    我们现在只讨论JVM在Android的行为。实际上JVM的内存划分必定是建立在c++编程中四驱模型的架构上。

    为了彻底弄懂栈帧的的内存申请,让我们把JVM和四驱模型联系起来吧。

    apk编译产物

    先聊聊apk安装编译产物,当dexManager通过dex2oat进程把dex转化为如下几种文件:

    art-dex2oat生成的产物.png
    • dex 是把jar包(内含class文件)经过压缩后的产物

    • odex 则是在Android 5.0之前常用的,是App安装时通过dex2opt对apk包中dex文件进行了优化产物,之后运行App就加载odex文件,加速App响应时间

    • oat 是在Android 5.0之后,安装时候会对dex文件中的数据通过dex2oat翻译成本地机器码保存起来。本质上是一个elf格式的文件

    • art 是Android安装后生成的可选对象,一旦生成了则会获取odex中部分常用内容(长方法等)存储到elf文件中。并保存到ImageSpace中,通过这种方式加速App的运行效率。

    • vdex 是Android 8.0之后的产物。当dex经过了编译的校验后,则会把校验后代码方法都缓存到vdex中。

    而这几个文件,最终都会加载到内存中,对于Android高版本中,我们来讨论oat文件以及art文件。

    art文件的内存从属

    来看看art文件通过JVM加载到内存是一个什么形式:


    art-class_linker加载内存布局与art文件的解析.png art-Art2ImageSpace.png

    art文件实际上在App应用启动初期进行初始化JVM时候,会加载到一个为ImageSpace的内存空间中。这个内存实际上是属于c/c++四驱的堆,也就是说是通过malloc的方式为ImageSpace申请内存。

    当然,ImageSpace中存在两个十分重要字段:mem_maplive_bitmap。这两个实际上是通过mmap系统调用把art文件中重要的数据字段映射到内存中。

    其中mem_map 映射了art中的方法,属性,类表等核心对象。

    当生成了ImageSpace后,会把这个对象托付给JVM Heap对象进行管理,而这个对象就是JVM中堆:


    art-Heap.png

    而堆本质上也是通过malloc 从内存中申请出来的,也是属于c/c++四驱模型的堆一份子。

    oat文件的内存从属

    art文件只是作为辅助的作用,大部分的核心代码都是由oat文件进行管理。从apk安装产物一图可以得知,oat文件的结构粗略是可以分2个区域:象征Oat文件头部的OatHeader,以及象征Oat文件的内容单元OatDexFileOatDexFile在Oat中是数组的单元对象,他象征着被压缩到oat文件中的dex文件。

    art-Oat文件.png

    这里面的结构稍微有点复杂,但是还是可以一窥究竟:

    • 1.OatHeader 带了Oat文件的魔数,版本号等数据,当然也指向了Trampoline code 也就是只有boot.oat(framework.jar对应的oat)文件才有的直接调准方法

    • 2.OatDexFile 则是Dex文件本身经过翻译后,压缩到文件中的数据。可以从图中可以看到内含了dex文件对应的偏移量,class对应的偏移量

    • 3.DexFile OatDexFile 指向的dex偏移量就是指的DexFile对象,这个对象其实指代了dex文件

    • 4.TypeLookupTable 是一个哈希表,这个哈希表目的是快速通过类名找到dex对应class数组的偏移索引

    • 5.ClassOffsets 这是这个class二维偏移数组是指,第几个dex的第几个class

    • 6.OatClass 则是ClassOffsets 指向的class对象。其中包含了当前的class的状态(加载,校验,准备,解析,初始化),当前class的编译类型(是全部转化成机器码,还是部分转化机器码,还是压根没经过转化),class中的方法等等

    • 7.VmapTable 是指CompileMethod 中的 vmap_table_ 数组。CompileMethod 是dex2oat 进程处理dex中java方法的结果对象。其中 CompileMethodvmap_table_ 定长数组是一个映射表,反映了机器码用到的物理寄存器到dex中虚拟寄存器的映射关系.

    • 8.OatQuickMethodHeader数组 指向所有的Oat文件的方法,内含当前方法所需要的栈帧大小,vmap的偏移量,代码段数据结构的偏移量等信息。

    这些所有的数据最终都会加载到四驱模型的堆内存中。

    art和oat的关系
    art-Oat文件和art文件的关联.png

    能看到实际上在上一片文章提到过的ArtMethod在这里可以看到关联。首先art文件加载进来的ArtMethod结构体内含一个核心的字段ptr_sized_fieldsentry_point_from_quick_compiled_code_ 指向真正保存代码段的数据结构,也就是加载到内存的Oat文件中的OatQuickMethodcode数组字段。

    通过这样层层递进,才能找到方法中真正的执行逻辑。

    方法区

    方法区和堆是可以进行JVM内跨线程共享,那么肯定是一些运行时不变的数据。

    方法区是用于存放class二进制文件数据,内含虚拟机的类信息,静态常量,常量数据,编译后代码数据。

    有了上一节的基础知识后,就能清晰的明白方法区实际上就是指加载到四驱模型的堆中。

    而实际上在JVM中,我们常说的堆并非是c/c++编程的堆,而是从四驱模型的堆中申请一块对象名为Heap的才是Java堆。

    对于Java语言来说,堆特指的是JVM虚拟机生成的Heap对象。JVM全局有且只有一个。所有的对象都会保存在堆的位图中。每一个对象实际上都是属于四驱模型中的堆。

    肯定有的人觉得疑惑,那在方法内申请出来的对象不应该属于在栈中吗?从我们编程的角度看来似乎如此。实际上在解析字节码并不会联系代码段的上下文,而是一个指令进行一种操作。

    当发现new的操作的时候,就是NEW_INSTANCENEW_ARRAY申请对象和数组时候,都会调用不同内存管理器的Alloc方法,从提前malloc出来的内存区域获取对象。

    一个虚拟机会根据启动配置而生成不同的内存管理器,而不同的内存管理器就是会有不同的内存回收策略(GC),如标记-清除算法(MarkAndSwap),标记-压缩算法等。

    关于内存分配与回收这部分细节,之后会有专题解析。

    那么又衍生出一个新的问题,栈是如何管理局部变量的。

    虚拟机栈

    关于虚拟机栈就是我们说的java方法的栈。上文已经介绍了在解释执行中,栈帧实际上指代的是ShadowFrame对象。这个对象对应的内存实际上是申请在栈中,如下图:

    art-ShadowFrame家族.png

    能看到ShadowFrame本质上是通过placement new 方式为一个对象申请内存。这种特殊的方式会从栈中申请内存。

    在虚拟栈中,有这个4个核心对象构成:


    art-虚拟机栈.png
    • 1.局部变量表 用于存储方法中的局部变量
    • 2.操作数栈 进行逻辑操作时候会对临时变量进行缓存,一旦一个操作需要完成则会弹出操作数栈
    • 3.动态链表 是指把常量池中的数据链接到对应的字段中
    • 4.返回地址 方法返回地址

    要彻底理解这4个对象,我们需要进一步的了解dex指令码。

    dex指令码

    dex指令码和java字节码有着不少的区别。可以这么看这两者的关系,java编程生成jar包对应的class文件此时内容是java字节码。而在Android编译apk时候,则会取出这些代码数据,压缩成dex字节码。其中会把所有的java字节码进行转化压缩成dex字节码。dex字节码内容格式更小更加利于apk的包体积大小。

    dex字节码更少,但是相对的记录的信息多了。

    class Test {
        private static int a = 88;
        private static String name = "1111";
    
        public static void test(String a){
            int b = 127;
            Log.e("aaa",b+"");
        }
    }
    
    

    只看Code字段对应的字节码:

      com.yjy.hellotest.Test();
        descriptor: ()V
        flags:
        Code:
          stack=1, locals=1, args_size=1
             0: aload_0
             1: invokespecial #1                  // Method java/lang/Object."<init>":()V
             4: return
          LineNumberTable:
            line 14: 0
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
                0       5     0  this   Lcom/yjy/hellotest/Test;
    
      public static void test(java.lang.String);
        descriptor: (Ljava/lang/String;)V
        flags: ACC_PUBLIC, ACC_STATIC
        Code:
          stack=3, locals=2, args_size=1
             0: bipush        127
             2: istore_1
             3: ldc           #2                  // String aaa
             5: new           #3                  // class java/lang/StringBuilder
             8: dup
             9: invokespecial #4                  // Method java/lang/StringBuilder."<init>":()V
            12: iload_1
            13: invokevirtual #5                  // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
            16: ldc           #6                  // String
            18: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
            21: invokevirtual #8                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
            24: invokestatic  #9                  // Method android/util/Log.e:(Ljava/lang/String;Ljava/lang/String;)I
            27: pop
            28: return
          LineNumberTable:
            line 19: 0
            line 20: 3
            line 21: 28
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
                0      29     0     a   Ljava/lang/String;
                3      26     1     b   I
    
      static {};
        descriptor: ()V
        flags: ACC_STATIC
        Code:
          stack=1, locals=0, args_size=0
             0: bipush        88
             2: putstatic     #10                 // Field a:I
             5: ldc           #11                 // String 1111
             7: putstatic     #12                 // Field name:Ljava/lang/String;
            10: return
          LineNumberTable:
            line 15: 0
            line 16: 5
    }
    
    

    再来看看apk包压缩之后,取出其中的dex文件,调用dexdump命令打印dex中所有的内容,比较两者之间的区别。

    Class #1027            -
      Class descriptor  : 'Lcom/yjy/hellotest/Test;'
      Access flags      : 0x0000 ()
      Superclass        : 'Ljava/lang/Object;'
      Interfaces        -
      Static fields     -
        #0              : (in Lcom/yjy/hellotest/Test;)
          name          : 'a'
          type          : 'I'
          access        : 0x000a (PRIVATE STATIC)
        #1              : (in Lcom/yjy/hellotest/Test;)
          name          : 'name'
          type          : 'Ljava/lang/String;'
          access        : 0x000a (PRIVATE STATIC)
      Instance fields   -
      Direct methods    -
        #0              : (in Lcom/yjy/hellotest/Test;)
          name          : '<clinit>'
          type          : '()V'
          access        : 0x10008 (STATIC CONSTRUCTOR)
          code          -
          registers     : 1
          ins           : 0
          outs          : 0
          insns size    : 9 16-bit code units
    11e854:                                        |[11e854] com.yjy.hellotest.Test.<clinit>:()V
    11e864: 1300 5800                              |0000: const/16 v0, #int 88 // #58
    11e868: 6700 9b2b                              |0002: sput v0, Lcom/yjy/hellotest/Test;.a:I // field@2b9b
    11e86c: 1a00 df01                              |0004: const-string v0, "1111" // string@01df
    11e870: 6900 9c2b                              |0006: sput-object v0, Lcom/yjy/hellotest/Test;.name:Ljava/lang/String; // field@2b9c
    11e874: 0e00                                   |0008: return-void
          catches       : (none)
          positions     : 
            0x0000 line=15
            0x0004 line=16
          locals        : 
    
        #1              : (in Lcom/yjy/hellotest/Test;)
          name          : '<init>'
          type          : '()V'
          access        : 0x10000 (CONSTRUCTOR)
          code          -
          registers     : 1
          ins           : 1
          outs          : 1
          insns size    : 4 16-bit code units
    11e878:                                        |[11e878] com.yjy.hellotest.Test.<init>:()V
    11e888: 7010 243c 0000                         |0000: invoke-direct {v0}, Ljava/lang/Object;.<init>:()V // method@3c24
    11e88e: 0e00                                   |0003: return-void
          catches       : (none)
          positions     : 
            0x0000 line=14
          locals        : 
            0x0000 - 0x0004 reg=0 this Lcom/yjy/hellotest/Test; 
    
        #2              : (in Lcom/yjy/hellotest/Test;)
          name          : 'test'
          type          : '(Ljava/lang/String;)V'
          access        : 0x0009 (PUBLIC STATIC)
          code          -
          registers     : 4
          ins           : 1
          outs          : 2
          insns size    : 25 16-bit code units
    11e890:                                        |[11e890] com.yjy.hellotest.Test.test:(Ljava/lang/String;)V
    11e8a0: 1300 7f00                              |0000: const/16 v0, #int 127 // #7f
    11e8a4: 2201 9007                              |0002: new-instance v1, Ljava/lang/StringBuilder; // type@0790
    11e8a8: 7010 573c 0100                         |0004: invoke-direct {v1}, Ljava/lang/StringBuilder;.<init>:()V // method@3c57
    11e8ae: 6e20 5d3c 0100                         |0007: invoke-virtual {v1, v0}, Ljava/lang/StringBuilder;.append:(I)Ljava/lang/StringBuilder; // method@3c5d
    11e8b4: 1a02 0000                              |000a: const-string v2, "" // string@0000
    11e8b8: 6e20 613c 2100                         |000c: invoke-virtual {v1, v2}, Ljava/lang/StringBuilder;.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; // method@3c61
    11e8be: 6e10 663c 0100                         |000f: invoke-virtual {v1}, Ljava/lang/StringBuilder;.toString:()Ljava/lang/String; // method@3c66
    11e8c4: 0c01                                   |0012: move-result-object v1
    11e8c6: 1a02 711e                              |0013: const-string v2, "aaa" // string@1e71
    11e8ca: 7120 ac05 1200                         |0015: invoke-static {v2, v1}, Landroid/util/Log;.e:(Ljava/lang/String;Ljava/lang/String;)I // method@05ac
    11e8d0: 0e00                                   |0018: return-void
          catches       : (none)
          positions     : 
            0x0000 line=19
            0x0002 line=20
            0x0018 line=21
          locals        : 
            0x0002 - 0x0019 reg=0 b I 
            0x0000 - 0x0019 reg=3 a Ljava/lang/String; 
    
      Virtual methods   -
      source_file_idx   : 6825 (Test.java)
    

    这部分内容最终都会压缩在Dex文件的CodeItem结构体中。我们来看看静态构造函数:

        Code:
          stack=1, locals=0, args_size=0
             0: bipush        88
             2: putstatic     #10                 // Field a:I
             5: ldc           #11                 // String 1111
             7: putstatic     #12                 // Field name:Ljava/lang/String;
            10: return
    

    bipush 把88压入到操作数栈中,putstatic 则从操作数栈中弹出栈顶设置到a字段中。

    art-操作数栈.png

    我们来看看对应的dex字节码做了什么?

      Direct methods    -
        #0              : (in Lcom/yjy/hellotest/Test;)
          name          : '<clinit>'
          type          : '()V'
          access        : 0x10008 (STATIC CONSTRUCTOR)
          code          -
          registers     : 1
          ins           : 0
          outs          : 0
          insns size    : 9 16-bit code units
    11e854:                                        |[11e854] com.yjy.hellotest.Test.<clinit>:()V
    11e864: 1300 5800                              |0000: const/16 v0, #int 88 // #58
    11e868: 6700 9b2b                              |0002: sput v0, Lcom/yjy/hellotest/Test;.a:I // field@2b9b
    11e86c: 1a00 df01                              |0004: const-string v0, "1111" // string@01df
    11e870: 6900 9c2b                              |0006: sput-object v0, Lcom/yjy/hellotest/Test;.name:Ljava/lang/String; // field@2b9c
    11e874: 0e00                                   |0008: return-void
    

    此时就能看到不同了,此时bipushjava字节码转化成const_16 dex字节码,并把88压倒操作栈中,最后通过sputdex字节码把88从操作数栈弹出,设置到静态字段a中。

    操作数栈,局部变量表内存划分

    当有了上一节的基础知识后,我们进一步的来讨论操作数栈和局部变量在其中的所属的内存已经作用。

    我们回顾图ShadowFrame家族,这个图中详细的列举了ShadowFrame几个核心的对象。

    vregs 这个int数组就是我们常说的操作数栈以及局部变量表。很奇妙是吧。

    总的来说,java字节码可以分为如下4类:

    • 1.*load_* 这种格式的字节码代表从本地变量表加载到操作栈
    • 2.*store_* 则是把一个数值从操作栈读取到本地变量表中
    • 3.*ipush,ldc_*,*const_* 是把一个常量读取到操作栈中。
    • 4.putstatic等 把数值弹出操作栈并进行后续的逻辑操作,如赋值给某个对象

    而dex字节码,经过获取到java字节码后,就能获得更加相信的信息,从而转化为更加准确dex字节码。此时只有一个引用表 vregs。这个引用表可以作为局部变量以及操作数栈。其实这么设计也是可行的,无论是局部变量表还是操作数栈都是在运行字节码和解释执行时候的临时结果的缓存内存,只是hotspot虚拟机对这两者的职责划分更加详细,一个表控制局部变量,另一个表控制逻辑操作临时结果。

    因此操作数栈和局部变量表都是属于四驱内存的栈区域。

    程序计数器

    回顾图ShadowFrame家族 .实际上程序计数器就是指shadowFrame中的dex_pc。每当出现打断的时候,就会终止一个ShadowFrame解析执行其中的CodeItem.此时就会记录当前运行的行数的dex_pc,当下一次回到当前的ShadowFrame继续执行,就会跳到dex_pc继续执行。

    一般的,打断当前ShadowFrame的执行有如下几种情况:

    • 1.从解释执行跳到机器码执行
    • 2.跳转到c/c++本地库的代码中执行
    • 3.因时间片轮转,而切换线程执行方法

    由于dex_pc也是属于ShadowFrame的一个字段,因此也是属于栈区域

    本地方法栈

    本地方法栈,可以看成就是c/c++四驱模型中的栈区域,并没有特指什么具体的对象。直接执行c/c++中的代码块。

    方法执行原理

    理解了栈帧ShadowFrame之后,再来看看在ART虚拟机中,方法是如何执行的。

    方法想要正确执行,就需要知道当前方法的所属的类。从下图:

    art-虚拟机的启动后半段.png

    可以的得知,ART虚拟机在加载Class的时候会查找所有的属性和方法。在LoadMethod步骤的时候就会加载整个class中所有的方法和字段的信息。当然此时只是当前class的信息。

    方法存储

    只有在LinkMethod和LinkField步骤的时候,才会把父类和接口的信息全部解析出来并且保存在vtableembedded_table,methods_,iftable中。
    其中可实例化对象只存在embedded_table 否则只存在iftable.
    methods_保存了所有的方法。
    embedded_tableiftable 只保存了接口方法。可实例化对象可以通过embedded_table快速查找当前类实现的接口方法。

    方法执行

    当查找到方法后,就能确定这是否是一个重写的方法。如果是重写方法就会调用该类中所存储的对应方法。也就是说重写是在编译时候决定。

    如果这是一个重载方法,只有在运行时候知道是此时需要调用的参数是什么?通过方法描述符从而决定是调用哪个方法。

    方法是如何运行的,可以先来看看这个后半段的示意图。


    art-方法跳转后半段.png

    整个核心流程可以分为如下几个步骤:

    • 1.整个过程都会流转到artQuickToInterpreterBridge中进行集中处理。一旦虚拟机调用到这个方法后,就会从栈中生成一个栈帧对象,并把当前这个栈帧对象放到ManagerStack中管理。

    此时就ManagerStack决定了当前栈帧为哪个线程的ShadowFrame.注意看下面一块的示意图,在线程的私有内存中,保存着ManagerStack对象,这个对象保存的top_shadow_frame_就是我们常说的当前栈帧。

    • 2.ExecuteSwitchImpl 这个方法就会开始解析ShadowFrame 栈帧中所对应的代码块,也就是dex指令流。同时会记录当前的指令调用到了哪一行,也就是程序计数器。

    • 3.当判断到不是机器码,是解释执行的时候。就会调用ArtInterpreterToInterpreterBridge方法继续通过ExecuteSwitchImpl 解析下一个栈帧的指令流

    • 4.当判断到是机器码,则调用ArtInterpreterToCompiledCodeBirdge,执行机器码

    • 5.调用ManagerStack的pop方法,把执行完毕的当前栈帧弹出。

    小结

    本文对JVM引擎的方法执行做了一个初步的总结。能看到实际上我并没有带领大家一行行的看看ART虚拟机整体流程。是因为虚拟机的内容实在太多,没有一个初步的概念对全局有一个认识,会迷失在其中。加上本文也是对辉哥课程进行的一次小总结,以及自己学习过的只是进行的扩展。

    先到这里,我们对JVM是如何执行方法的原理明白后,我们就可以开启Gradle的总结。

    相关文章

      网友评论

          本文标题:效能优化笔记 JVM引擎执行总结

          本文链接:https://www.haomeiwen.com/subject/ktkwzktx.html