オブジェクトのライフサイクルとは
「オブジェクトのライフサイクル」は、あるオブジェクトが
生まれて(生成されて)、使われて、やがて不要になり、最後に片付けられるまでの一連の流れのことです。
Java では、オブジェクトのライフサイクルは大まかに次の段階に分けられます。
生成される
初期化される
使われる(状態が変わったり、メソッドが呼ばれたりする)
どこからも参照されなくなる
ガベージコレクションで回収される
この「流れ」を意識すると、メモリの使い方や設計が一気に理解しやすくなります。
生成と初期化(生まれる瞬間)
new で生成され、コンストラクタで初期化される
Java でオブジェクトが生まれるのは、基本的に new を呼んだ瞬間です。
同時にコンストラクタが走り、そのオブジェクトの「最初の状態」が決まります。
final class User {
private final String name;
User(String name) { // コンストラクタ(初期化)
this.name = name;
}
String name() {
return name;
}
}
User u = new User("Taro"); // ここで User オブジェクトが「生成+初期化」される
Javaメモリ的には、ヒープ領域に User 用の領域が確保され、
コンストラクタで name フィールドに “Taro” が入ります。
この時点で「User オブジェクトが1つ、生まれた」と言えます。
ファクトリメソッドを使う場合も本質は同じ
コンストラクタを直接呼ばず、別クラスのメソッド経由で生成することもあります。
final class UserFactory {
User createGuest() {
return new User("GUEST");
}
}
User guest = new UserFactory().createGuest();
Javanew がどこに書かれているかが違うだけで、
「new のところで生まれる」「コンストラクタで初期化される」という本質は変わりません。
利用と状態変更(生きて働いている期間)
メソッド呼び出しとフィールドの変化
生成されたオブジェクトは、メソッドを呼ばれて仕事をし、場合によっては内部状態を変えながら働きます。
final class Counter {
private int value = 0;
void increment() {
value++;
}
int value() {
return value;
}
}
Counter c = new Counter(); // ライフサイクル開始
c.increment(); // 内部状態が 1 に変化
c.increment(); // 2 に変化
int v = c.value(); // v は 2
Javaこの「生成されてから、まだどこかから参照されている間」が、そのオブジェクトの“活動期間”です。
どこから参照されているかが寿命を決める
オブジェクトがいつまで生きるかは「どこかから参照され続けているか」で決まります。
変数やフィールド、配列、コレクション(List、Map など)からそのオブジェクトに辿れる限り、
そのオブジェクトは“現役”です。
逆に言うと、「参照を切る」「コレクションから取り除く」といった操作が、
ライフサイクルの終わりに近づける行為になります。
参照が切れる(到達不能になる瞬間が重要)
変数の中身を別のものに変える
例えば次のコードを見てください。
User u = new User("Taro"); // ここで 1 つ User が生まれる
u = new User("Jiro"); // ここで別の User が生まれ、u は Jiro を指すように変わる
Java最初に “Taro” の User が生成されます。
そのあと u に新しい User(“Jiro”) を代入した瞬間、
“Taro” のほうは変数 u からの参照を失います。
もし他のどこからも “Taro” の User へ辿れる参照が残っていなければ、
そのオブジェクトは「到達不能(誰からも届かない)」状態になり、
ガベージコレクションの対象候補になります。
null を代入して参照を明示的に切る
同じように、null を代入することで参照を切ることもできます。
User u = new User("Taro");
// ここではまだ u が User を指しているので、User は現役
u = null;
// ここで u からの参照が切れる
// 他に参照がなければ、この User は到達不能になり、いずれ GC 対象になる
Java重要なのは、「参照が1つも残っていないかどうか」です。
どこかのフィールドや別変数がまだ参照を持っているなら、そのオブジェクトは生き続けます。
ガベージコレクション(片付け)のタイミングと注意点
自分で delete しない世界
C や C++ では delete や free で明示的にメモリを解放しますが、
Java ではそれを自分で行いません。
「もう誰からも参照されていない」オブジェクトを、
JVM のガベージコレクタが適切なタイミングで見つけて回収します。
ここで大事なのは、「いつ GC が走るかは基本的に自分では決められない」ということです。
System.gc() のようなものはありますが、あくまで「お願い」であって、必ずその場で実行されるわけではありません。
外部リソースは“自分で閉じる”責任がある(重要)
メモリの解放は GC 任せでよいのですが、
ファイル、ソケット、データベース接続などの“外部リソース”は話が別です。
これらは OS や外部システムとつながっているので、
オブジェクトが GC されるのを待っていると、
「ファイル開きっぱなし」「コネクション繋ぎっぱなし」のような状態になり得ます。
このため、外部リソースはライフサイクルの最後で明示的に close する必要があります。
try (var reader = java.nio.file.Files.newBufferedReader(java.nio.file.Path.of("data.txt"))) {
String line = reader.readLine();
// 読み込み処理
} // try-with-resources を抜けると、自動的に close が呼ばれる
Javaオブジェクトの寿命と、外部リソースの寿命は必ずしも一致しません。
「オブジェクトが生きていても、リソースはもう閉じる」
「オブジェクトが死ぬ前に、先にリソースだけ閉じる」
といった制御が必要になることがあります。
スコープとライフサイクルの関係(深掘り)
ローカル変数のスコープと寿命
メソッド内のローカル変数で参照しているオブジェクトは、
そのメソッドを抜けたタイミングで、変数自体がスコープから消えます。
void process() {
User u = new User("Taro"); // u は process メソッドの中だけで見える
// ここで u を使っていろいろする
} // この行を超えると u という変数は存在しない
// もし他に参照がなければ、この User は GC 対象候補になる
Javaただし、メソッドの中で作ったオブジェクトを
フィールドやコレクションに入れて外へ渡したりしていると、
メソッドを抜けてもそのオブジェクトは生き続けます。
final class UserHolder {
private User user;
void setUser(String name) {
this.user = new User(name); // メソッドを出ても、フィールドが参照を持ち続ける
}
}
Javaフィールド参照と長いライフサイクル
フィールドに持たれた参照は、そのオブジェクト自身が生きている限り残ります。
さらに、そのオブジェクトが static フィールドであれば、
アプリケーションの終了まで生き続けることもあります。
final class Config {
static final Config INSTANCE = new Config(); // ほぼアプリ全体と同じ寿命
private String baseUrl;
private Config() {}
void setBaseUrl(String url) {
this.baseUrl = url;
}
String baseUrl() {
return baseUrl;
}
}
Javastatic フィールドは「アプリ全体のライフサイクル」とほぼ同じ長さになることが多いため、
何でもかんでも static に突っ込むと、
本当はいらなくなっているオブジェクトを延々と生かし続けることになります。
これもメモリリークの原因になり得ます。
典型的なライフサイクルパターン(オブジェクトの“生き方”)
一時オブジェクト(短命)
メソッド内で計算用にだけ使うオブジェクトなどは、ライフサイクルが短いです。
メソッドのスコープを抜ければ、他に参照がなければ GC 対象です。
int sumLength(String a, String b) {
String merged = a + b; // merged はこのメソッド内だけで使う
return merged.length();
} // merged はここでスコープから消える
Java短命なオブジェクトは、多めに作っても GC が回収してくれるので、
そこまで神経質になる必要はありません。
リクエストスコープ(Web アプリなど)
Web アプリでは「リクエストごと」にオブジェクトを生成し、
レスポンスを返したらもう使わない、というライフサイクルがよくあります。
コントローラの中で new した DTO などは、
リクエスト処理終了とともに不要になり、GC の対象になります。
アプリケーションスコープ(長命)
設定、接続プール、キャッシュなどはアプリ全体で使われるため、
アプリケーションの起動から終了まで生き続けることがあります。
こうしたオブジェクトは、
「ちゃんと初期化されているか」「終了時に外部リソースを片付けているか」が特に重要になります。
設計視点で見たライフサイクル(重要なまとめ)
オブジェクトのライフサイクルを意識することは、設計にも直結します。
どのクラスがいつ生成されるべきか
誰がそのオブジェクトを持ち、いつまで持ち続けるべきか
いつ参照を切るべきか
外部リソースをいつ開き、いつ閉じるべきか
これを曖昧にすると、
「よく分からないけどとりあえず static に置いておく」
「どこかで new してそのまま放置」
「どこかで close し忘れている」
といった問題が起きます。
逆に言うと、
このオブジェクトはどのタイミングで生まれて、
どのくらいの期間使われて、
どこで役目を終えるのか
を自分の言葉で説明できるなら、ライフサイクル設計はかなり良い線まで来ています。
