- ベストアンサー
循環参照を開放する理由に関して
初心者PHPプログラマです。 質問させて頂きます。 現在、「PHP Simple HTML DOM Parser」というライブラリを使用して、HTMLパースを行っております。 その際に、循環参照しているので開放しないとメモリリークが起こるという情報を得ました。 そこで初めて循環参照という言葉を知り、調べた結果、 「Aの中でBを呼び」「Bの中でもAを呼ぶ」という認識で覚えました。 しかし、循環参照をしている場合、開放をしてあげないとメモリリークが起きるという流れがよく理解出来ません。 これは一体どういう意味なのでしょうか? ご存知の方がいらっしゃいましたら、よろしくお願い致します。
- みんなの回答 (4)
- 専門家の回答
質問者が選んだベストアンサー
初めまして。 以下、ご回答になってるか分かりませんが、DOMというよりは、一般論的にご回答してみたいと思います。 まず、一般に、メモリは使用が終わったら開放してあげないと、循環参照であるかどうかに関わらず、メモリリーク(メモリが無駄に減り続けること)が起こります。 そして、循環参照とは無限ループ(永久ループともいい、プログラムが終了しないこと)の原因となるものです。 例えば、ご質問の、 「Aの中でBを呼び」「Bの中でもAを呼ぶ」 という処理を実際に展開すると、 Aが呼ばれ→Bが呼ばれ→Aが呼ばれ→Bが呼ばれ→Aが呼ばれ・・・・ というふうに、ABが交互に永久に呼ばれ続けます(AかBの中でプログラムが終了しない限り)。 このとき、AかBの中で、メモリを消費する処理があるとします。(大抵はあります)そのとき、そのメモリが適切に開放されていないと、上記の理由で、AとBが交互に呼ばれる度に、メモリをどんどん食っていくわけで、大抵はあっという間に空きメモリがなくなってしまいます。 また、上記A、Bを「関数A,関数B」と置き換えると、循環参照による無限ループは、スタックと呼ばれるメモリ(の一種)を消費し、いわゆるスタックオーバーフローの原因になり得ます。 以上の点を総合すると、 「循環参照しているので開放しないとメモリリークが起こる」 というよりは、 「メモリは使い終わったら開放しないとメモリリークが起こるが、そのとき循環参照をしていると、メモリがあっという間になくなるような、ひどいメモリリーク(やスタックオーバーフロー)の原因となり得る」 という解釈が正しいのかもしれません。 なお以上は私の理解に基づくもので、間違っている可能性もありますので、ご参考までになさってください・・・。
その他の回答 (3)
- Yune-Kichi
- ベストアンサー率74% (465/626)
循環参照は, $a = array(); $b = array(); $b[] = $a; $a[] = $b; のようになっているものを言います。 $b[0]は$aで,$a[0]は$bです。 これは簡単な例ですが,もっと多くのオブジェクトを経由して循環することもあります。 次に,PHPのガベージコレクトは,参照カウント方式が採られています。 この参照カウント方式は, ・カウンタの初期値は0 ・変数等に参照されるとカウンタをインクリメント ・ある変数等からの参照がなくなるとカウンタをデクリメント ・カウンタが0になるとメモリを解放する というものです。 ref) PHP: 参照カウント法の原理 - Manual http://www.php.net/manual/ja/features.gc.refcounting-basics.php 循環参照が発生している状態では,全ての変数から参照されていないにも関わらず,カウンタの値が0になりません。 $a = array(); // array(1) : refcount 1 ($a) $b = array(); // array(2) : refcount 1 ($b) $b[] = $a; // array(1) : refcount 2 ($a, $b[0]) $a[] = $b; // array(2) : refcount 2 ($b, $a[0]) この状態で関数から抜けても,$a[0]/$b[0]相当の参照が残ります。 すると,これらのメモリは永久に解放されなくなります。 これが循環参照によるメモリリークです。 ただし,マニュアルによるとPHP 5.3から循環参照時もGCさせるアルゴリズムを導入したとあります。 ref) PHP: 循環の収集 - Manual http://www.php.net/manual/ja/features.gc.collecting-cycles.php ただし,参照カウント方式のGCを使う場合は循環参照に気をつけるようにした方がよいのは言うまでもありません。 # 言語によっては弱い参照という,カウンタをインクリメントしない参照を用意しているものもあります。
- takkun3728
- ベストアンサー率70% (14/20)
なんどもすみません。。 ご質問者様が言われた「開放」は、メモリの開放ではなく、「循環参照から抜ける」という意味だったかもしれないと思いました。 その場合、「開放」とは、 (AかBの中でプログラムが終了するか、Aの中にBを呼ばないケースや、 Bの中にAを呼ばないケースが存在しない限り)。 を指すと思います。 つまり、それにより、再帰を抜けることで、無限ループ(によるスタックオーバーフロー)を防ぐ、という意味合いかもしれません。。 ただ、「開放しないとメモリリーク」というと、やはりメモリの開放をさしていたのではないか・・・などと、一人自問自答しました。。 解決しない場合はよろしければこの点、補足いただけると、他の回答者様の参考になるかもしれません。 ちなみにこんな議論もあるようです(^^;): http://okwave.jp/qa/q1106226.html
補足
まずは何度も回答して頂いたこと、本当にありがとうございます。 ほぼ解決しましたが、私の考えも伝えたかったため、補足に記述させて頂きます。 求めていた回答はNo1のものでした! その後自分なりに調べたのですが、 一般的に変数定義を行った場合、確保されたメモリはスコープを抜けた時点で自動で解放されるが、循環参照を行った場合、ガーベージコレクションが正常に行われず、メモリも解放されず残ってしまうので、手動で解放を行う必要がある。という考えに行き着きました。 ちなみに、解放せずに残ってしまったメモリは、スクリプトが終了しても残ってしまうという記述を今回の件について調べている時にみつけました。 もしそうであるのならば、先日サーバが半日近く止まってしまったのですが、原因は私のせいではないのかと震え上がっています。 今までメモリに関してそこまで深く考えたことは無かったのですが、そういった部分もしっかり考える必要があると考えさせられました。
- takkun3728
- ベストアンサー率70% (14/20)
一点補足させてください・・・ >例えば、ご質問の、 >「Aの中でBを呼び」「Bの中でもAを呼ぶ」 >という処理を実際に展開すると、 >Aが呼ばれ→Bが呼ばれ→Aが呼ばれ→Bが呼ばれ→Aが呼ばれ・・・・ >というふうに、ABが交互に永久に呼ばれ続けます(AかBの中でプログラムが終了しない限り)。 は、下記の方がより正しいと思います: 例えば、ご質問の、 「Aの中でBを呼び」「Bの中でもAを呼ぶ」 という処理を実際に展開すると、 Aが呼ばれ→Bが呼ばれ→Aが呼ばれ→Bが呼ばれ→Aが呼ばれ・・・・ というふうに、ABが交互に永久に呼ばれ続けます。 (AかBの中でプログラムが終了するか、Aの中にBを呼ばないケースや、 Bの中にAを呼ばないケースが存在しない限り)。 ※ケースとは、ifやcaseなどでの条件節による場合分けのことです。
お礼
回答ありがとうございます。 循環参照を行った場合に参照カウントの値が0にならなくなる。 よってメモリリークが起こる。 この流れに関しては調べているうちに理解出来たのですが、実際になぜ0にならなくなるのか曖昧なままだったので大変勉強になりました。 本当にありがとうございました!