Home > 保険システム開発室NEWS > Javaのスタックとヒープ

Javaのスタックとヒープ

2008-12-14 19:30

私達はJava言語を得意とした部隊ですが、このオブジェクト指向言語で初心者が犯しやすいポイントがいくつかあります。

その一つは「変数にはインスタンス自体ではなく、インスタンスへの参照先(アドレス)が保持されている」ということが理解できていないことにより、オブジェクトの属性値がいつの間にか変わってしまうといったような不可解なバグを生み出してしまう事です
このようなバグを入れてしまうのはJavaのスタックとヒープの動きが理解できていない為です。

そういう私も新人の頃に同様のバグを起こして大変苦労した経験があります。 よって弊社での新人研修では私は必ずこのスタックとヒープの動きを説明します。また、新人の記述したソースコードをレビューする際は忘れずにこの点をチェックしています。

以下に、Javaのスタックとヒープに関しての説明と犯し易い間違いを簡単に説明します。

スタックとヒープ

Javaのメモリ領域は、大きく分けて「スタック」と「ヒープ」の2つが存在します。

スタック
 ・主にローカル変数が扱われ、その中にはインスタンスへのアドレスが保持される。
  ※変数はアドレスを入れる箱を考えてください。
ヒープ
 ・インスタンスそのものが保持される。
SampleClass sclass = null; ------ (1)
sclass = new SampleClass(); ------ (2)

上記のサンプルコードを下図と共に説明します。
まず(1)で変数を定義した際にはスタック上にアドレスを入れる箱が生まれます。
次に(2)でインスタンスを生成した際にはヒープ上にそのインスタンスが生成され、そこへのアドレスが(1)で定義した変数の中にセットされます。

スタックとヒープ

間違えやすいコードの例

先の説明を踏まえて、初心者が間違えやすいコードは以下の通りとなります。

StringBuffer sb1 = null;
StringBuffer sb2 = null;
sb1 = new StringBuffer("あ"); ------ (1)
sb2 = sb1; ------ (2)
sb1.append("い"); ------ (3)
System.out.println(sb1);
System.out.println(sb2);

最初に変数sb1とsb2を宣言します。この時点ではスタック上にsb1とsb2の変数の領域だけが確保されます。中身に入るべきアドレスは空の状態です。

次に(1)でStringBufferのインスタンスを生成します。ここで、ヒープ上にStringBufferのインスタンスが生成され、そのインスタンスへのアドレスが変数sb1に保持されます。

次に(2)でsb1をsb2に代入しています。ここで初心者は勘違いを起こす事があります。 初心者はsb2にStringBufferのインスタンスそのものをコピーしたと思ってしまいます。具体的には "あ"という値を持つStringBufferのインスタンスがもう一つ出来たと思ってしまうのです。

しかし、実際は下図の通り先にヒープ上に生成されたStringBufferのインスタンスへのアドレスがコピーされたに過ぎません。 同じアドレスがsb1とsb2に保持されますので、そのアドレスが指すヒープ上のインスタンスは同じものなのです。

スタックとヒープ

(3)でsb1変数を使用して"い"という文字列を追加します。

最後にsb1とsb2の値をSystem.out()で出力しています。
このプログラムの作成者はsb1は"あい"でsb2は"あ"と出力されることを期待したとしても、実際には下記の通りどちらも"あい"と出力されます。

サンプルプログラムの実行結果
あい
あい

上記のようなバグはソースを一見しただけでは気づきにくく、また状況によって不具合の現象も一定ではない為、原因の特定に時間のかかるバグです。
私が新人の頃に同様のバグを出した当時はJavaが生まれて間もない頃だった事もあり、 ここで説明したスタックとヒープの動きを理解している人が少なかった為、 恥ずかしながら原因の究明に何日もかかってしまったのを憶えています。

補足

上記で説明した「変数にはアドレスが格納される」というのは、オブジェクト型の変数に限ります。 プリミティブ型の変数には、値そのものが保持され代入した際には値そのものがコピーされるので上記のような不具合は発生しません。
このオブジェクト型とプリミティブ型のメモリの管理方法の違いが初心者を混乱させてしまう一つの原因のようです。

このスタックとヒープに関連して初心者が陥りやすい同様の間違いとして、仮引数と実引数の話があります。 2008年12月19日にJavaの仮引数と実引数という記事も書きましたので、是非御覧ください。

Posted by T.S

このページの上部へ