昨天同事在部門分享中談到了Android的虛擬機機制。正好想到我在學習.Net CLR的時候研究過CLR運行時的相互關系,所以拿出來分享一下。
我們在本文中要搞清楚類型,對象,線程棧,托管堆的關系。
首先你需要對內存管理、.net框架和.net clr有個基本的了解。.net框架
線程棧
我們先來看看一個加載了CLR的Microsoft Windows的進程。一個進程可能會有多個線程。一個線程創建時,會分配到1MB大小的棧。
作用
- 保存向方法傳遞的實參
- 保存方法內部定義的局部變量
棧是高位向低位地址構建的
一個線程的棧,準備調用M1方法
分配局部變量name的內存
M1調用M2方法,把實參s壓入棧,調用方法時,還需要將“返回地址”壓入棧,被調用方法結束后,返回這個位置。
M2開始執行,最終到達return語句,指令指針被設置為棧中的返回地址,M2的棧幀(指當前線程中調用棧的一個方法調用,每個方法調用都會在調用棧中創建并壓入一個棧幀)會被釋放。線程棧如下圖
線程將繼續執行M1在調用M2之后的代碼
托管堆
我們假定有這兩個類的定義
window進程已經啟動,托管堆已初始化,已經創建了一個線程。該線程已經調用了一些代碼,現在馬上要調用M3。
這時候CLR要做的事情
- 確保M3內部引用的所有類型的程序集都已加載
- 利用程序集的元數據創建類型對象(Type Object表示類型本身)
類型對象包括
- 類型對象指針(Type Object Pointer)和同步塊索引(sync block index)
- 靜態字段
- 方法表
當前面的事情做好后,開始執行M3的本地代碼,首先為局部變量分配內存,CLR會自動將局部變量初始化成null或0
接下來,M3代碼開始構造一個Manager實例(也就是一個Manager對象),步驟如下
- 初始化類型對象指針,并指向與對象對應的類型對象
- 初始化同步索引塊
- 將所有實例字段初始化
- new操作符會返回對象的內存地址,將之保存到變量e
對象包括
- 類型對象指針(Type Object Pointer)和同步塊索引(sync block index)
-
實例字段
image.png
M3下一行調用Employee的靜態方法Lookup。調用靜態方法的步驟
- CLR會定位與定義靜態方法的類型相對應的類型對象
- JIT編譯器在類型對象的方法表找到該方法,對方法進行JIT進行編譯(如果需要的話),再調用JIT編譯的代碼
- Lookup方法在堆上構造一個新的Manager對象,并返回地址
- M3將地址存到變量e
需要注意的是,變量e已經引用了新的對象。原來的對象沒有變量引用,GC將會對其自動進行回收。
M3的下一行代碼調用虛實例方法GenProgressReport。調用一個虛實例方法時,JIT編譯器要在方法中生成一些額外的代碼;方法每次調用時,都會調用這些代碼。
- 檢查發出調用的變量,跟隨地址來到發出調用的對象,檢查對象的類型對象指針,找到類型對象,并在方法表中定位該方法。(和非虛方法的區別:非虛關心的是變量e的類型,而虛關心的是變量e所指向的對象的類型)
- JIT編譯器在類型對象的方法表找到該方法,對方法進行JIT進行編譯(如果需要的話),再調用JIT編譯的代碼
Manager和Employee類型對象也有類型對象指針,那這些指針是指向哪里的呢?實際上,CLR在進程運行時,會為System.Type創建一個特殊的類型對象,Manager和Employee的類型對象的類型對象指針都指向它。System.Type的類型對象指針就指向自身。
System.Obejct有一個GetType方法,返回類型對象指針,這樣就可以判斷系統中任何對象(也包括類型對象)的類型了。