(譯)你不必擔心Dart的垃圾回收器

coderwhy發表於2020-05-27

你不必擔心Dart的垃圾回收器(譯)

在學習Flutter的過程中,我們知道Widget只是最終渲染物件(RenderObject)的配置檔案,它會在build的時候頻繁的銷燬和建立,那麼,我們不需要擔心他的建立和銷燬帶來的效能問題嗎?

其實大可不必,因為Dart針對Flutter的Widget的建立和銷燬專門做過優化,這也是Flutter在多種語言中選擇Dart的一個重要因素,甚至我們還可以刻意利用這一點。

下面這篇文章解析了Dart的GC(Garbage Collector),對它做了個翻譯以及部分內容的解析,包括一些排版,有不對的地方大家多多指正。

原文地址:https://medium.com/flutter/flutter-dont-fear-the-garbage-collector-d69b3ff1ca30

Flutter使用Dart作為開發語言和執行時機制,Dart一直保留著執行時機制,無論是在除錯模式(debug)還是釋出模式(release),但是兩種構建方式之間存在很大的差異。

  • 在除錯模型下,Dart將所有的管道(需要用到的所有配件)全部裝載到裝置上:執行時,JIT(the just-in-time)編譯器/直譯器(JIT for Android and interpreter for iOS),除錯和效能分析服務。
  • 在釋出模式下,會除去JIT編譯器/直譯器依然保留執行時,因為執行時是Flutter App的主要貢獻者。
debug和release
debug和release

Dart的執行時包括一個非常重要的元件:垃圾回收器,它主要的作用就是分配和釋放記憶體,當一個物件被例項化(instantiated)或者變成不可達(unreachable)。

在Flutter執行過程中,會有很多的Object。

  • 在StatelessWidget在渲染前(其實上還有StatefulWidget),他們被建立出來。
  • 當狀態發生變化的時候,他們又會被銷燬。
  • 事實上,他們又很短的壽命(lifespan)。
  • 當我們構建一個複雜的UI介面時,會有成千上萬這樣的Widgets。

所以,作為Flutter開發者,我們需要擔心垃圾回收器不能很好的幫助我們管理這些嗎?(是不是會帶來很多的效能問題呢)

  • 當Flutter頻繁的建立和銷燬這些Widget(Objects),我們是否需要很迫切的限制這種行為呢?
  • 非常普遍,對於新的Flutter開發者來說,當一個Widget的狀態不需要改變時,他們會建立引用的Widget,來替代State中的Widget,以便於不會被銷燬或者重建。

不需要這樣做

擔心Dart的GC是沒有任何事實根據的(沒有必要),這是因為它分代(generational)架構和實現,可以讓我們頻繁建立和銷燬物件有一個最優解。在大多數情況下,我們只需要Flutter引擎按照它的方式建立和銷燬這些Widgets即可。

Dart的GC

Dart的GC是分代的(generational)和由兩個階段構成:the young space scavenger(scavenger針對年輕一袋進行回收) and parallel mark sweep collectors(sweep collectors針對老一代進行回收)

註解:事實上V8引擎也是這樣的機制

排程安排(Scheduling)

為了讓RG最小化對App和UI效能的影響,GC對Flutter引擎提供了hooks,hooks被通知當Flutter引擎被政策到這個App處於閒置的狀態,並且沒有使用者互動的時候。這就給了GC一個空窗期來執行它的手機階段,並且不會影響效能。

垃圾收集器還可以在那些空閒間隔內進行滑動壓縮(sliding compaction),從而通過減少記憶體碎片來最大程度地減少記憶體開銷。

空閒時GC
空閒時GC

階段一:Young Space Scavenger

這個階段主要是清理一些壽命很短的物件,比如StatelessWidget。當它處於阻塞時,它的清理速度遠快於第二代的mark、sweep方式。並且結合排程,完成可以消除程式執行時的暫停現象。

本質上來講,物件在記憶體中被分配一段連續的、可用的記憶體空間,直接被分配完為止。Dart使用bump pointer(註解:如果像malloc一樣,維護free_list再分配,效率很低。)分配新的空間,處理過程非常快。

分配了新物件的新空間,被為兩部分,稱之為semi spaces。一部分處於活動狀態,另一部分處於非活動狀態。新物件分配在活動狀態,一旦填充完畢,依然存活的Object,就會從活動狀態copy到非活動狀態,並且清除死亡的Object。這個時候非活動狀態變成了活動狀態,上面的步驟一次重複。(註解:GC來完成上面的步驟)

為了確定哪些Object是存活的或死亡的,GC從根物件開始檢測它們的應用。然後將有引用的Object(存活的)移動到非活動狀態,直接所有的存活Object被移動。死亡的Object就被留下;

有關此的更多資訊,請檢視Cheney演算法

img
img

階段二:Parallel Marking and Concurrent Sweeping

當物件達到一定的壽命(在第一階段沒有被GC回收),它們將被提升由第二代收集器管理的新記憶體空間:mark-sweep。

這個階段的GC有兩個階段:第一階段,首先遍歷物件圖(the object graph),然後標記人在使用的物件。第二階段,將掃描整個記憶體,並且回收所有未標記的物件。

這種GC機制在標記階段會阻塞,不能有記憶體變化和UI執行緒也會被阻塞。但是由於短暫的物件在Young Space Scavenger階段以及被處理,所有這個階段非常少出現。不過由於Flutter可以呼叫收集時間,影響的效能也會被見到最低。

但是如果引用程式不遵守分代的機制,反而這種情況會經常發生。但是由於Flutter的Widget的機制,所有這種情況不經常發生,但是我們還是需要了解這種機制。

Isolate

值得注意的是,Dart中的Isolate機制具有私有堆的概念,彼此是獨立的。每個Isolate有自己單獨的執行緒來執行,每個Isolate的GC不影響其他執行緒的效能。使用Isolate是避免阻塞UI和減輕密集型任務的好方法(註解:耗時操作可以使用Isolate)。

總結

到這裡你應該明白:Dart使用了強大的分代GC,以最大限度的減少Flutter中GC帶來的效能影響。

所以,你不需要擔心Dart的垃圾回收器,這個反而是我們應用程式的核心所在。

備註:所有內容首發於公眾號,之後除了Flutter也會更新其他技術文章,TypeScript、React、Node、uniapp、mpvue、資料結構與演算法等等,也會更新一些自己的學習心得等,歡迎大家關注

公眾號
公眾號

相關文章