垃圾回收算法、內存管理
餵雞百科 翻譯:
追蹤垃圾回收是一種自動內存管理,這種機制決定了什麽對象應該被回收,除了從根作用域開始的引用鏈上可到達的對象外,其余對象一律被認為是“垃圾”而且應該要回收。垃圾回收經常都是采用這樣的方式實現,而不是采用引用計數方式。
一個對象的可達性
一個可到達的對象準確來說有以下兩種情況:
1.所有可以在調用棧中引用的對象(包括當前執行函數中的參數、函數中的本地變量),以及所有全局變量【ps:如f函數沒執行完,開始執行垃圾回收,則f裏的東西就是可達的】
2.所有具備可達性的對象所引用的對象,也具備可達性。
“垃圾”不具備可達性,我們不再使用這個數據而且它離開了它的作用域。和“語法垃圾”不同,“語法垃圾”是程序中不可能訪問到的對象,而“語義垃圾”是程序中不可能用的對象,如:
Object x = new Foo(); Object y = new Bar(); x = new Quux(); if(x.check_something()) { x.do_something(y); } System.exit(0);
對於前三行代碼來說,Foo實例對象就是後續的代碼中無法訪問到的“語法垃圾”,對於if語句塊,y可能是“語義垃圾”,因為if條件可能不執行,這樣y就用不到了。
對於具備引用類型和基礎類型的語言來說,垃圾回收器需要采取某一種方式去區分棧中的變量或者對象的字段他們屬於什麽類型以及他們引用了什麽;在內存中,一個整數和一個引用可能很類似。垃圾回收器需要去弄清楚元素什麽情況下是引用類型,什麽情況下是基本類型,一種通用的解決方案就是使用“標記指針”
強類型和弱類型引用
弱引用:用於引用存在的對象,而且不會延長這個對象的生命周期
強引用:當對象沒有強引用的時候,即使這個對象上有弱引用,垃圾回收器也會回收這個對象
基礎算法
垃圾回收會周期地執行,當內存不足以滿足一次分配請求時,垃圾回收會被馬上觸發。垃圾回收執行的操作是“標記和清除”
標記清除法
內存中每個對象都有一個標記(通常只會占用1比特大小),這個標記只會被垃圾回收器所讀取使用,在垃圾回收期間外,所有對象都處於未標記狀態(cleared)
第一階段就是 標記階段 ,以遍歷樹的形式遍歷作用域樹,每個作用域中被直接指向的對象會被進行標記,所有其他對象如果當前作用域中被已標記對象所引用,則也會被進行標記。這樣所有可以直接通過根作用域訪問到的對象都會被標記。
第二階段就是 清除階段 ,整塊內存會被重新掃描。沒有被標記的對象意味著他們不能被任何某個作用域所訪問到,那就釋放那些對象的空間。接著把之前標記了的對象的標記進行清除,準備下一次的垃圾回收。
這個方式有多個缺點,其中最主要的是垃圾回收過程中,不允許內存工作集發生任何變化,導致系統會處於停滯狀態(觸發時機通常是不可預料的)。這使“實時”和“時序嚴格”變得不可能。總的來說,整塊內存必須被檢查,而且還要檢查兩次,對於分頁系統來說可能會造成其他問題
剩余的以後再補充
案例分析
例子來源
語法垃圾
function foo() { this.var1 = "potential accidental global"; } // Foo作為函數調用,this指向全局變量(window) // 而不是undefined foo(); function foo(arg) { bar = "some text"; }
以上的foo函數中,都無意地創建了一個全局變量,這是我們用不到的,出現內存泄漏
循環引用
function f() { var o1 = {}; var o2 = {}; o1.p = o2; // o1 引用 o2 o2.p = o1; // o2 引用 o1 形成循環引用 } f();
以上例子如果使用引用計數,則f每調用一次,則兩個對象會出現內存溢出。如果使用標記清除法,函數執行時他們兩個對象會被標記,所以不會被回收;當函數結束後,因為兩個對象都沒有被外部可達對象所引用,所以他們不會被標記,會被回收。
垃圾回收算法、內存管理