← ./articles-ja

WebView2のメモリが高く見える時はTask ManagerとV8 heap snapshotを比べる

WebView2、Tauri、似たWebView stackで作ったdesktop appは、Task Manager上でメモリを多く使っているように見えます。しかし、それだけでJavaScript heapがleakしているとは限りません。

V8はobjectを回収した後もOS pageを保持することがあります。Task Managerはprocess memoryを示します。live JavaScript objectが増え続けているかは分かりません。

state layerを書き換える前に、Task Managerとheap snapshotを比較します。

false-positive leak

よくある観測です。

Task Manager:
  app process grows from 120 MB to 260 MB

App behavior:
  no obvious slowdown
  no growing list in UI
  clearing data does not immediately lower RSS

これはV8の通常のmemory behaviorである可能性があります。V8は再利用のためにmemory pagesをOSへすぐ返さないことがあります。

必要なのはprocess RSSだけではなく、live JavaScript heapです。

heap snapshotを取る

WebView2 instanceへEdgeまたはChrome DevToolsでattachします。

  1. WebViewのDevToolsを開く
  2. Memory panelへ行く
  3. heap snapshotを取る
  4. leakが疑われる操作後にもう一度取る
  5. retained objectsとheap sizeを比べる

Task Managerは高いがheap snapshotが小さく安定しているなら、JS object leakとは言い切れません。V8のpage retentionかもしれません。

Task Managerもheap snapshotも単調増加するなら、leakの根拠が強くなります。

高頻度state updateはGC pressureを作る

永久leakでなくても、高頻度updateはmemoryとCPUを悪く見せます。

desktop toolではよくあります。

  • serial logs
  • terminal output
  • file watcher streams
  • telemetry graphs
  • build logs
  • trace viewers

非効率なpatternです。

socket.onmessage = (event) => {
  useLogStore.setState((state) => ({
    lines: [...state.lines, event.data],
  }));
};

毎秒何十回も配列を作り直すと、短命objectが大量に生まれます。

bufferしてUI速度でflushする

ReactやZustand更新の前にbatchします。

const buffer: string[] = [];

socket.onmessage = (event) => {
  buffer.push(event.data);
};

setInterval(() => {
  if (buffer.length === 0) return;

  const batch = buffer.splice(0, buffer.length);

  useLogStore.setState((state) => ({
    lines: [...state.lines, ...batch].slice(-10_000),
  }));
}, 200);

state writeを「eventごと」から「毎秒5回」へ減らせます。logやmonitoring viewなら200ms程度は十分responsiveです。

使われていないstore fieldを消す

ZustandやRedux storeに、もうcomponentが読まないfieldが残っていることがあります。毎回copyされるなら、未使用でもallocationに効きます。

rg "unusedFieldName" src

componentが読まず、persistence formatにも必要ないなら削除します。特にdead arrayは高頻度update pathで高コストです。

leak診断とoptimizationを分ける

判断ルールです。

RSS high, heap stable
  -> JS leakの証拠としては弱い
  -> performanceが悪いならupdate frequencyを最適化

RSS high, heap grows, retained objects grow
  -> JS retention leakの可能性が高い
  -> retaining pathを見る

CPU high, heap stable
  -> update/render pressureの可能性
  -> batch events and reduce renders

Task ManagerだけでWebView2 appをleak扱いしないことです。

参考