[英]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::Isolate
和v8::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 使用圖:
gc()
調用開始負載測試:沒有“泄漏”gc()
調用並開始另一個負載測試 session: "leak"gc()
調用:memory 使用量開始逐漸減少。gc()
仍在腳本中):memory 使用量迅速下降到基線值。我的問題是:
(這里是 V8 開發人員。)
- 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()
調用的影響。
- 為什么只有在使用 JS 的可選鏈時才會出現這種情況?
這是沒有意義的,我敢打賭,還有其他事情正在發生。
- 除了一直強制全 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.