監視業務によくあるシステムのプロセス再起動はなぜ必要なのか?
コラム2:システムのプロセス再起動が必要な理由
ITシステムの運用現場では、様々なシステムがある。特に、アールワークスにおけるシステム運用・監視サービスでは、監視対象となるシステムを限定していない。顧客のシステムに応じて、それぞれのシステム(プログラム)の性格や特徴に応じて、オーダーメードな運用・監視サービスを提供している。そういったシステムそれぞれごとに応じた監視業務のルーチンワークとしてよくあるのが、Javaで開発されたシステムの、(特にTomcatなどのアプリケーションサーバー上で動くサーバーアプリケーション)プロセス再起動である。
それでは、なぜJavaプログラムに、定期的な再起動が必要になるのだろうか。
「スタック領域」と「ヒープ領域」
プロセスは、ぞれぞれ独自にメモリー空間を持つ。ここに、必要に応じてメモリー領域を確保してデータを保持し、そのデータを参照したり加工したり、というのがプログラムが行う作業の実体だ。例えば、32ビットOSの場合は2の32乗分、40億バイト(=4Gバイト)までのアドレスが扱える(図2)。
通常のプロセスの場合、このメモリー空間をプログラムの呼び出し元のアドレスなどを格納するための「スタック領域」と、オブジェクトをnew()すると確保されるような、自由に利用できる「ヒープ領域」に分けられる。C言語などで作られたネイティブプログラムの場合は、このヒープ領域のメモリー管理について、プログラムが自分で行わないといけない。つまり、C言語のプログラマーは、このヒープ領域のメモリー管理を意識してプログラムを書く必要があるのだ。
しかし、メモリー管理を意識するプログラムを書くのは難しい。皆さんは、ソフトウェアで何か作業をしていて突然そのプログラムがクラッシュしたり、普通に動いているのにもかかわらず、長時間動かしていると、ある日突然ダウンした、といった経験をしているのではないだろうか。
これらは、ほぼすべて、
「本来アクセスしてはいけないメモリー領域にアクセスし想定しない値を取得した」
「必要なくなったメモリー領域を開放しておらず、このヒープ領域を圧迫して動けなくなった」
など、メモリー管理にまつわるものだ。
自動メモリー管理をする「ガーベージコレクション」(GC)
このように、プログラミングとして難易度の高い「メモリー管理」をプログラムで自動で行わせ、プログラマーの負担を和らげたり、プログラムのクラッシュといったトラブルを軽減したりする試みが、ガーベージコレクション(GC)だ。
C言語のようなネイティブプログラムの場合は不可能であるが、Javaをはじめとするインタプリタ言語では、昔からインタプリタ上にGC機能を実装し、メモリー管理をすべてインタプリタに任せる、というアーキテクチャが採用されてきた。
Javaでは、JVM(Java Virtual Machine)という実行環境(つまりインタープリタ)にGC機能が実装されており、必要なくなったメモリー領域の開放は、すべてGCが行ってくれる。JVM管理下のメモリー空間において、このJVMプロセスが確保したネイティブのヒープ領域上に、独自のメモリー管理機構が動作するようになっている(図3)。
また、JVMでは、JNIなどのネイティブライブラリ、HotSpotというJust In Time(JIT)コンパイラによる、実行時コンパイルにより、部分的にネイティブコードが動作することがある。これらのネイティブコードが使用する「ネイティブヒープ領域(Cheap)」と、プログラムの中でクラスをnew()することで確保される「Javaヒープ領域」に大別される。Javaヒープ領域では、オブジェクト展開後の経過時間に応じて領域を世代別に管理している。その管理のため、Javaヒープ領域はさらに役割ごとに細分化されている。
ガーベージコレクション(GC)の具体的な処理動作
GCの処理内容とは、おおよそこういうものだ。
まず、オブジェクトが生成される〔new()を実行する〕と、ヒープ領域内に、メモリー領域が確保される。同時に、領域管理テーブルに、その確保された領域のアドレスがエントリーされる。「new()された」ということは、その領域は1つのオブジェクトから参照されているので、参照カウンタは‘1’となる。その後、他のオブジェクトからも参照されると、参照カウンタは‘2’となる。
プログラムには、スコープというものがあり、生成されたオブジェクトはスコープを超えて存在し続けることができない。またスコープの外側から、そのオブジェクトを参照することができない。このスコープの範囲としては、「クラス」であったり、「オブジェクトインスタンス」であったり、「メソッド」(関数)であったりする。
例えば、ある関数が呼ばれたとしよう。その関数の冒頭で、クラスAのオブジェクトが生成されたとする。このインスタンスのスコープは、この関数となる。関数が実行されている最中に、何度か参照されたり、書き換えられたりといったことが行われる。そして、関数の処理が終了し、関数から処理が抜けると、同時に、スコープを抜けることとなり、このオブジェクトは参照されなくなる。こうして、オブジェクトの参照元がなくなると、前述した参照カウンタが「0」になり、どこからも参照されない(つまり利用されていない)メモリー領域ができる。このような、どこからも参照されないメモリー領域を定期的に探し出して、見つけたらそれを自動で削除する、という処理を行っているのがCGだ。
CGは、このようにプログラマーが意識しなくても、必要なくなったメモリー領域を定期的に掃除してくれて、さらに歯抜け状態になったヒープ領域中に残存するメモリー領域を再配置し、常にメモリー空間を適切な状態に維持してくれるのである。