前書き
- すべての記事は、自分の勉強目的と主観の整理を含めています。あくまで参考レベルで活用してください。もし誤った情報などがあればご意見をいただけるととっても嬉しいです。
- 内容では、省略するか曖昧な説明で、わかりづらいところもあると思います。そこは、連絡いただければ補足などを追加するので、ぜひ負担なくご連絡ください。
- 本文での「GC」は、「Garbage Collection, Garbage Collector」の意味しており、略語として使われています。
- この記事は、連載を前提に構成されています。
※ 連載目録
- PHP・GCの話-1話)なぜGarbageCollection? メモリとGCを意識する(←現在の記事)
- PHP・GCの話-2話)変数の管理情報、zval containerとreference count 『投稿済み』
- PHP・GCの話-3話)変数データのメモリからの消滅 『投稿済み』
- PHP・GCの話-4話)MemoryLeakと解除できない変数データ 『投稿済み』
- PHP・GCの話-5話)GC登場。GC発生条件とroot buffer 『投稿済み』
- PHP・GCの話-6話)管理対象の巡回・削除。Garbage Collection Cycle 『投稿済み』
- PHP・GCの話-7話)GC 関連機能紹介1 (GC Statistics)『投稿済み』
- PHP・GCの話-8話)GC 関連機能紹介2(Weak Reference, Weak Map)(END)『投稿済み』
※ 連載で使うサンプルコード
● ExampleGc.php : 2話から6話までの内容で使うサンプルコードです。
● ExampleWeakReference : 7話のWeakReferenceの内容で使うサンプルコードです。
はじめに
今回は概論と本人の持論が主な話になります。メモリとGCに対する知識を既にお持ちの方は、すぐに次の話から読んで頂いても良いと思います。2話からは、PHPの仕様に基づいた客観的な内容が主になります。
1. GCは何をするの?
GCとは、一言で「メモリ内のゴミ自動回収仕組み」と言えます。それを意識しながら内容をご覧いただけると良いと思います。
この概念は、日常生活のゴミ捨てとかなり似ています。
- 呼ばなくても、回収に来てくれる。(自動回収)
- 毎週、指定日に回収に来てくれる。(GC発動条件)
もし、上記の仕組みがなかったら、私達はゴミ処理において相当苦しみを感じたと思います。
GCは、プログラムが使うメモリ空間において、ほぼ似たような役目を遂行します。
プログラム内のメモリ空間内のゴミ状態の参照や変数を、特定条件が達したタイミングで自動でメモリから解除し、メモリ空間を広く確保してくれます。「エンジニアが明示的に解除しなくても」やってくれるのが核心です。
2. なぜGCを知る必要があるの?
1) PHPにおいてのGCは重要?
実のところPHPは、Java・Tomcat基盤などの資源共有型のマルチスレッドステムとは違い、一つのリクエストに対して単体プロセスとして実行させ終了する形で使われる場合が多いです。
こういう単体プロセスで処理されるタイプの場合は、プロセス終了とともにすべてのメモリ専有が解除されるため、Memory LeakとGCに対するトラブルで大きい問題になる場合は少ないです。
しかし最近のPHPは、
- パッケージやフレームワークをベースとした開発によりプログラムのサイズは増大
- バッチ系やデーモン系の大容量処理プログラムなどのニーズも多くなったこと
により、PHPにおいてのメモリ管理の関心は高くなっていると思うので、軽く知っておいても良いと思っています。
2) GCは多くの言語においての「メモリ管理メカニズム」
GCは、プログラム開発分野において、ある程度「共通認識」になったと言っても過言では無いと思います。
プログラムを作ることにおいて、メモリ管理というのは、昔から多く語られる課題と言えます。
最近の開発言語は、エンジニアがメモリ管理に多く気にしなくてもいいように、仮想マシーンやインタプリタなどの言語プラットフォームが大半を管理してくれます。
PHPはメモリ管理メカニズムの一つとして、GCを導入しています(Version 5.3以降)。
PHP以外にも、Java(Kotlin),C#、Python,Go, Rustなどなどの言語から、具現体により差はあるかもですが、大人気のJavaScriptもまたGCのメカニズムを導入しています。
3) それでもメモリは有限である
Symfony\Component\ErrorHandler\Error\FatalError
Allowed memory size of 134217728 bytes exhausted (tried to allocate 83886112 bytes)
私達はプログラムを作る時、ある情報を保存・取得・処理・提供するため、様々な情報を変数としてアサインしています。
その情報はシステムマシーンの「メモリ」と言う空間に保存されます。そして変数の大きさに比例し一定の空間を占有します。
しかし、メモリは物理的な空間であり、容量の限界があります。
もしメモリが足りなくなり、それを放置すればシステムはダウン状態に落ち、トラブルになります。
特に最近は、開発トレンドが、フレームワークやパッケージ組み合わせ開発が主類になっています。
そこで生産性と大型システム開発の容易性は飛躍的に上がりましたが、必要以上の機能を搭載することにより平均的にはプログラムが要求するシステム資源もまた上がっています。
PHP、OSや、クラウドサービス(例:AWS)などでそれを防ぐために用意された仕組みが色々あります。
しかし、それだけに依存するのは制約・費用・運用面で、長期的に色々デメリットを起こす可能性が高いので、すごく損することだと言えます。
4) GCも万能の神様ではない
GCも万能ではなく、エンジニアがメモリを全く気にしない実装をすると、時々に、すごく難題の欠陥を作ったりします。自分が考えるたとえとしては以下の3つがあります。
PHPでは、要請を独立したプロセスで処理する場合が多いので、影響は少ないかもですが、無関係では無いところと、他の言語でのGCを見る観点としては、役に立つと思うので、参考までにご覧ください。
● GCの発動条件。常に発動するわけではない。
言語によってGCの発動条件はそれぞれ違いますが、常に発動しているわけではありません。
つまり、GCが発動する前に、メモリが足りなくなり、プログラムが落ちてしまうことは全然ありです。PHPの例えを簡単に引用すると、PHPはメモリ解除候補が10000個に達した条件でGCが発動しますが、条件に達する前にメモリ制限の容量まで達してしまうと、プログラムがMemoryLimitErrorで落ちてしまいます。
(PHPのGC発動条件に対しては、後ほど詳しく扱う予定です。)
● GCが発生すると言うのはすでにメモリ空間においての赤信号!
GCの発生条件ともつながる話ですが、GCが発生するという状態は、「ついにメモリを回収しないと行けない状況になってしまった」という状態であることを肝に銘じる必要があります。
※1
● GCが発動して終わるまで、プログラムがフリーズするか、システム負荷が相当かかる!
GCの発生と遂行は、プログラムにおいての最優先処理事項として遂行されます。
つまり、該当プロセスのすべての作業を止めて、GCを遂行するということを意味します。GCで回収している間は、プログラムがメモリ空間を使ってしまったら整合性が崩れるからです。
しかしGCは相当重い処理であり、時々相当時間がかかってしまう場合もあります。GCに時間がかかっている間、プログラムはFreeze状態になります。
それは、つまり処理の遅延を意味し、ユーザーサービスにおいては、敏感な問題になります。※1
5) メモリ最適化は、関心度が高い課題
PHPに例えるなら、
- Version 5.3から、GCメカニズムを導入し、
- Version 7では、Weak Reference Typeを提供し、
- Version 8では、WeakMap Typeを提供する予定(RFC確定)
で、PHP内でもメモリ最適化の関心度は高いと思っています。
そして、PHPだけじゃなく開発全般において、関心度が高いものだと言えます。
それを少しでも意識しておくことは、開発全般においてもメリットになると思ってます。
6) メモリとGCを少しでも意識した上で、プログラムを作り運用する
メモリとGCを意識したシステム作りと運用は、長期的にマシーン性能にかかるコストと、メモリ関連問題発生時の運用コストの節減につながると思います。
特にメモリに関わる欠陥問題は、事前検知が難しい場合も多く、起きたとしても原因特定と解決に苦難する場合が多いです。
なので、メモリとGCを意識するのは、
- 難しい欠陥問題に対する予防策であり、
- 問題が起きたときの対処ノウハウにもつながる。
のではないでしょうか。
3. 「PHP・GCの話」では何をやるの?
1) 事前に知っておいたらいいのは?
以下の内容を事前に熟知しておくと、記事を見ることに役立つと思います。
- PHPとOOPの基本概念
- 変数・参照変数の基本概念
- システムマシーンのメモリの基本概念
- GCの基本概念(当記事や、Wikipediaなどの内容でOK、1ページ以下で収まるような内容がおすすめ)
2) 連載で扱う内容のオーバービュー
- PHP・GCの話-1話)なぜGarbageCollection? メモリとGCを意識する
- GCの役目、GCをなぜ知る必要があるかの持論と概論で構成されています。
- PHP・GCの話-2話)変数の管理情報、zval containerとreference count
- PHPの変数管理のメカニズムと、参照カウントの概念を説明します。
- PHP・GCの話-3話)変数データのメモリからの消滅
- 変数のデータが消滅してメモリが確保される条件を説明します。
- PHP・GCの話-4話)MemoryLeakと解除できない変数データ
- 無効になってもなお、メモリから解除されず、残り続ける現象について説明します。
- PHP・GCの話-5話)GC登場。GC発生条件とroot buffer
- GCの役目、発動条件、GC監視対象を保存するroot buffer仕様を説明します。
- PHP・GCの話-6話)管理対象の巡回・削除。Garbage Collection Cycle
- GCがどういうメカニズムでGC監視対象のデータを巡回し、解除するとかを説明します。
- PHP・GCの話-7話)GC関連機能紹介(GC Statistics, Weak Reference)(END)
- GC分析ツールと、Weak Reference(>PHP7.4), WeakMap(>PHP8)を簡単に説明します。
3) 参考した資料は?
主ににPHP MANUALを参考にしています。
https://www.php.net/manual/en/features.gc.php
https://www.php.net/manual/en/langref.php
4) 連載に使うサンプルコード
● ExampleGc.php : 2話から6話までの内容で使うサンプルコードです。
● ExampleWeakReference : 7話のWeakReferenceの内容で使うサンプルコードです。
GCに関する本連載記事は、基本的にこのサンプルコードをベースにPHP-GCの特徴をはなしていきます。
サンプルコードは、必ず見る必要も実行してみる必要もありません。
各話ごとに、コードを分解して動作原理と結果を解説しますので、基本記事の内容で足りるように心がけます。
あくまで、全体コードをみたい、手元で回してみたい、修正して回してみたいという方向けです。
そして、このサンプルコードは、あくまでGC説明のため用意されたコードなので、書き方としてはよくないパターンもあります。
コードの完成度よりも、GCの動作理解の参考までにご活用ください。
加えて、本コードはlaravelフレームワークをベースに作成されていますが、Laravel自体を詳しく知る必要はありません。そして、あくまでGCの開設のため書かれたコードで、実用性のあるコードではあることをご理解ください。
反面、そのコードを見て、
- コード動作で、内部でどういうものが起きるのか?
- どういうケースになれば、メモリが足りなくなり、問題が発生しそうなのか?
- 使われた参照変数はどういう関係図になるのか?
- どこで変数の有効範囲が切れるか?メモリリークはどこで起きるか?
- どういう絵図で、zvalとroot zval, Garbage Collection CycleのGC対象巡回が行われるのか?
- 弱参照タイプとは?
に関してすでにご存知の方は、当連載記事の大半の内容をすでにご存知の方なので、読む必要はあんまり無いかもしれません。
Summary
今回で、最低限に覚えて頂くと良い内容は以下になります。
- GCは「メモリ・自動回収仕組み」
- GCは、プログラム開発においての共通認識
- メモリは有限である
- GCも万能ではない
今回の後書き
今回は主観的持論や概論を主に話しましたが、こういう系の記事が一番むずかしい気がします。
なので自分としては、今の記事の細かな内容よりも、「GCの役目」と「なぜGCを知る必要があるか?」の問を心のどこかでとどめていただければ、嬉しいと思っております。
次回からは、主にPHPに絞ったGCの話をやっていきます。実際のサンプルコードを使いづつ、今回の話よりは客観的な内容になると思います。だからこそGCに対して、読者自身の主観を持った上で見ていただけることで、より効果的な情報共有することができるという思いで、今回の話を書いてみました。
では、次回も頑張って整理していきたいと思います。
※注釈
※1
▶ 「ついにメモリを回収しないと行けない状況になってしまった」という状態であることを肝に銘じる必要があります。比較的に最近までは、GCが起きることは赤信号という風にみることもでき、特に古いシステムではよくトラブルになるポイントとなっていました。
しかし、厳密にいうとこの内容は、もう100%当てはまりません。最近はJavaのG1GC(Garbage First GC)は短い周期でGCを起こし、オーバーヘットを最低限にするとかもしており、Java 9からはデフォルトで推奨されています。近いうちには、この内容が大きく変わるかもしれません。