iT邦幫忙

2022 iThome 鐵人賽

DAY 29
0
Modern Web

這些那些你可能不知道我不知道的Web技術細節系列 第 29

你可能不知道的(Web)API--FinalizationRegistry(GC)

你可能不知道的Web API–FinalizationRegistry(GC)

FinalizationRegistry是和WeakMapWeakSetWeakRef在ES12一同進入到語言規範裡的兩個API。其實後面幾個更容易使用到,但我今天偏偏就是要來聊聊前者--FinalizationRegistry

在說說為什麼這個API有點雞肋可能沒什麼人知道之前,還是先介紹介紹這個API的用法。這個API的作用是在變數物件在被記憶體回收以前,可以註冊一些清理動作。比如可以建立物件:

var registry = new FinalizationRegistry((heldValue) => {
    console.log(`${heldValue} is cleaned`);
})

var obj1 = { toString() { return "<Object obj1>"} };

registry的callback function將快被記憶體回收的訊息打印出來。如果我們希望了解obj1obj2何時被回收,可以用.register()註冊:

registry.register(obj1, obj1.toString());

那麼當obj1obj2不再可以被存取的時候,就有可能被記憶體回收,進而列印出訊息出來:

obj1 = null;

至於為什麼obj1不能使用delete,可以參考「你可能都不瞭解的JS變數祕密 - 一文了解無宣告、var、let、const變數細節
(突然發現這也是你可能不知道系列呢XD)

JavaScript和許多高級程式語言一樣,實現了自動的垃圾回收機制,在變數資料不再有辦法被存取後,一段時間會將其從記憶體釋放。但也與其他語言一樣,並不知道釋放的時間。

在我認識的中高級程式語言裡,只有C、C++、Rust比較能夠預測釋放記憶體的時間。
不過通常來說,自動記憶體回收機制若恰當,執行起來結果並不會太壞。
但如果是會佔用到IO等程式語言外部資源的,像是資料庫連線,仍不建議依賴自動記憶體釋放。

也因此實際上無法作用在任何前端應用上。不過透過瀏覽器工具,還是可以手動觸發記憶體回收機制的運作。

值得注意的是,本篇標題雖然還是帶有一個括號的Web,但其實這個是進到語言的設計規範裡面的。換句話說,Node.js、deno等等可以作為後端的語言實現,一樣可以使用這個特性。

只是不巧前陣子硬碟壞了,私人電腦並沒有安裝Node.js嘗試。但如果它還是基於V8引擎的話,最新版本應該已經可以使用。

該不該使用FinalizationRegistry

會有這樣的設計出現,想必是有需要的情況。不過以我個人的看法,可以作為最後保險手段,但不要依賴。像是Java 8以前物件有finalize可以使用,但是在Java 9廢棄的finalize?,換成Cleaner,並且在《Effective Java》第三版條款八中,依舊建議避免使用。

我並沒有閱讀過《Effective Java》。上面算是引用的

在C#,雖然物件有解構子,但如果有引用到外部資源,仍會建議實現IDisposable介面。(以下僅作為概念範例,並不是可以運行的C#程式)

using System;
using System.ComponentModel;

public class DatabaseManager: IDisposable {
    private Database db;
    private bool disposed = false;
    
    public DatabaseManager()
    {
        db = Database.open();
    }
    
    public void Dispose() 
    {
        Dispose(disposing: true);
        GC.SuppressFinalize(this);
    }
    
    public void Dispose(bool disposing)
    {
        if(!this.disposed)
        {

            db.close();

            disposed = true;
        }
        
    }
    
    ~DatabaseManager()
    {
        Dispose(disposing: false);
    }
}

雖然C#在釋放記憶體前,會執行解構式。不過最好還是使用Dispose()方法來明確釋放非受控資源。實現了IDisposable可以使用using語法:

using(DatabaseManger dm = new DatabaseManager) 
{ }

這有點像是Python的:

with DatabaseManager() as dm:
    ...

最後在解構式在呼叫一次Dispose(),算是做一層保險,避免使用該類型的人忘記呼叫Dispose()。但為了避免被多次呼叫Dispose()而造成的重複釋放記憶體的問題,還需要一個guard變數disposed檢查。

注意:可以多次呼叫Dispose(),不代表在多執行緒下使用是安全的。

在了解以後,同理可以在JavaScript設計類似的類別(同樣這不是個可以執行的JavaScript):

const registry = new FinalizationRegistry((obj) => {
    obj.Dispose();
})


class DatabaseManager {
    #disposed = false;
    db = null;
    
    constructor() {
        this.db = Database.open();
        registry.register(obj1, obj1);
    }
    
    Dispose() {
        if(this.#disposed) {
            db.close();
            this.#disposed = true;
        }
    }
}

小結語

就算這個API對我沒啥用,不過知道瀏覽器怎麼手動釋放記憶體還蠻有意思的~

本文同時發表於我的隨筆


上一篇
你可能不知道的Web API -- postMessage
下一篇
你可能不知道的Web API--Web Locks
系列文
這些那些你可能不知道我不知道的Web技術細節33
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言