JVMのメモリモデル

JVMメモリ構造の概要

JVMのランタイムデータ領域はいくつかの部分に分かれますが、オブジェクトのデータを主に格納するのはヒープ領域とメソッド領域(非ヒープ領域)です。これらの領域はスレッド間で共有されます。一方、スタック(仮想マシンスタック、ネイティブメソッドスタック、プログラムカウンタ)は各スレッドごとに独立しています。

ヒープ領域は主に以下の部分で構成されます。まず、ヒープはOld領域とYoung領域に分かれます。Young領域はさらにEden領域とSurvivor領域(S0とS1)に分けられます。Survivor領域のS0とS1はサイズが同じで、FromとToとして機能します。

メモリ内でのオブジェクトの割り当てと回収

以下に、ヒープ、メソッド領域、スタックの各領域で発生する代表的なメモリ例外の例を示します。

ヒープ領域のOOM(Out of Memory)

ヒープ領域がオブジェクトでいっぱいになり、新しいオブジェクトを割り当てるスペースがなくなると、OOMエラーが発生します。以下の例では、無限ループでオブジェクトをリストに追加し続けることで、ヒープを枯渇させます。

// JVM起動オプション: -Xmx50M -Xms50M
public class HeapOOMExample {

    public static void main(String[] args) {
        java.util.List<Data> dataList = new java.util.ArrayList<>();
        while (true) {
            dataList.add(new Data());
        }
    }
}

class Data {
    // 簡単なデータクラス
}

メソッド領域(メタスペース)のOOM

メソッド領域(Java 8以降はメタスペース)は、クラスやメソッドのメタデータを保持します。大量のクラスを動的に生成すると、この領域が枯渇し、OOMエラーが発生します。ASMライブラリを使用して多数のクラスを生成する例を示します。

// 依存関係 (Maven)
<dependency>
    <groupId>org.ow2.asm</groupId>
    <artifactId>asm</artifactId>
    <version>9.4</version>
</dependency>
// JVM起動オプション: -XX:MetaspaceSize=50M -XX:MaxMetaspaceSize=50M
import org.objectweb.asm.*;

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

public class ClassGenerator extends ClassLoader {

    public static List> generateClasses() {
        List> generatedClasses = new ArrayList<>();
        for (int i = 0; i < 100000; ++i) {
            ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS);
            classWriter.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, "GeneratedClass" + i, null, "java/lang/Object", null);

            MethodVisitor methodVisitor = classWriter.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "()V", null, null);
            methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
            methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
            methodVisitor.visitInsn(Opcodes.RETURN);
            methodVisitor.visitMaxs(1, 1);
            methodVisitor.visitEnd();

            ClassGenerator loader = new ClassGenerator();
            byte[] bytecode = classWriter.toByteArray();
            Class newClass = loader.defineClass("GeneratedClass" + i, bytecode, 0, bytecode.length);
            generatedClasses.add(newClass);
        }
        return generatedClasses;
    }
}
public class MetaspaceOOMExample {

    public static void main(String[] args) {
        java.util.List> classList = new java.util.ArrayList<>();
        while (true) {
            classList.addAll(ClassGenerator.generateClasses());
        }
    }
}

スタックオーバーフロー

スタックはメソッド呼び出しの情報を保持します。メソッドが再帰的に呼び出され続けると、スタックの深さが許容範囲を超え、スタックオーバーフローが発生します。

public class StackOverflowExample {

    private static long callCount = 0;

    public static void recursiveCall() {
        System.out.println(callCount++);
        recursiveCall(); // 無限再帰
    }

    public static void main(String[] args) {
        recursiveCall();
    }
}

タグ: JVM ガベージコレクション ヒープ メタスペース スタックオーバーフロー

5月16日 09:20 投稿