免費獲得垃圾回收
JavaScript 的效能始終是 Chrome 價值的關鍵,尤其是在提供流暢的體驗方面。從 Chrome 41 開始,V8 採用了一種新技術通過在閒置時間的小型未使用片段中隱藏昂貴的內存管理操作來提升 Web 應用的響應速度。結果,Web 開發者可期待更順暢的滾動和流暢的動畫,垃圾回收引起的卡頓大幅減少。
許多現代語言引擎,例如 Chrome 的 V8 JavaScript 引擎,為運行的應用動態管理內存,讓開發者不用自己擔心內存管理問題。引擎定期掃描分配給應用的內存,確定哪些數據不再需要,然後清除它們以騰出空間。此過程被稱為 垃圾回收。
在 Chrome 中,我們致力於提供順暢的每秒 60 幀(FPS)視覺體驗。雖然 V8 已嘗試以小型片段執行垃圾回收,但更大的垃圾回收操作可能在不可預測的時間發生——有時甚至在動畫進行中,導致執行暫停,使 Chrome 無法達到 60 FPS 的目標。
Chrome 41 包括 Blink 渲染引擎的任務調度器,使其能優先處理對延遲敏感的任務,以確保 Chrome 依然響應迅速且流暢。除了能優先處理任務,該任務調度器還集中了解系統的忙碌程度、需要執行哪些任務以及每個任務的緊急程度。因此,它可以估計 Chrome 可能處於閒置狀態的時候以及預期閒置的持續時間。
一個例子是 Chrome 在 Web 頁面上顯示動畫時。動畫將以每秒 60 幀更新屏幕,讓 Chrome 大約有 16.6 毫秒的時間完成更新。因此,在前一幀被顯示後,Chrome 會立即開始當前幀的工作,完成輸入、動畫和幀渲染任務。如果 Chrome 在 16.6 毫秒內完成這些工作,那麼它在開始渲染下一幀之前的剩餘時間內無事可做。而 Chrome 的調度器使 V8 能利用這段_閒置時間段_,安排特殊的_閒置任務_,當 Chrome 本來是閒置時執行。
閒置任務是特殊的低優先級任務,當調度器判定處於閒置時段時才運行。閒置任務被賦予一個截止時間,即調度器估算的預期閒置持續時間。在圖 1 的動畫例子中,這將是下一幀開始繪製的時間。在其他情況下(例如,屏幕上無活動時),這可能是下個待處理任務安排運行的時間,上限為 50 毫秒,以確保 Chrome 對意外的用戶輸入仍具響應性。截止時間由閒置任務用於估算可執行多少工作而不會導致卡頓或輸入響應延遲。
在閒置任務中完成的垃圾回收被隱藏在關鍵的、對延遲敏感的操作之外。這意味著這些垃圾回收任務是“免費”的。為了解 V8 如何做到這一點,值得回顧其當前的垃圾回收策略。
深入探討 V8 的垃圾回收引擎
V8 使用 世代垃圾回收器,將 JavaScript 堆分為小型的新生代分代(用於新分配的物件)和大型的老生代分代(用於長時間存活的物件)。由於大多數物件具有短暫性,這種分代策略使垃圾回收器能在較小的新生代分代(稱為清除)中執行定期短暫垃圾回收,無需跟踪老生代分代中的物件。
年輕世代使用s半空間分配策略,新的物件最初分配到年輕世代的活躍半空間中。當這個半空間充滿後,清理操作將移動存活的物件到另一個半空間。已經移動過一次的物件會被提升到老世代,並被視為長壽命物件。當存活的物件被移動後,新半空間會變成活躍半空間,而舊半空間中的死物件會被丟棄。
因此,年輕世代清理操作的持續時間取決於年輕世代中存活物件的大小。當大多數物件在年輕世代中變得不可達時,清理操作會很快完成 (<1 毫秒)。然而,如果大多數物件存活,清理操作可能會顯著變長。
當老世代中存活物件的大小超出啟發式得出的限制時,將進行一次整個堆的主要回收。老世代使用標記-清除回收器,並結合多項優化以改善延遲和內存消耗。標記延遲取決於需要標記的存活物件數量,對整個堆進行標記可能會花費超過100毫秒,尤其是大型網站應用程序。為了避免主線程長時間暫停,V8早已具備能力以多個小步驟增量標記存活物件,旨在每個標記步驟的持續時間保持在5毫秒以下。
在標記完成後,通過清除整個老世代內存,釋放可用內存供應用程式使用。這項任務由專用清除執行緒並發完成。最後,執行內存整理以減少老世代內存碎片化。這項任務可能非常耗時,但僅當內存碎片化成為問題時才執行。
總而言之,垃圾回收主要有四個主要任務:
- 年輕世代的清理操作,通常速度很快
- 通過增量標記器執行的標記步驟,根據步驟大小可能任意長
- 全堆垃圾回收,可能需要較長時間
- 搭配激進內存整理的全堆垃圾回收,可能需要較長時間,但可以清除碎片化內存
為了在空閒期執行這些操作,V8向排程器提交垃圾回收空閒任務。當這些空閒任務運行時,系統會提供任務完成的截止時間。V8的垃圾回收空閒時間處理器評估應執行哪些垃圾回收任務,以減少內存消耗,同時尊重截止時間以避免未來在幀渲染或輸入延遲方面出現停滯。
如果應用程序的測量分配率顯示年輕世代可能在下一次預期空閒期之前被填滿,垃圾回收器將在空閒任務期間執行一次年輕世代清理操作。此外,它計算最近清理任務的平均耗時,以預測未來清理操作的持續時間並確保不會違反空閒任務的截止時間。
當老世代中存活物件的大小接近堆限制時,將開始增量標記。增量標記步驟可以根據需要標記的字節數成線性擴展。根據測量的平均標記速度,垃圾回收空閒時間處理器試圖在給定的空閒任務中放入盡可能多的標記工作。
如果老世代幾乎滿了且提供給任務的截止時間估計足夠長以完成回收,會在空閒任務期間排程全堆垃圾回收。回收暫停時間根據標記速度乘以分配物件數量進行預測。如果網頁已長時間處於空閒狀態,則僅執行帶有額外內存整理的全堆垃圾回收。
性能評估
為了評估在空閒時運行垃圾回收的影響,我們使用Chrome的Telemetry性能基準框架來評估流行站點在加載時的平滑滾動情況。我們在Linux工作站上基準測試了排名前25的站點,以及在Android Nexus 6智慧手機上基準測試了典型移動站點。這些測試中會打開流行的網頁(包括像Gmail、Google Docs和YouTube這樣的複雜網頁應用)並滾動其內容幾秒鐘。Chrome的目標是保持滾動速率在60 FPS以提供流暢的用戶體驗。
圖2顯示了在空閒時排程的垃圾回收百分比。工作站的硬體速度較快,導致空閒時間總量比Nexus 6更多,從而使垃圾回收能在空閒時間中排程更大的比例(工作站為43%,Nexus 6為31%),從而在我們的卡頓指標上提升約7%。
除了改善頁面渲染的流暢性外,這些閒置時段還提供了執行更積極垃圾回收的機會,特別是在頁面完全閒置時。Chrome 45 的最新改進利用了這一點,大幅減少了閒置前景標籤頁所消耗的記憶體量。圖 3 展示了 Gmail 的 JavaScript 堆內存在閒置時可以減少約 45%,與 Chrome 43 中的同一頁面相比。
這些改進證明,只要更聰明地決定何時執行耗時的垃圾回收操作,就可以隱藏垃圾回收的暫停。網頁開發者再也不用害怕垃圾回收暫停,甚至可以面向流暢的 60 FPS 動畫進行開發。敬請期待,我們將繼續拓展垃圾回收調度的邊界。