前言
本文繼續(xù)總結(jié)輝哥的第二周二三節(jié)課的總結(jié)。這兩節(jié)課主要還是對(duì)JVM引擎執(zhí)行,Gradle進(jìn)行了初步的介紹和學(xué)習(xí)。
注意正文中所有的對(duì)虛擬機(jī)的討論都是基于Android 7.0 art虛擬機(jī)。
如果遇到什么問題歡迎來到本文:http://www.itdecent.cn/p/084b636c5cc4 互相討論
正文
JVM 引擎執(zhí)行
JVM引擎是如何執(zhí)行的。這里我們以art虛擬機(jī)為例子。先來看看整個(gè)核心原理圖。
首先JVM執(zhí)行代碼分為如下兩種:
1.解釋執(zhí)行,通俗一點(diǎn)的話語就是一遍執(zhí)行一遍翻譯字節(jié)碼。常常我們會(huì)把這種執(zhí)行方式和JIT(Just In time)聯(lián)系起來說。JIT是解釋執(zhí)行的核心機(jī)制,當(dāng)某個(gè)方法執(zhí)行的次數(shù)達(dá)到了閾值(也就是閾值)就會(huì)翻譯成機(jī)器碼
2.機(jī)器碼執(zhí)行 直接是機(jī)器碼進(jìn)行本地執(zhí)行。其中一個(gè)核心的機(jī)制,也就是我們常說的AOT(Ahead Of Time), 運(yùn)行前編譯。也就是在安裝的時(shí)候,把代碼經(jīng)過PMS的DexManager 跨進(jìn)程執(zhí)行dex2oat翻譯成優(yōu)化過的本地機(jī)器碼,可以被機(jī)器識(shí)別。
對(duì)于ART虛擬機(jī)來說,其實(shí)整個(gè)過程較為復(fù)雜,但是值得稱贊的是,整個(gè)流程的封裝設(shè)計(jì)做的不錯(cuò),不糾結(jié)底層的細(xì)節(jié)理解起來沒有想象的困難。
說起方法執(zhí)行,就必須提及一個(gè)概念,棧幀。
ShadowFrame棧幀
棧幀(Stack Frame)是用于支持虛擬機(jī)方法執(zhí)行和調(diào)用的數(shù)據(jù)結(jié)構(gòu),他是虛擬機(jī)運(yùn)行時(shí)在數(shù)據(jù)區(qū)中虛擬機(jī)棧的棧元素。棧幀保存了方法的局部變量表,操作數(shù)棧,動(dòng)態(tài)鏈接和方法返回地址和一些額外信息。
當(dāng)一個(gè)方法從調(diào)用到運(yùn)行完畢,實(shí)際上是一個(gè)棧幀從入棧到出棧的過程。
在編譯執(zhí)行過程中,會(huì)在Class文件中保存好Code字段中記錄好這個(gè)方法需要多少個(gè)局部變量,多少操作棧,方法的參數(shù)個(gè)數(shù)。通過這些數(shù)據(jù)就能決定了在當(dāng)前虛擬機(jī)環(huán)境下,當(dāng)前棧幀內(nèi)存大小。
一個(gè)線程中虛擬機(jī)棧的棧幀可能很長,只有在棧頂?shù)臈攀腔钴S的狀態(tài),成為當(dāng)前棧幀。與這個(gè)棧幀關(guān)聯(lián)的方法稱為當(dāng)前方法。
單單這么說概念,可能會(huì)不太明白。這里我們就挑出Android 7.0對(duì)應(yīng)的虛擬機(jī)中的對(duì)應(yīng)的數(shù)據(jù)結(jié)構(gòu)來進(jìn)一步闡述棧幀為何物:
機(jī)器碼執(zhí)行相關(guān)的邏輯較為復(fù)雜和抽象,這里來聊聊解釋執(zhí)行中棧幀的原理。
在ART虛擬機(jī)中,棧幀指代的數(shù)據(jù)結(jié)構(gòu)為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);
};

能看到,ShadowFrame 本質(zhì)上是被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 托管著一個(gè)當(dāng)前正在運(yùn)行的ShadowFrame。并能通過ManagedStack中的link 對(duì)象知道上一次線程切換時(shí)候的幀布局,從而進(jìn)行切換。
棧幀的創(chuàng)建與虛擬機(jī)的內(nèi)存劃分
上文說到棧幀屬于哪個(gè)數(shù)據(jù)區(qū)域。 我們回顧一下c++ 編程中的內(nèi)存四驅(qū)模型:
- 1.棧區(qū):由編譯器自動(dòng)分配釋放,存放函數(shù)參數(shù),局部變量。
- 2.堆區(qū):由編程者手動(dòng)分配釋放,如果不釋放則造成內(nèi)存泄露,只有進(jìn)程被銷毀時(shí),才被OS回收
- 3.數(shù)據(jù)區(qū):存放全局變量,靜態(tài)變量,常量字符等。只能被OS回收
- 4.代碼區(qū)域:存放代碼的區(qū)域
在java 虛擬機(jī)中內(nèi)存的劃分如下:
- 1.方法區(qū)
- 2.虛擬機(jī)棧
- 3.本地方法棧
- 4.堆
- 5.程序計(jì)數(shù)器
這一塊我們通常稱為運(yùn)行時(shí)數(shù)據(jù)區(qū),也就是JVM內(nèi)存。
在這一塊內(nèi)存中又分為共享內(nèi)存,和線程私有內(nèi)存:
- 共享內(nèi)存: 方法區(qū),堆
- 線程私有內(nèi)存:虛擬機(jī)棧,本地方法棧,程序計(jì)數(shù)

我們現(xiàn)在只討論JVM在Android的行為。實(shí)際上JVM的內(nèi)存劃分必定是建立在c++編程中四驅(qū)模型的架構(gòu)上。
為了徹底弄懂棧幀的的內(nèi)存申請(qǐng),讓我們把JVM和四驅(qū)模型聯(lián)系起來吧。
apk編譯產(chǎn)物
先聊聊apk安裝編譯產(chǎn)物,當(dāng)dexManager通過dex2oat進(jìn)程把dex轉(zhuǎn)化為如下幾種文件:

dex 是把jar包(內(nèi)含class文件)經(jīng)過壓縮后的產(chǎn)物
odex 則是在Android 5.0之前常用的,是App安裝時(shí)通過dex2opt對(duì)apk包中dex文件進(jìn)行了優(yōu)化產(chǎn)物,之后運(yùn)行App就加載odex文件,加速App響應(yīng)時(shí)間
oat 是在Android 5.0之后,安裝時(shí)候會(huì)對(duì)dex文件中的數(shù)據(jù)通過dex2oat翻譯成本地機(jī)器碼保存起來。本質(zhì)上是一個(gè)elf格式的文件
art 是Android安裝后生成的可選對(duì)象,一旦生成了則會(huì)獲取odex中部分常用內(nèi)容(長方法等)存儲(chǔ)到elf文件中。并保存到ImageSpace中,通過這種方式加速App的運(yùn)行效率。
- vdex 是Android 8.0之后的產(chǎn)物。當(dāng)dex經(jīng)過了編譯的校驗(yàn)后,則會(huì)把校驗(yàn)后代碼方法都緩存到vdex中。
而這幾個(gè)文件,最終都會(huì)加載到內(nèi)存中,對(duì)于Android高版本中,我們來討論oat文件以及art文件。
art文件的內(nèi)存從屬
來看看art文件通過JVM加載到內(nèi)存是一個(gè)什么形式:


art文件實(shí)際上在App應(yīng)用啟動(dòng)初期進(jìn)行初始化JVM時(shí)候,會(huì)加載到一個(gè)為ImageSpace的內(nèi)存空間中。這個(gè)內(nèi)存實(shí)際上是屬于c/c++四驅(qū)的堆,也就是說是通過malloc的方式為ImageSpace申請(qǐng)內(nèi)存。
當(dāng)然,ImageSpace中存在兩個(gè)十分重要字段:mem_map和live_bitmap。這兩個(gè)實(shí)際上是通過mmap系統(tǒng)調(diào)用把a(bǔ)rt文件中重要的數(shù)據(jù)字段映射到內(nèi)存中。
其中mem_map 映射了art中的方法,屬性,類表等核心對(duì)象。
當(dāng)生成了ImageSpace后,會(huì)把這個(gè)對(duì)象托付給JVM Heap對(duì)象進(jìn)行管理,而這個(gè)對(duì)象就是JVM中堆:

而堆本質(zhì)上也是通過malloc 從內(nèi)存中申請(qǐng)出來的,也是屬于c/c++四驅(qū)模型的堆一份子。
oat文件的內(nèi)存從屬
art文件只是作為輔助的作用,大部分的核心代碼都是由oat文件進(jìn)行管理。從apk安裝產(chǎn)物一圖可以得知,oat文件的結(jié)構(gòu)粗略是可以分2個(gè)區(qū)域:象征Oat文件頭部的OatHeader,以及象征Oat文件的內(nèi)容單元OatDexFile。OatDexFile在Oat中是數(shù)組的單元對(duì)象,他象征著被壓縮到oat文件中的dex文件。

這里面的結(jié)構(gòu)稍微有點(diǎn)復(fù)雜,但是還是可以一窺究竟:
1.OatHeader 帶了Oat文件的魔數(shù),版本號(hào)等數(shù)據(jù),當(dāng)然也指向了
Trampoline code也就是只有boot.oat(framework.jar對(duì)應(yīng)的oat)文件才有的直接調(diào)準(zhǔn)方法2.OatDexFile 則是Dex文件本身經(jīng)過翻譯后,壓縮到文件中的數(shù)據(jù)。可以從圖中可以看到內(nèi)含了dex文件對(duì)應(yīng)的偏移量,class對(duì)應(yīng)的偏移量
3.DexFile OatDexFile 指向的dex偏移量就是指的
DexFile對(duì)象,這個(gè)對(duì)象其實(shí)指代了dex文件4.TypeLookupTable 是一個(gè)哈希表,這個(gè)哈希表目的是快速通過類名找到dex對(duì)應(yīng)class數(shù)組的偏移索引
5.ClassOffsets 這是這個(gè)class二維偏移數(shù)組是指,第幾個(gè)dex的第幾個(gè)class
6.OatClass 則是ClassOffsets 指向的class對(duì)象。其中包含了當(dāng)前的class的狀態(tài)(加載,校驗(yàn),準(zhǔn)備,解析,初始化),當(dāng)前class的編譯類型(是全部轉(zhuǎn)化成機(jī)器碼,還是部分轉(zhuǎn)化機(jī)器碼,還是壓根沒經(jīng)過轉(zhuǎn)化),class中的方法等等
7.VmapTable 是指CompileMethod 中的
vmap_table_數(shù)組。CompileMethod是dex2oat 進(jìn)程處理dex中java方法的結(jié)果對(duì)象。其中CompileMethod的vmap_table_定長數(shù)組是一個(gè)映射表,反映了機(jī)器碼用到的物理寄存器到dex中虛擬寄存器的映射關(guān)系.8.OatQuickMethodHeader數(shù)組 指向所有的Oat文件的方法,內(nèi)含當(dāng)前方法所需要的棧幀大小,vmap的偏移量,代碼段數(shù)據(jù)結(jié)構(gòu)的偏移量等信息。
這些所有的數(shù)據(jù)最終都會(huì)加載到四驅(qū)模型的堆內(nèi)存中。
art和oat的關(guān)系

能看到實(shí)際上在上一片文章提到過的ArtMethod在這里可以看到關(guān)聯(lián)。首先art文件加載進(jìn)來的ArtMethod結(jié)構(gòu)體內(nèi)含一個(gè)核心的字段ptr_sized_fields的entry_point_from_quick_compiled_code_ 指向真正保存代碼段的數(shù)據(jù)結(jié)構(gòu),也就是加載到內(nèi)存的Oat文件中的OatQuickMethod的code數(shù)組字段。
通過這樣層層遞進(jìn),才能找到方法中真正的執(zhí)行邏輯。
方法區(qū)
方法區(qū)和堆是可以進(jìn)行JVM內(nèi)跨線程共享,那么肯定是一些運(yùn)行時(shí)不變的數(shù)據(jù)。
方法區(qū)是用于存放class二進(jìn)制文件數(shù)據(jù),內(nèi)含虛擬機(jī)的類信息,靜態(tài)常量,常量數(shù)據(jù),編譯后代碼數(shù)據(jù)。
有了上一節(jié)的基礎(chǔ)知識(shí)后,就能清晰的明白方法區(qū)實(shí)際上就是指加載到四驅(qū)模型的堆中。
而實(shí)際上在JVM中,我們常說的堆并非是c/c++編程的堆,而是從四驅(qū)模型的堆中申請(qǐng)一塊對(duì)象名為Heap的才是Java堆。
堆
對(duì)于Java語言來說,堆特指的是JVM虛擬機(jī)生成的Heap對(duì)象。JVM全局有且只有一個(gè)。所有的對(duì)象都會(huì)保存在堆的位圖中。每一個(gè)對(duì)象實(shí)際上都是屬于四驅(qū)模型中的堆。
肯定有的人覺得疑惑,那在方法內(nèi)申請(qǐng)出來的對(duì)象不應(yīng)該屬于在棧中嗎?從我們編程的角度看來似乎如此。實(shí)際上在解析字節(jié)碼并不會(huì)聯(lián)系代碼段的上下文,而是一個(gè)指令進(jìn)行一種操作。
當(dāng)發(fā)現(xiàn)new的操作的時(shí)候,就是NEW_INSTANCE和NEW_ARRAY申請(qǐng)對(duì)象和數(shù)組時(shí)候,都會(huì)調(diào)用不同內(nèi)存管理器的Alloc方法,從提前malloc出來的內(nèi)存區(qū)域獲取對(duì)象。
一個(gè)虛擬機(jī)會(huì)根據(jù)啟動(dòng)配置而生成不同的內(nèi)存管理器,而不同的內(nèi)存管理器就是會(huì)有不同的內(nèi)存回收策略(GC),如標(biāo)記-清除算法(MarkAndSwap),標(biāo)記-壓縮算法等。
關(guān)于內(nèi)存分配與回收這部分細(xì)節(jié),之后會(huì)有專題解析。
那么又衍生出一個(gè)新的問題,棧是如何管理局部變量的。
虛擬機(jī)棧
關(guān)于虛擬機(jī)棧就是我們說的java方法的棧。上文已經(jīng)介紹了在解釋執(zhí)行中,棧幀實(shí)際上指代的是ShadowFrame對(duì)象。這個(gè)對(duì)象對(duì)應(yīng)的內(nèi)存實(shí)際上是申請(qǐng)?jiān)跅V校缦聢D:

能看到ShadowFrame本質(zhì)上是通過placement new 方式為一個(gè)對(duì)象申請(qǐng)內(nèi)存。這種特殊的方式會(huì)從棧中申請(qǐng)內(nèi)存。
在虛擬棧中,有這個(gè)4個(gè)核心對(duì)象構(gòu)成:

- 1.局部變量表 用于存儲(chǔ)方法中的局部變量
- 2.操作數(shù)棧 進(jìn)行邏輯操作時(shí)候會(huì)對(duì)臨時(shí)變量進(jìn)行緩存,一旦一個(gè)操作需要完成則會(huì)彈出操作數(shù)棧
- 3.動(dòng)態(tài)鏈表 是指把常量池中的數(shù)據(jù)鏈接到對(duì)應(yīng)的字段中
- 4.返回地址 方法返回地址
要徹底理解這4個(gè)對(duì)象,我們需要進(jìn)一步的了解dex指令碼。
dex指令碼
dex指令碼和java字節(jié)碼有著不少的區(qū)別??梢赃@么看這兩者的關(guān)系,java編程生成jar包對(duì)應(yīng)的class文件此時(shí)內(nèi)容是java字節(jié)碼。而在Android編譯apk時(shí)候,則會(huì)取出這些代碼數(shù)據(jù),壓縮成dex字節(jié)碼。其中會(huì)把所有的java字節(jié)碼進(jìn)行轉(zhuǎn)化壓縮成dex字節(jié)碼。dex字節(jié)碼內(nèi)容格式更小更加利于apk的包體積大小。
dex字節(jié)碼更少,但是相對(duì)的記錄的信息多了。
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字段對(duì)應(yīng)的字節(jié)碼:
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文件,調(diào)用dexdump命令打印dex中所有的內(nèi)容,比較兩者之間的區(qū)別。
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)
這部分內(nèi)容最終都會(huì)壓縮在Dex文件的CodeItem結(jié)構(gòu)體中。我們來看看靜態(tài)構(gòu)造函數(shù):
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壓入到操作數(shù)棧中,putstatic 則從操作數(shù)棧中彈出棧頂設(shè)置到a字段中。

我們來看看對(duì)應(yīng)的dex字節(jié)碼做了什么?
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
此時(shí)就能看到不同了,此時(shí)bipushjava字節(jié)碼轉(zhuǎn)化成const_16 dex字節(jié)碼,并把88壓倒操作棧中,最后通過sputdex字節(jié)碼把88從操作數(shù)棧彈出,設(shè)置到靜態(tài)字段a中。
操作數(shù)棧,局部變量表內(nèi)存劃分
當(dāng)有了上一節(jié)的基礎(chǔ)知識(shí)后,我們進(jìn)一步的來討論操作數(shù)棧和局部變量在其中的所屬的內(nèi)存已經(jīng)作用。
我們回顧圖ShadowFrame家族,這個(gè)圖中詳細(xì)的列舉了ShadowFrame幾個(gè)核心的對(duì)象。
vregs 這個(gè)int數(shù)組就是我們常說的操作數(shù)棧以及局部變量表。很奇妙是吧。
總的來說,java字節(jié)碼可以分為如下4類:
- 1.
*load_*這種格式的字節(jié)碼代表從本地變量表加載到操作棧 - 2.
*store_*則是把一個(gè)數(shù)值從操作棧讀取到本地變量表中 - 3.
*ipush,ldc_*,*const_*是把一個(gè)常量讀取到操作棧中。 - 4.
putstatic等 把數(shù)值彈出操作棧并進(jìn)行后續(xù)的邏輯操作,如賦值給某個(gè)對(duì)象
而dex字節(jié)碼,經(jīng)過獲取到j(luò)ava字節(jié)碼后,就能獲得更加相信的信息,從而轉(zhuǎn)化為更加準(zhǔn)確dex字節(jié)碼。此時(shí)只有一個(gè)引用表 vregs。這個(gè)引用表可以作為局部變量以及操作數(shù)棧。其實(shí)這么設(shè)計(jì)也是可行的,無論是局部變量表還是操作數(shù)棧都是在運(yùn)行字節(jié)碼和解釋執(zhí)行時(shí)候的臨時(shí)結(jié)果的緩存內(nèi)存,只是hotspot虛擬機(jī)對(duì)這兩者的職責(zé)劃分更加詳細(xì),一個(gè)表控制局部變量,另一個(gè)表控制邏輯操作臨時(shí)結(jié)果。
因此操作數(shù)棧和局部變量表都是屬于四驅(qū)內(nèi)存的棧區(qū)域。
程序計(jì)數(shù)器
回顧圖ShadowFrame家族 .實(shí)際上程序計(jì)數(shù)器就是指shadowFrame中的dex_pc。每當(dāng)出現(xiàn)打斷的時(shí)候,就會(huì)終止一個(gè)ShadowFrame解析執(zhí)行其中的CodeItem.此時(shí)就會(huì)記錄當(dāng)前運(yùn)行的行數(shù)的dex_pc,當(dāng)下一次回到當(dāng)前的ShadowFrame繼續(xù)執(zhí)行,就會(huì)跳到dex_pc繼續(xù)執(zhí)行。
一般的,打斷當(dāng)前ShadowFrame的執(zhí)行有如下幾種情況:
- 1.從解釋執(zhí)行跳到機(jī)器碼執(zhí)行
- 2.跳轉(zhuǎn)到c/c++本地庫的代碼中執(zhí)行
- 3.因時(shí)間片輪轉(zhuǎn),而切換線程執(zhí)行方法
由于dex_pc也是屬于ShadowFrame的一個(gè)字段,因此也是屬于棧區(qū)域
本地方法棧
本地方法棧,可以看成就是c/c++四驅(qū)模型中的棧區(qū)域,并沒有特指什么具體的對(duì)象。直接執(zhí)行c/c++中的代碼塊。
方法執(zhí)行原理
理解了棧幀ShadowFrame之后,再來看看在ART虛擬機(jī)中,方法是如何執(zhí)行的。
方法想要正確執(zhí)行,就需要知道當(dāng)前方法的所屬的類。從下圖:

可以的得知,ART虛擬機(jī)在加載Class的時(shí)候會(huì)查找所有的屬性和方法。在LoadMethod步驟的時(shí)候就會(huì)加載整個(gè)class中所有的方法和字段的信息。當(dāng)然此時(shí)只是當(dāng)前class的信息。
方法存儲(chǔ)
只有在LinkMethod和LinkField步驟的時(shí)候,才會(huì)把父類和接口的信息全部解析出來并且保存在vtable,embedded_table,methods_,iftable中。
其中可實(shí)例化對(duì)象只存在embedded_table 否則只存在iftable.
methods_保存了所有的方法。
embedded_table和iftable 只保存了接口方法??蓪?shí)例化對(duì)象可以通過embedded_table快速查找當(dāng)前類實(shí)現(xiàn)的接口方法。
方法執(zhí)行
當(dāng)查找到方法后,就能確定這是否是一個(gè)重寫的方法。如果是重寫方法就會(huì)調(diào)用該類中所存儲(chǔ)的對(duì)應(yīng)方法。也就是說重寫是在編譯時(shí)候決定。
如果這是一個(gè)重載方法,只有在運(yùn)行時(shí)候知道是此時(shí)需要調(diào)用的參數(shù)是什么?通過方法描述符從而決定是調(diào)用哪個(gè)方法。
方法是如何運(yùn)行的,可以先來看看這個(gè)后半段的示意圖。

整個(gè)核心流程可以分為如下幾個(gè)步驟:
- 1.整個(gè)過程都會(huì)流轉(zhuǎn)到
artQuickToInterpreterBridge中進(jìn)行集中處理。一旦虛擬機(jī)調(diào)用到這個(gè)方法后,就會(huì)從棧中生成一個(gè)棧幀對(duì)象,并把當(dāng)前這個(gè)棧幀對(duì)象放到ManagerStack中管理。
此時(shí)就ManagerStack決定了當(dāng)前棧幀為哪個(gè)線程的ShadowFrame.注意看下面一塊的示意圖,在線程的私有內(nèi)存中,保存著ManagerStack對(duì)象,這個(gè)對(duì)象保存的top_shadow_frame_就是我們常說的當(dāng)前棧幀。
2.ExecuteSwitchImpl 這個(gè)方法就會(huì)開始解析
ShadowFrame棧幀中所對(duì)應(yīng)的代碼塊,也就是dex指令流。同時(shí)會(huì)記錄當(dāng)前的指令調(diào)用到了哪一行,也就是程序計(jì)數(shù)器。3.當(dāng)判斷到不是機(jī)器碼,是解釋執(zhí)行的時(shí)候。就會(huì)調(diào)用
ArtInterpreterToInterpreterBridge方法繼續(xù)通過ExecuteSwitchImpl解析下一個(gè)棧幀的指令流4.當(dāng)判斷到是機(jī)器碼,則調(diào)用
ArtInterpreterToCompiledCodeBirdge,執(zhí)行機(jī)器碼5.調(diào)用ManagerStack的pop方法,把執(zhí)行完畢的當(dāng)前棧幀彈出。
小結(jié)
本文對(duì)JVM引擎的方法執(zhí)行做了一個(gè)初步的總結(jié)。能看到實(shí)際上我并沒有帶領(lǐng)大家一行行的看看ART虛擬機(jī)整體流程。是因?yàn)樘摂M機(jī)的內(nèi)容實(shí)在太多,沒有一個(gè)初步的概念對(duì)全局有一個(gè)認(rèn)識(shí),會(huì)迷失在其中。加上本文也是對(duì)輝哥課程進(jìn)行的一次小總結(jié),以及自己學(xué)習(xí)過的只是進(jìn)行的擴(kuò)展。
先到這里,我們對(duì)JVM是如何執(zhí)行方法的原理明白后,我們就可以開啟Gradle的總結(jié)。