メモリリークと簡単なヒープダンプを実験してみる話

## 序文

こんにちは。Qiitaに投稿する初めての記事は、メモリリークと簡単なヒープダンプを実験した話をしたいと思います。
※参考までに、今後Qiitaに投稿する記事は、以前私が韓国語で書いた記事を翻訳したものです。

私がこの話をするきっかけとなったのは、以下の技術ブログを読んだからです。
https://techblog.woowahan.com/2628/

この記事を簡単に説明すると、GCを実行したにもかかわらずヒープ領域に空きスペースができず、リークであることに気づき、ヒープダンプを分析して解決した話です。

この記事を読んで、「JVM? GC? そしてヒープダンプ?...」
JAVAを使っている人であるにもかかわらず、まだ整理できていなかったり、新しい単語に出会って悲しくなりました。

そこで、これを機に簡単にでも勉強してみました。

## 目次
1. JVM
2. メモリリーク
3. GC
4. ヒープダンプ
この順で整理してみましょう。

## 1. JVM

まず、JVMに触れるときには、JREやJDKという言葉も聞いたことがあるでしょう。これらの概念について整理してみましょう。

JVM
- Javaコンパイラが作成したバイトコードを各OSが理解できるように通訳する役割を果たします。

JRE
- JVM ＋ 実行に必要なライブラリファイル群
例：System.out や Scanner など

JDK
- JRE ＋ 開発に必要なツール（デバッガやコンパイラなど）
- Javaのバージョンを指します。
- LTS：長期間使用しても問題がなく、Javaコミュニティによる長期サポートを保証する特別なバージョンです。
- JDKには種類があります。（例：Oracle JDK、OpenJDK）



JVMも実際には、オペレーティングシステムからメモリを割り当てられるプログラムです。そのため、JVMが実行されるとRAMからメモリを割り当てられ、そのメモリ領域を論理的に再分割して、メソッド領域、ヒープ領域、スタック領域、PCレジスタ、ネイティブメソッドスタックの領域として使用します。


## 2. memory leak
これはJVM内部のメモリ領域のうち、ヒープ領域で発生します。
なぜ発生するのでしょうか？
使用されていないオブジェクトが参照状態のまま残り続けることで、GCがそれを除去できず、メモリが徐々に枯渇してしまうのです。

## 3. GC
ヒープの不要なオブジェクトを削除してくれます。
ヒープは大きく Young世代 と Old世代 に分けられます。
- Young世代 では Minor GC が発生します。
- Old世代 では Major GC が発生します。
メモリリークは主に Old世代 で発生します。Young世代は、GCの設計原理により、オブジェクトが生成されてからすぐに解放されるためです。


## 4. ヒープダンプ
ヒープダンプは現在のヒープの状態を確認するための診断表です。
Eclipse MAT（Memory Analyzer Tool）というプログラムを使用すると、どのオブジェクトがヒープ領域の大部分を占めているかを確認できます。

今回は簡単にメモリリークが発生するコードを作成し、ヒープダンプを生成して、MATを使用してその原因を特定できるか試してみましょう。

まず、IntelliJで次のようにクラスを作成して実行してみましょう。
```java
import java.util.ArrayList;

public class TestHeap {
    public static void main(String[] args) throws InterruptedException {
        test();
    }

    public static void test() throws InterruptedException {
        ArrayList list = new ArrayList();
        try {
            for(int i=0; i < 250000; i++) {
                list.add(new int[10000000]); // OOM 
                System.out.println(i);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

```

重要な点は!! VMオプションを別途設定する必要があることです！IntelliJのEdit Configuration設定に入り、次のように設定しましょう。

```
// Edit configuration
-Dfile.encoding=UTF-8
-Dconsole.encoding=UTF-8
-Xmx50m // ヒープサイズを最大50MBに設定する。
-XX:+HeapDumpOnOutOfMemoryError // OOMが発生したらヒープダンプを作成する。
-XX:HeapDumpPath=C:\Users\jmcho\Desktop // ヒープダンプの保存場所。
```

それでは結果はどうでしょうか？ドキドキ…！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3864348/07415b74-d4ec-66bb-b393-78427398f26c.png)


OOMが発生しました。

そして、デスクトップを確認すると、ヒープダンプファイルである.hprofファイルが生成されているはずです。

Eclipse MATをダウンロードして、この.hprofファイルを開いてみましょう。すると、次のような結果が表示されます。

![image (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3864348/99a1e1fc-910e-afec-5778-1b149d3b0c18.png)
![image (2).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3864348/23393f0a-4e74-ce03-675f-74d22dcfa4cb.png)


おお、問題の原因を正確に示していますね。
int[1000000...]の部分が現在ヒープ領域の98%を占めていると表示されています。

## まとめ
今回の機会を通じて、JVM、GC、ヒープダンプについて学ぶ時間を持つことができました。
元々はOS関連の知識がなくて挑戦を避けていましたが、簡単なコードでも体験し、直接目で結果を確認できたのはとても楽しかったです。

特にヒープダンプの分析は実務でも頻繁に行われる作業ですが、シニア開発者が担当する領域とも言われています。私ももっと勉強して、技術ブログに載っているように格好良く問題を解決できるようになりたいと思います。

日本の企業技術ブログを参考にしていたところ、サイボウズでもメモリリークが発生したという記事を読みました。
2015年の記事なので現在のJVMメモリ構造とは少し異なり、今では使われていないfinalize()が原因でしたが、要するにリソースを開いたら必ず閉じることを心がける必要があるという点が強調されていました。
https://blog.cybozu.io/entry/8218

次回は、Springに関する話をしてみたいと思います。