HeapとStack

概要

  • JVMでのStackOverflowErrorとOutOfMemoryErrorの違いを調べてみました
  • 特にStackOverflowErrorはなぜ発生するのか?のイメージが湧かなかったのでChatGPTに問い詰めました

StackとHeap

両方プログラムが実行される際に使用されるメモリ領域を表しますが、異なる目的や特性を持っています

スタック(Stack)

スタックは、主に関数の呼び出しやローカル変数の格納などの短寿命のデータを管理するために使用される
プログラムが関数を呼び出すと、関数のローカル変数や戻りアドレスなどがスタックに積まれる
データは後入れ先出し(LIFO: Last-In, First-Out)の順序でスタックに格納され、関数が終了するとそのフレームのデータが削除される

ヒープ(Heap)

ヒープは、プログラムの実行中に動的に確保されるメモリを管理するために使用される
プログラム実行中に、必要に応じてヒープからメモリを割り当て、解放することができる
ヒープ上のメモリは手動で管理する必要があり、適切なタイミングで確保および解放する必要がある

ざっくりまとめると

スタックは静的でスコープが短いデータを処理し、ヒープは動的でスコープが長いデータを処理する
スタックは効率的で高速だけど、データのサイズや寿命が事前にわかっている必要がある
一方、ヒープは柔軟で動的なメモリ管理が可能だけど、その分オーバーヘッドが発生する

StackOverflowErrorとOutOfMemoryError

StackOverflowError

JavaでのStackOverflowErrorは、通常、再帰呼び出しが無限ループに陥ったときに発生する

public class StackOverflowExample {
    public static void main(String[] args) {
        recursiveMethod(0);
    }

    public static void recursiveMethod(int count) {
        System.out.println("Count: " + count);
        recursiveMethod(count + 1);  // 終了条件がないため無限に再帰する
    }
}

何が溢れてスタックがいっぱいになるのか?ChatGPTに聞いてみました

ChatGPT

Javaのプログラムが実行されると、プログラムの実行状態や関数の呼び出し履歴などがスタックメモリに格納されます
スタックメモリは、関数呼び出しや変数の格納などのために一時的に使用され、それぞれの関数呼び出しに対してスタックフレームが作成されます
上記の無限ループでは新しいスタックフレームが作成されてその中に同様の情報が格納されます
これが再帰呼び出しの際にスタックが積み重なっていき、、最終的にはスタックメモリが尽きてしまい、、その状態が続くと、StackOverflowErrorがスローされる

要は履歴データでいっぱいになるということか……

OutOfMemoryError

Javaのヒープメモリが上限に達すると、OutOfMemoryErrorが発生します
プログラムが多くのオブジェクトを作成し、それらが十分に解放されない場合、ヒープメモリが枯渇し、OutOfMemoryErrorがスローされます
こっちはなんとなくイメージできる

ArrayListに整数を追加し続ける無限ループの例

import java.util.ArrayList;
import java.util.List;

public class OutOfMemoryExample {
    public static void main(String[] args) {
        List<Integer> numbers = new ArrayList<>();

        try {
            while (true) {
                numbers.add(1);
            }
        } catch (OutOfMemoryError e) {
            System.err.println("OutOfMemoryError: " + e.getMessage());
        }
    }
}

メモリが不足すると、ガベージコレクションが解放できる余裕がなくなり、最終的にはヒープメモリが尽きてOutOfMemoryErrorが発生します
重要なのは、「OutOfMemoryError」が発生したときにそれをキャッチして復旧することが難しいという点、だそうです
ヒープメモリ不足が発生すると、プログラムが実行を続けるためには、通常の手段では対処できないケースが多いらしい

通常の手段では対処できないケースが多い理由
  • 実行中に発生する予測が難しいエラー(どの瞬間にどれだけのメモリが必要になるかを正確に予測するのは難しい)
  • ヒープメモリが不足した場合、プログラムが正常に動作することが期待できないため、エラーを捕捉して適切に復旧することは難しい
  • プログラムのメモリ使用量が許容範囲を超えていることを示しているため、この問題を解決するにはプログラム全体の再設計やメモリ使用の最適化が必要

まとめ

  • StackOverflowError, OutOfMemoryErrorは両方厄介なエラー
  • 個人的には「OutOfMemoryError」はどこが原因かはコードを調査して、試行錯誤して、解消へ踏み出せそう
  • 「StackOverflowError」は、とりあえずコードを調査して、謎すぎて頭抱えそう