在物理內存中觀察CLR托管內存及GC行為


雖然看了一些書,還網絡上的一些博文,不過對CLR托管內存細節依然比較模糊。而且因為工作原因總會有很多質疑,想要親眼看到內存里二進制數據的變化。

所以借助winhex直接查看內存以證實書上的描述或更進一步揣摩CLR托管內存的運作方式,這里寫下來跟大家一起分享(由於自己這方面知識儲備不太充足,下面的好多內容也是猜測,肯定有很對錯誤,希望了解的網友可以幫忙指正)

 

測試環境: windowsXP win10 win7 (dotnet4.0 Releases編譯 ,下文截圖為win7上的運行結果)

內存查看工具: winhex 7.5

 

雖然重點是監測二進制的內存,不過基本的測試代碼還是要有的(測試是直接運行編譯好的exe,沒有使用調試模式,編譯時要使用Releases,因為debug跟Releases在GC回收時對象是否可達的判斷是不一樣的)

下面對內存的查找部分看起來可能有點跳躍,因為是借助了反復測試得到的規律,很多過程沒有贅述

 

進行之前需要先簡單了解CLR對象分配(類型對象指針要知道),GC的基本過程(G0,G1,G2需要簡單了解),二進制數據的存儲(主要是大小端)

 

下圖是測試中用到的引用對象的結構

 

 下圖為測試的主要步驟,會分8步進行,每一步也都標注出來了

 

 

 

 第1監測點

 
 
通過TestForGC_3對象里的值類型87564023523 (hex 146338F6E3 ,windows為小端存儲,所以最后搜索E3 F6 38 63 14)
前面的78 41 9B 92 為32位類型對象指針 后面接着的是同步塊索引 (如果是64位程序這2個數據則都將是64位)
 
 
 
根據前面TestForGC_3的地址70354802,我們在內存里搜索到了唯一的一個匹配項,說明這里一定是張表,這個指針指向了TestForGC_3的地址
 
 
 
這個就是 NextObjPtr 了  (這個也只是推測,根據后面的操作發現新增對象末尾地址總是002BFB
48里面的數據,以及之前的反復測試前面的類型指針也是匹配的,但測試結果並不是每次這個類型指針都是這個值,並且在不同系統版本下差距非常大)
后面的操作大家可以看到它的確就是NextObjPtr ,整個內存塊里存着這個地址的位置也只有這里)【在托管堆中維護着一個NextObjPtr指針,指向下一個新建對象分配時在托管堆中所處的位置】
 

第2監測點
 
 
按照順序我們通過內存搜索先找到了a1的地址
這里順便解析下對象(引用類型)在內存里的存儲
最前面8字節為類型對象指針及同步塊索引(每個32位,如果是64位應用則每個64位)
類型對象指針不是一成不變的,就是dotnet內置的類型也不能保證,這次運行是一個值(地址),另外一個實例運行起來可能是另外一個(地址) (這里的地址全部使用偏移地址)
后面接着的3個8字節數據發布是TypeA里3個引用類型變量的地址,可以看到第2個地址就緊接在下面(因為是一起分配的)
順便看下string類型在內存里的存儲
 
 
 
根據a1里面的存儲的地址02483588,輕松定位到了“testtypea”字符串
同樣與其他對象一樣擁有類型對象指針,同步索引塊,后面有4個字節的數據長度,然后后面跟數據
這也的確說明了string千真萬確引用類型,毫無爭議
最后TypeA里面還有一個引用對象TypeB,是一樣的就不重復說了,不過TypeB的指針只存在a1里面(即他的回收確實也只能靠根搜索)
 
 
 
現在我們通過a1的位置,查找內存中含有其地址的內存,居然搜索到了5塊內存,而且都靠的非常近
 
 
 
 
同樣的方法搜索到bytesStart在內存里的地址
同樣的結構類型指針,同步索引,后面跟8字節的長度,再后面就是數據
 
 
 
根據地址搜索bytesStart在內存里的指針,也只有1個(這種結果在同樣環境下運行每次的表現都是已有的,不過在更換運行環境后就會有明顯差異),而且也緊靠着a1的指針(可以推斷他們確實是在一張“表”上)
 
 
 
現在看下剛剛找到的NextObjPtr里面的數據是多少(02486988),下面我們看下這個地址里是什么
 
 
 
可以看到就指向了最后分配的bytesStart地址的后面
每個引用對象后面都有8個全0的字節(多次測試,反復分析數據都是這樣)
 

監測點3 (為了驗證剛剛的NextObjPtr的確是那塊內存)
 
 
到第3步,可以直觀的看到bytesThen就直接使用了剛剛NextObjPtr后面指向的內存,
 
 
 
 
同時也看到NextObjPtr指示下的一片內存(這個時候對A0 6D 48 02 的搜索也證明內存里只有這么一塊存的是這個數據)
而且可以看到這個地址確實就是bytesThen后面的內存地址
 

第4監測點 (重復創建10分份的typeA)
 
 
這次直接使用類型對象指針搜索新創建的10份TypeA (可能會搜索出其他數據,因為內存里有其他程序及測試前幾次運行殘留的數據)
可以看到這些TypeA直接分配在了bytesThen的后面(測試中盡量少使用終端打印。終端打印雖然1行代碼,不過clr會創建很多對象去完成打印,不方便觀察)
 
 
 
 
現在想知道這些TypeA的指針,卻發現內存里根本沒有這個地址(后面9個的結果一樣)
甚至連里面的TypeB的指針也搜索不到
 
其實這些TypeA從一創建即為不可達,因為后面再也沒有用到它們的地方,即一開始就沒有任何對象引用過他們,在引用跟蹤里一直被作為垃圾
 
 
 
這個時候NextObjPtr 已經指向了02487520
 

監測點5 (出RunCreat)
 
 
 
出RunCreat()這個方法回到RunTest()里,NextObjPtr指向依然沒有變化(沒有新的對象創建)。
 

監測點6 (回收G0)
 
 
執行完G0回收后 ,NextObjPtr直接變為了全0 (其實后面還有跟的8個字節的數據也變為了0,這8個字節可能為G0閥值)
 
 
 這里有個疏忽,本來先要監測a1的回收,現在發現后面的代碼殘留上一次的測試代碼錯誤的把a1引用了, 所以要到這一行結束a1是垃圾
 
 
 
經過G0回收后,bytesStart  bytesThen,應該移動到G1,不過看他們在內存里的位置並沒有發生任何變化(內存里也只有這一份)
那10個在RunCreat創建的TypeA也似乎沒有什么變化
 
關於書上的描述跟圖例,似乎在GC完成后,G0向G1的代提升會移動內存,不過現在看來並沒有移動內存(目前GC把85000字節的數據當作大對象,所以這里的bytesStart  也不是大對象)
 
 
那如果bytesStart  bytesThen是存活的,不能回收,那下一個NextObjPtr也一定緊接着在bytesThen后面
整個內存符合條件的也就這么一處(即使是搜索?? 6D 48 02 也只有這一塊內存符合條件),雖然這塊內存看起來沒有什么特別的格式
 

監測點7 (下一個地址從什么地方開始分配)
 
 
 
可以看到bytes這個全a的數據真的是從剛剛推測的地址開始分配內存的,在RunCreat創建的TypeA也直接被覆蓋了(確實被當作了垃圾)
 
 
 
NextObjPtr現在也正常的指向了bytes的后面
 

監測點8
 
 
 
 
數據確實在里面被改動了,而且bytesStart  bytesThen也的確處於G1
 

監測點9
 這次回收除了a1 其他的bytesStart  bytesThen bytes ,應該都會被回收
 
 
之前放着bytesStart  bytesThen bytes 指針的內存 數據已經被覆蓋了,現在他們都被移動到另外一塊內存
而a1 現在應該由g1提升到了g2
 
 
 
之前存放a1指針的地址也全部被覆蓋了,在內存里搜索到4塊新內存,其中一塊還與前面的bytesStart  bytesThen 新指針放在一起
 
 
 
雖然現在NextObjPtr 現在不為0 ,但也明顯被重置了 (因為打印的緣故,GC后馬上創建了新的對象)
也很明顯,並沒有覆蓋前面的內存,而是直接指向了后面的內存
 
 
現在來看剛剛被認為是標記GC后下一次分配內存的地址的的內存塊,現在的地址02488A88,這個地址也十分合理,正好在兩次NextObjPtr 的中間
標識這個地址的確是標記GC后 新NextObjPtr的初始值
 

監測結束

跳出RunTest,馬上就執行了一次完全的GC

 

上面寫的比較雜亂,雖然很對東西還是沒有弄明白也沒有發現什么規律,不過至少可以得到下面的一些結果

1:證明了NextObjPtr 的存在,也了解他的基本行為(其結構后面數據可能還包含G0閾值等其他數據)

2:GC回收使用的標記方法的確是根搜索

3:被回收的內存不會被擦除,只是通過移動NextObjPtr標記下一個內存能被分配的位置

4:對象從G0移動到G1,內存本身不會移動(可能記錄對象的指針的表會有相應更新)

5:不是每次回收都會壓縮內存,大部分時間都維持原有結構

6:對象在內存中的存儲細節

 

最后上面寫了那么多,其實不單單就是為了看CLR物理內存,同樣也是表達一種方法,用同樣的方法也可以查看包括jvm在內的幾乎所有進程的物理內存,同時winhex不僅可以查看,還擁有在運行時直接修改物理內存的能力。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM