【Java】JVMにおけるメモリ管理とガベージコレクションについて

# 初めに
組み込み系エンジニアの友人と飲んでいた際、C言語ではメモリをユーザが意図的に開放しないとメモリリークが起きるということを教えてもらいました。
自分は普段Javaを書いてますが、メモリを意識してコーディングをしたことはほとんどありません。どのような仕組みになっているのか気になったため、Javaにおけるメモリ管理について調べてみました。

# Javaではメモリ管理は誰がやってるの？
CやC++ではユーザが意図的にメモリ管理をする必要がありました。JavaではJVMがその役割を担っています。メモリの使用や解放を自動で行ってくれるため、ユーザがメモリを意識せずにコーディングできるというわけです。

# メモリの種類
Javaプログラムが実行されると、Javaのプロセスにメモリが割り与えられます。そのメモリ領域は以下の４つの領域に分けられます。
* ヒープ領域
* スタック領域
* メタスペース
* ネイティブメモリ（※JVMではなくOSが管理する領域）

|領域|用途・特徴|
|--|--|
|ヒープ領域|生成されたインスタンスの情報が保存される領域。|
|スタック領域|実行中のプログラムの情報が保存される領域。<br>実行中の行が「どこから呼び出されていて、どんな情報を参照可能か」などを持っているイメージ。|
|メタスペース|クラス定義、メソッドコード、定数プールなどのクラス関連の情報が保存される領域。|
|ネイティブメモリ|Javaプログラムがネイティブコード（C、C++などで記述されたコード）を呼び出す際に使用される領域。<br>JVMではなくOSが直接管理をしている。|

上記４つのうち、プログラム実行中にメモリの管理が必要なのはヒープ領域のみです。
メモリ管理方法をみていきましょう。

## ヒープ領域のメモリ管理
ヒープ領域のメモリが解放される仕組みを知るには、**ヒープ領域の構造**と**ガベージコレクション**について知る必要があります。

### ヒープ領域の構造
ヒープ領域は大きく分けて以下の２つの領域で構成されています。
* Young：メモリに格納されてからの時間が比較的短いデータが格納されている領域。
* Old：メモリに格納されてからの時間が比較的長いデータが格納されている領域

Young領域にはさらにEdenとSurvivorの2つの領域に分けられます。
Survivorは領域が2つありますが、どちらも役割は同じです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/430841/b37189aa-cc8a-fcf1-71ed-a81c604c32a3.png)

### ガベージコレクション（GC）とは
ガベージコレクション（GC）とは、不要になったメモリを解放するJVMの機能です。
ヒープ領域を対象として不要になったメモリの解放を行います。
GCは主に以下の2種類があります。
* マイナー GC：Young領域を対象としたメモリ解放
* Full GC：ヒープ領域全体を対象としたメモリ解放

それぞれのメモリ解放の流れを説明します。
以下の記事がわかりやすかったので参考にさせていただきました。
https://qiita.com/e_tyubo/items/48398391a8ef0f24c1be

#### マイナーGCによるメモリ解放の流れ
マイナーGCは、Edenがいっぱいになったタイミングで発生します。
その際、以下のルールでデータを処理してメモリを解放します。
* Edenのデータ
  * データが不要（参照されてない）場合、削除してメモリを解放する
  * データがまだ必要な場合、Survivorに移動させてEdenを解放する
* Survivorのデータ
  * データが不要の場合、削除してメモリを解放する
  * データがまだ必要な場合、もう片方のSurvivor or Oldに移動させる（一定回数以上Survivorを行き来した場合はOld）


具体的に図で見ていきましょう。
EdenがいっぱいになるとマイナーGCが起きます。
その際、Edenのまだ必要なデータはSurvivorに移動されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/430841/af1d050c-0dd3-556e-3e9e-f360921cb845.png)

これでEdenのメモリがすべて解放されました。

その後、Edenのデータがたまっていくと再度マイナーGCが起きます。
Survivorにあるデータは不要になっていた場合解放されますが、まだ必要な場合はもう片方のSurvivorに移動されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/430841/8865beea-26e3-af8f-2bae-37f9f4267362.png)

これを繰り返すとSurvivorにあるデータは2つのSurvivor領域を行き来することになります。そして、行き来した回数が一定数を超えるとOld領域に移動されるというわけです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/430841/2aabf114-ed33-1b42-40ce-45b43299e6d2.png)


#### Full GCによるメモリ解放の流れ
マイナーGCを繰り返していると、Oldにデータがたまっていくことになります。
Old領域がいっぱいの状態でマイナーGCを実行すると、SurvivorからOldへデータを移動させようとしたタイミングで失敗します。
その失敗を契機として実行されるのがFull GCです。

Full GCでは、YoungとOldの両方に対してメモリ解放を行います。Young領域に対してはマイナーGCと同じルールで実施し、Oldについては不要になったメモリを解放するというシンプルなルールです。


# おわりに
今回はJVMにおけるメモリ管理についてまとめました。
Javaを書いているとメモリ管理を意識する機会は少ないですが、知っておいて損はないと思います。



# 参考
https://qiita.com/e_tyubo/items/48398391a8ef0f24c1be
https://itpfdoc.hitachi.co.jp/manuals/3020/30203M0461/EM040178.HTM
https://phoeducation.work/entry/20210212/1613088000
https://qiita.com/odekekepeanuts/items/b87bff49565fc9abc91b
https://doc.support-dreamarts.com/%E3%81%B2%E3%81%B3%E3%81%8DSm@rtDB/V45/%E3%81%B2%E3%81%B3%E3%81%8DSm@rtDB_Ver.4.5_%E9%81%8B%E7%94%A8%E3%82%AC%E3%82%A4%E3%83%89/appendix/914000/index.html
http://msugai.fc2web.com/java/perform/storage.html