質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
Chrome

Google Chromeは携帯、テレビ、デスクトップなどの様々なプラットフォームで利用できるウェブブラウザです。Googleが開発したもので、Blink (レンダリングエンジン) とアプリケーションフレームワークを使用しています。

JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

HTML

HTMLとは、ウェブ上の文書を記述・作成するためのマークアップ言語のことです。文章の中に記述することで、文書の論理構造などを設定することができます。ハイパーリンクを設定できるハイパーテキストであり、画像・リスト・表などのデータファイルをリンクする情報に結びつけて情報を整理します。現在あるネットワーク上のほとんどのウェブページはHTMLで作成されています。

Q&A

解決済

5回答

11380閲覧

domノードがメモリ開放されない?

hixtutokun

総合スコア21

Chrome

Google Chromeは携帯、テレビ、デスクトップなどの様々なプラットフォームで利用できるウェブブラウザです。Googleが開発したもので、Blink (レンダリングエンジン) とアプリケーションフレームワークを使用しています。

JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

HTML

HTMLとは、ウェブ上の文書を記述・作成するためのマークアップ言語のことです。文章の中に記述することで、文書の論理構造などを設定することができます。ハイパーリンクを設定できるハイパーテキストであり、画像・リスト・表などのデータファイルをリンクする情報に結びつけて情報を整理します。現在あるネットワーク上のほとんどのウェブページはHTMLで作成されています。

1グッド

4クリップ

投稿2018/06/14 13:36

編集2018/06/15 02:56

ノードを削除しているはずなのに解放されない

Javascriptでulノード下にliノードを追加や削除できる機能を作成しました。liノードを削除後メモリリークしていないかを、Google Chrome の DevTools の Performance で Memory Timeline をオンにして調べました。

結果はliノードを削除してもメモリグラフのNodeが減少しませんでした。下に載せたコードまで単純化して調べたところ、原因となる操作は「liノード内のbuttonノードをクリックする」ことでした。

buttonノードをクリックしなければNodeは減少します。しかしクリックするとNodeは減少しません。なぜなのでしょうか? 皆様のお知恵をお貸しください。

Node が減少しないコード

html

1<!DOCTYPE html> 2<html> 3 4<head> 5 <meta charset="utf-8" /> 6 <meta http-equiv="X-UA-Compatible" content="IE=edge"> 7 <meta lang="en"> 8 <title>Sample</title> 9 <meta name="viewport" content="width=device-width, initial-scale=1"> 10</head> 11 12<body> 13 <button id="addListItem">add</button> 14 <button id="deleteAllItem">delete all</button> 15 <ul id="list"></ul> 16 <script> 17 document.getElementById("addListItem").addEventListener("click", () => { 18 const ul = document.getElementById("list"); 19 const li = document.createElement("li"); 20 const btn = document.createElement("button"); 21 ul.appendChild(li); 22 li.appendChild(btn); 23 btn.appendChild(document.createTextNode("button")); 24 }); 25 26 document.getElementById("deleteAllItem").addEventListener("click", () => { 27 const ul = document.getElementById("list"); 28 while (ul.firstChild) { 29 ul.removeChild(ul.firstChild); 30 } 31 }); 32 </script> 33</body> 34 35</html>

再現方法

  1. 上記のHTMLをGoogle Chromeで読み込む
  2. addボタンを何回か押す
  3. buttonボタンをいくつか押す
  4. delete allボタンを押す

2から4の操作をPerformanceツールで計測すると、buttonボタンを押したliノード分のNodeが減少しません。画像はGarbageCollectしています。
イメージ説明

補足情報

OS: Windows 10 (ver 1709)
Browser: Google Chrome (ver 67.0.3396.87)

x_x👍を押しています

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

m.ts10806

2018/06/15 00:43

前後の画面キャプチャなど状況が分かるものをご提示いただけますか?
Lhankor_Mhy

2018/06/15 06:19

問題が再現しませんでした。NodesはGC時に全て回収されているように見えます。(Win7、Chrome67)
Lhankor_Mhy

2018/06/15 06:36

と思ったのですが、3の手順で複数個のボタンを押すと残るようですね。
Lhankor_Mhy

2018/06/15 09:07

と思ったのですが、Dev tools の記録開始後にGCしてから手順2をやらないとゴミが残るんですね。たしかに button を押した個数の Node が残りますね。手順2~4を繰り返すと累積分がGCで回収されないようですから、focus は関係なさそうな感じ。お騒がせしました。
hixtutokun

2018/06/15 12:33

Lhankor_Mhy さん 試していただきありがとうございます。
guest

回答5

0

自己解決

Chrome ver 69.0.3497.81 にて確認したところ Node は全て解放されました。皆様ご協力ありがとうございました。
イメージ説明

投稿2018/09/05 15:12

hixtutokun

総合スコア21

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

回答ではないのですが、共有のために回答欄使わせてください。

js

1 { 2 let add = function (){ 3 document.getElementById("list").appendChild( 4 document.createElement("li").appendChild( 5 document.createElement("button").appendChild( 6 document.createTextNode("button") 7 ).parentNode 8 ).parentNode 9 ); 10 } 11 document.getElementById("addListItem").addEventListener("click", add); 12 13 let remove = function(){ 14 const ul = document.getElementById("list"); 15 while (ul.firstChild) { 16 ul.removeChild(ul.firstChild); 17 } 18 delete ul; 19 document.getElementById("addListItem").removeEventListener("click", add); 20 delete add; 21 document.getElementById("deleteAllItem").removeEventListener("click", remove); 22 delete remove; 23 } 24 document.getElementById("deleteAllItem").addEventListener("click", remove); 25 }

終わった後に全部消してみましたが、やはりクリックされたbutton要素のノードはGCで回収されず。
ちなみに、removeしたリスナの方はGCで回収されてました。

投稿2018/06/16 01:09

Lhankor_Mhy

総合スコア36115

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

hixtutokun

2018/06/16 12:36

試していただきありがとうございます。やはり回収されませんね。 もしかしてMemoryグラフの機能がliノードを保持し続けているのでしょうかねぇ?それでGCの対象にならないとか。Performanceを計測しつつHeapSnapshotを録れば分かるかもしれない!と思ったのですが同時使用はできませんでした。 コードに問題がなくこれ以上回答がなければ、chromium に問い合わせてみます。仕様なのか安易に考えたくはありませんが、バグかもしれないので。
guest

0

appendChild の順番

<script> document.getElementById("addListItem").addEventListener("click", () => { const ul = document.getElementById("list"); const li = document.createElement("li"); const btn = document.createElement("button"); ul.appendChild(li); li.appendChild(btn); btn.appendChild(document.createTextNode("button")); });

リフロー回数的には、appendChild を逆順にすると良いと思います。
リフローが一回で済みます。

JavaScript

1btn.appendChild(document.createTextNode("button")); 2li.appendChild(btn); 3ul.appendChild(li);

focus 起因の可能性

buttonノードをクリックしなければNodeは減少します。しかしクリックするとNodeは減少しません。

今は検証する環境がない為、推測ですが、貼って頂いたSSのグラフを見ると、要素ノード一つ分のメモリ解放が行われていないように見えます。
「最後にクリックしたbutton要素がメモリから解放されない」と仮定すると、focus がある要素ノードをブラウザが捕まえているために解放できない可能性があります。
(勿論、削除されたなら、focusは外れるべきですので、これが原因なら、Google Chrome ver.67.0.3396.87 のバグになります)

切り分け手段として、下記が考えられます。

  • 要素ノード削除前に element.blur() でfocusを外す
  • button要素のclickにフックして、event.preventDefault() でデフォルト動作を無効化し、focusさせない

Re: hixtutokun さん

投稿2018/06/15 04:16

編集2018/06/15 04:17
think49

総合スコア18162

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

hixtutokun

2018/06/15 12:29

ご回答ありがとうございます。 リフローの事はすっかり忘れていました。ありがとうございます。 focusの視点はなかったので勉強になります。早速試してみましたがNodesグラフは減少しませんでした。removeChild 前に ul.firstChild.firstChild.blur() をしました。同じく preventDefault() 案もだめでした。 最後にクリックしたbutton要素以外も残るのでfocusが原因ではなさそうです。
guest

0

liのNodeの変数を、イベントリスナー内でcreateして定義しているので、解放しずらいと思うので、
外部で定義して、removeChildした上で、明示的に変数の解放をするのはいかがでしょうか。
(上記考えのもと、constだと、変数の中身が変えられないため、liなどはletで宣言するようにします)

javascript

1// Nodeを入れるための配列を外で定義 2const liArr = new Array(0); 3const ul = document.getElementById("list"); 4 5document.getElementById("addListItem").addEventListener("click", () => { 6 // 変数の中身を変えられるよう、letで宣言 7 let li = document.createElement("li"); 8 let btn = document.createElement("button"); 9 btn.appendChild(document.createTextNode("button")); 10 li.appendChild(btn); 11 // 外部から扱いやすくするため、外部の配列に入れる 12 liArr.push(li); 13 // 一応、配列から取り出して、ulに入れる 14 ul.appendChild(liArr[liArr.length - 1]); 15}); 16 17document.getElementById("deleteAllItem").addEventListener("click", () => { 18 while (ul.firstChild) { 19 ul.removeChild(ul.firstChild); 20 // Nodeが入った配列の要素に空要素を入れた上で、頭の要素を削除 21 liArr[0] = null; 22 liArr.shift() 23 } 24});

実際試したわけではないので、ご了承ください。
間違っていたらご指摘いただければと思います。

また、NodeというかDOMオブジェクトに関しては、
GCの扱いが違うようです。
他の言語にも見られる参照カウンタというものがあり、それが0にならない限り、
その変数は解放されないようです。
(HTML上で、JSでDOMのコントロールをする場合、プロトタイプチェーンもありますし、
自動的に付加されるイベントもあるので、参照カウンタという概念が必要なのだと思います)

下記記事が非常に参考になるかと思います。
DOM オブジェクトとメモリリーク: Days on the Moon

think49さんの回答もかなり核心だと思いますので、
その辺あたり(参照カウンタとか)で、調べて見てはいかがでしょうか?

投稿2018/06/15 01:25

編集2018/06/15 05:23
miyabi_takatsuk

総合スコア9528

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

hixtutokun

2018/06/15 02:47

ご回答ありがとうございます。 早速試してみましたが、Nodeグラフは減少しませんでした。 わざわざコードまで書いていただきありがとうございました。 余計なお世話かもしれませんが、変数に値を再代入しないのであればconstを使ったほうがいいと思います。配列の変数をconstで宣言しても、インデックスによる代入やpush等はできますので…
miyabi_takatsuk

2018/06/15 04:07

hixtutokunさん> そ、そうですね。あとで自分で見て気づきました。 ご指摘ありがとうございます。 こちらも実際に動かしてなんか方法がないか探って見ます。
guest

0

もしかしたらこれかなぁと。

取り除かれた子ノードはしばらくは記憶領域上に残りますが、もう DOM の一部ではありません。

DBにおいてもDELETE文は記憶領域までは解放されないので考え方としては同じですね。

投稿2018/06/15 00:47

m.ts10806

総合スコア80850

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

hixtutokun

2018/06/15 02:28

ご回答ありがとうございます。 私もそう思って再現させてGarbageCollectを発動させてから5分ほど放置してみたのですが、liノード分は残ったままでした。また取り除かれた子ノードがしばらくは記憶領域上に残るのであれば、button を押さずにdelete allボタンを押した後もNodeグラフは減少せず横ばいのままのはずです。 しかし実際には減少するので…なんなのでしょうね?
m.ts10806

2018/06/15 02:38

勘になりますが、GarbageCollectは全てのものに対して起こるわけではないのかなあと。 もしかしたら場合によりすぐメモリからサヨナラされてるケースもあるかもしれません。 余程のメモリリークが起きるようであれば改修を検討してもいいかとは思いますが、 気になるのであればひとまずmiyabi_takatsukさんの回答にあるようにnullを設定して明示的に解放してもいいかもしれません。
hixtutokun

2018/06/15 13:07

参考記事ありがとうございます。 記事内容のようなことも踏まえてイベントリスナーが外部変数の参照を持たないようにし、循環参照もなくした結果が質問のコードです。 Heap snapshot ではなく、とりあえずのメモリリーク確認としてNodesグラフを利用しようと思ったのです。なかなか難しいですねぇ。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問