2025年12月4日

【Android – メモリ管理②】GCの仕組みについて


Content
こんにちは、はるです!
今回はAndroidにおける基礎の一種であるメモリ管理についてお話ししていこうと思います。
本記事を読む前に必ずパート1を読むことを推奨します〜!

⭐️ この記事で理解できること
・GC Rootsについて
・到達可能性について
・参照の種類について

GC Rootsについて

⭐️ 概要

先に用語を定義します。

・オブジェクトグラフ → 上記画像の全体のグラフを指します(オブジェクト間の参照関係を示す)
・GC Roots → オブジェクトグラフの赤いノード(全ての参照の親)

まず、オブジェクト間の参照関係は上記画像のように有向グラフで表現されます。

⭐️ なぜ有向グラフ?
それはノードが複数の親から参照される場合を表現するためです。
GC Rootsでは親子関係を下記のように定義しています。
親:任意のオブジェクトへの参照を持つオブジェクト
子:親から参照されているオブジェクト

例えば、下記のようなコードを考えてみましょう。


val child = MyObject()
val parent1 = mutableListOf(child)
val parent2 = mutableListOf(child)

この時、childはparent1/parent2と二つの親から参照されています。
これをグラフとして表現するために有向グラフが用いられています。

⭐️ GC Rootsとは?
GC Rootsとは、ランタイムがプログラム実行のために管理している、特別な参照の集合です。
具体的には以下のような参照がGC Rootsになります:

・スタック上のローカル変数: 実行中のメソッドで使用している参照
・静的変数(static変数): プログラム全体で共有される参照
・JNI参照: ネイティブコードから参照されているオブジェクト
・アクティブなスレッド: 実行中のスレッドオブジェクト

これらはプログラムが動作するために必須の参照であり、ガベージコレクタはこれらを起点として「どのオブジェクトがまだ必要か」を判定します。
つまり:
主目的: プログラム実行のための参照管理
副次的用途: GCの生存判定の起点

ということです。

⭐️ オブジェクトグラフとは?
オブジェクトグラフとは、プログラム実行中のオブジェクト間の参照関係全体を表す有向グラフです。

⭐️ オブジェクトグラフとGC Rootsの関係

オブジェクトグラフは1つですが、GC Rootsは複数の起点を持ちます。
ガベージコレクタは、これらのすべての起点から辿って到達可能なオブジェクトを「生存中」と判定します。

到達可能性(Reachability)について

⭐️ 到達可能性とは
GC Rootsを起点として、参照を辿ってそのオブジェクトに到達できるかどうかを表します。

・到達可能: GC Rootsから参照を辿って到達できる → 生存
・到達不可能: GC Rootsからどう辿っても到達できない → 回収対象

⭐️ 走査アルゴリズム
これは非常にシンプルでBFS/DFSです。
AndroidだとDFSらしいです。(確証はありません)

ただ、ここは本質ではないです。
各ノードが到達可能かどうかを調べることが目的でありBFS/DFSのパフォーマンス(空間計算量及び時間計算量)は然程変わりませんからね。

⭐️ 到達可能性を判断するアルゴリズム
Mark & Sweepアルゴリズムが使われています。
名前の通り、MarkというフェーズとSweepというフェーズで分かれています。

== Mark処理 ==
GC Rootから各ノードを走査していき、到達したノードに対して各ノードが持つmarkbitというのを1にします。
このmarkbitというのが到達可能性を示すものです。

markbit=1 → 到達可能 = 別のオブジェクトから参照されている
markbit=0 → 到達不能 = 誰からも参照されていない
到達可能ノードをマーキングするという名前通りの挙動です。

== Sweep処理 ==
英語の意味通り、掃き集めるみたいな意味です。
ここでは2つの処理をしています。

1.不要ノードの回収(GC)
オブジェクトグラフ内におけるmarkbitが0のものを回収します。
厳密にはGCはヒープ内で実行されるので少し違いますが省略します。

2.markbitのリセット
全ノードのmarkbitをリセットします。
こうすることで、次回のGCが走った時に新たに到達不能ノードになったノードを把握することができます。

参照の種類について

参照と一括りにしても大きく4種類に分かれます。

⭐️ 強参照


val obj = MyObject()

普段使っているものですね。
特徴は
・GC Rootsから到達可能な限り絶対に回収されない
・明示的にnullにしないと残る

⭐️ 弱参照


val weakRef = WeakReference(MyObject())
val obj = weakRef.get() // 次のGC後はnull

特徴は
・強参照と合わせて使われたりすることが多い
・次のGCで確定で回収される

つまり、弱参照だけでは即座に回収されるので使い物にならないです。
ですから、強参照されているオブジェクトに対して弱参照をかけることで「強参照が消えたら連動して消える」を再現できます。
そのため、わざわざnullを明示的にセットしなくていいのです。

⭐️ ソフト参照


val softRef = SoftReference(MyObject())
val obj = softRef.get() // null の可能性あり

特徴は
・メモリが逼迫すると回収される
・キャッシュなど一時的なものに有効

⭐️ ファントム参照
これはほとんど使わないので省略

まとめ

本記事では以下を理解しました:

⭐️ GC Rootsについて
・オブジェクト間の参照関係はオブジェクトグラフ(有向グラフ)で表現される
・GC Rootsはプログラム実行に必須の参照(Stack上の変数、Method Areaのstatic変数など)
・Heapはオブジェクトの置き場であり、GC Rootsにはならない

⭐️ 到達可能性について
・GC Rootsからオブジェクトに到達できるかで生存を判定
・Mark & Sweepアルゴリズムで到達可能性を判定し、不要なオブジェクトを回収
・到達可能 = 回収しない、到達不可能 = 回収する

⭐️ 参照の種類について
・Strong Reference(強参照): 通常の参照、明示的にnullにしない限り回収されない
・Weak Reference(弱参照): 強参照が消えると次のGCで回収される
・Soft Reference(ソフト参照): メモリ逼迫時に回収される
・Phantom Reference(ファントム参照): 特殊用途

以上をここで知ってもらえればと思います!!
次回(Part3)では、ARTとGCの関連性について詳しく解説します。
具体的にはAndroidのメモリ管理の実装詳細や、実践的なメモリリーク対策などを扱います。

2025年12月4日 【Android – メモリ管理②】GCの仕組みについて

Category モバイル

ご意見・ご相談・料金のお見積もりなど、
お気軽にお問い合わせください。

お問い合わせはこちら