簡體   English   中英

在腳本中使用可選鏈接時 V8 Memory 泄漏

[英]V8 Memory leak when using optional chaining in script

我已將 V8 9.5 嵌入到我的應用程序(C++ HTTP 服務器)中。 當我開始在我的 JS 腳本中使用可選鏈接時,我注意到在導致 OOM 的重負載 (CPU) 下 memory 消耗異常上升。 雖然有一些空閑的 CPU,但 memory 的使用是正常的。 我在 grafana 中顯示了 V8 HeapStats(這僅適用於 1 個隔離,我的應用中有 8 個) 堆統計

在重負載下, peak_malloced_memory會出現峰值,而其他統計數據受到的影響要小得多並且看起來很正常。 我已將--expose-gc標志傳遞給 V8 並在腳本末尾調用gc() 它完全解決了問題,並且peak_malloced_memory不會像那樣上升。 此外,通過反復調用gc()我可以釋放沒有它消耗的所有額外 memory 。 --gc-global也有效。 但這些方法似乎更像是一種解決方法,而不是生產就緒的解決方案。 --max-heap-size=64--max-old-space-size=64沒有效果 - memory 消耗量仍然大大超過 8(我的應用程序中的隔離數)*64Mb(> 2Gb 物理 RAM)。

我沒有在我的應用程序中使用任何與 GC 相關的 V8 API。

我的應用程序創建一次v8::Isolatev8::Context並使用它們來處理 HTTP 請求。

v9.7 的行為相同。

Ubuntu xenial

使用這些 args.gn 構建 V8

dcheck_always_on = false
is_debug = false
target_cpu = "x64"
v8_static_library = true
v8_monolithic = true
v8_enable_webassembly = true
v8_enable_pointer_compression = true
v8_enable_i18n_support = false
v8_use_external_startup_data = false
use_thin_lto = true
thin_lto_enable_optimizations = true
x64_arch = "sandybridge"
use_custom_libcxx = false
use_sysroot = false
treat_warnings_as_errors = false # due to use_custom_libcxx = false
use_rtti = true # for sanitizers

然后手動將 static 庫轉換為動態庫(由於我以后不想處理的 LTO,static 庫存在一些鏈接問題):

../../../third_party/llvm-build/Release+Asserts/bin/clang++ -shared -o libv8_monolith.so -Wl,--whole-archive libv8_monolith.a -Wl,--no-whole-archive -flto=thin -fuse-ld="lld"

我做了一些負載測試(因為問題只發生在負載下),有和沒有手動gc()調用,這是負載測試期間帶有時間戳的 RAM 使用圖: 內存使用情況

  1. 使用gc()調用開始負載測試:沒有“泄漏”
  2. 刪除gc()調用並開始另一個負載測試 session: "leak"
  3. 在低負載下帶回手動gc()調用:memory 使用量開始逐漸減少。
  4. 開始另一個負載測試 session( gc()仍在腳本中):memory 使用量迅速下降到基線值。

我的問題是:

  1. peak_malloced_memory 可以超過 total_heap_size 正常嗎?
  2. 為什么只有在使用 JS 的可選鏈時才會出現這種情況?
  3. 除了一直強制全 GC 之外,還有其他更正確的解決方案嗎?

(這里是 V8 開發人員。)

  1. peak_malloced_memory 可以超過 total_heap_size 正常嗎?

Malloced memory 與堆無關,所以是的,當堆很小時,malloced memory(通常也不是很多)可能會超過它,可能只是短暫的。 請注意,峰值分配的 memory(屏幕截圖中為 53 MiB)不是當前分配的 memory(屏幕截圖中為 24 KiB); 這是過去任何時候使用的最大數量,但后來被釋放(因此不是泄漏,並且不會隨着時間的推移導致 OOM)。

不是堆的一部分,分配的 memory 不受--max-heap-size--max-old-space-size影響,也不受手動gc()調用的影響。

  1. 為什么只有在使用 JS 的可選鏈時才會出現這種情況?

這是沒有意義的,我敢打賭,還有其他事情正在發生。

  1. 除了一直強制全 GC 之外,還有其他更正確的解決方案嗎?

我不確定“這個問題”是什么。 malloced memory 的短暫峰值(很快將再次釋放)應該沒問題。 您的問題標題提到了“泄漏”,但我沒有看到任何泄漏的證據。 您的問題還提到了 OOM,但該圖沒有顯示任何相關內容(在繪制時間 window 的繪制時間結束時,當前 memory 消耗小於 10 MiB,具有 2GB 物理內存),所以我不知道該怎么做。

手動強制 GC 運行肯定不是一個好主意。 它甚至影響(非 GC'ed,)分配 memory 的事實令人驚訝。 但可能有一個非常普通的解釋,例如(我在這里瘋狂推測,因為您沒有提供重現案例或其他更具體的數據),可能是短期峰值是由優化編譯引起的. 並且通過強制 GC 運行,您正在破壞如此多的類型反饋,以至於優化的編譯永遠不會發生。

如果您提供更多數據(例如復制案例),很高興仔細查看。 如果您看到的唯一“問題”是peak_malloced_memory大於堆大小,那么解決方案就是不用擔心。

我想我已經深究了...

事實證明,這是由 V8 的--concurrent-recompilation功能與我們的 jemalloc 配置相結合造成的。

看起來當使用可選鏈接而不是手寫 function 時,V8 更積極地嘗試同時優化代碼並為此分配更多的 memory(區域統計顯示 > 70Mb 的 ZCD69B4957F06CDE818D) 它特別在高負載下這樣做(也許只有在 V8 注意到熱功能時)。

jemalloc 反過來,默認情況下有 128 個 arenas 和background_thread禁用。 因為並發重新編譯優化是在單獨的線程上完成的,所以 V8 的 TurboFan 優化器最終在單獨的 jemalloc 的競技場中分配了很多 memory,即使 V8 釋放了這個 memory,因為 jemalloc 的衰減策略並且因為它沒有被訪問在其他任何地方,頁面都沒有被清除,因此增加了常駐 memory。

傑馬洛克數據:
memory失控前:

Allocated: 370110496, active: 392454144, metadata: 14663632 (n_thp 0), resident: 442957824, mapped: 570470400, retained: 240078848

memory失控后:

Allocated: 392623440, active: 419590144, metadata: 22934240 (n_thp 0), resident: 1712504832, mapped: 1840152576, retained: 523337728

如您所見,雖然分配的 memory 小於 400Mb,但由於有約 300000 個臟頁(約 1.1Gb),RSS 為 1.7Gb。 所有這些臟頁都分布在少數幾個與 1 個線程相關聯的領域(V8 的 TurboFan 優化器在其上進行並發重新編譯)。

--no-concurrent-recompilation解決了這個問題,我認為在我們為每個 CPU 內核分配一個隔離並平均分配負載的用例中是最佳的,因此從帶寬的角度來看,並發執行重新編譯幾乎沒有意義。

這也可以在 jemalloc 方面通過MALLOC_CONF="background_thread:true" (據稱可能會崩潰)或通過減少競技場MALLOC_CONF="percpu_arena:percpu"的數量(這可能會增加爭用)來解決。 MALLOC_CONF="dirty_decay_ms:0"也解決了這個問題,但它是一個次優的解決方案。

不確定強制 GC 如何幫助重新獲得 memory,也許它以某種方式觸發了對這些 jemalloc 競技場的訪問,而沒有在其中分配太多 memory。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM