Java | Java 詳細・モダン文法:JVM・パフォーマンス – 不要なオブジェクト生成

Java Java
スポンサーリンク

「不要なオブジェクト生成」がなぜパフォーマンス問題になるのか

Java では new しても GC が片付けてくれるので、「とりあえず new しとけばいいや」となりがちです。
でも、オブジェクトを作るたびにヒープが汚れ、GC の仕事が増えるのは事実です。

短命オブジェクトが多いのは JVM が得意とするパターンですが、
「本当はいらないのに毎回 new している」コードが積み重なると、
GC の頻度が上がり、Stop The World の時間が増え、結果としてレスポンスがじわじわ悪くなります。


典型例1:毎回 new している String / StringBuilder

文字列連結での無駄な生成

例えば、ループの中でこう書いてしまうパターンです。

String result = "";
for (int i = 0; i < 1000; i++) {
    result = result + i;
}
Java

この書き方だと、
result + i のたびに 新しい String オブジェクト が生成されます。
1000 回ループすれば、ほぼ 1000 個近い String が一瞬だけ生まれては捨てられます。

JVM は短命オブジェクトの回収は得意ですが、
それでも「いらない String を大量に作っている」ことには変わりません。

StringBuilder を再利用する形にする

同じ処理を、StringBuilder を使って書き直すとこうなります。

StringBuilder sb = new StringBuilder();
for (int i = 0; i < 1000; i++) {
    sb.append(i);
}
String result = sb.toString();
Java

ここでは、
ループ中に生成されるのは StringBuilder の内部バッファの拡張くらいで、
毎回新しい String を作っては捨てる、ということはしていません。

ポイントは、「同じ目的のために何度も新しいオブジェクトを作らない」という意識です。


典型例2:一時オブジェクトをループの中で毎回 new する

悪い例:ループ内での無駄な一時オブジェクト

for (int i = 0; i < 1_000_000; i++) {
    Point p = new Point(i, i + 1);
    process(p);
}
Java

もし Point が本当に「一時的な入れ物」で、
process の中で値を読むだけなら、
毎回 new Point する必要はないかもしれません。

改善例:再利用できるなら、1 個を使い回す

Point p = new Point(0, 0);
for (int i = 0; i < 1_000_000; i++) {
    p.x = i;
    p.y = i + 1;
    process(p);
}
Java

もちろん、process の中で p をどこかに保存したりしない前提ですが、
「その場限りの値の入れ物」であれば、1 個を使い回すだけで 100 万個のオブジェクト生成を減らせます

ここで大事なのは、
「本当に毎回別のオブジェクトである必要があるのか?」
と一度立ち止まって考えることです。


典型例3:オートボクシングによる見えないオブジェクト生成

int と Integer の行き来で勝手に new される

オートボクシングは便利ですが、
その裏で「見えない new Integer(...)」が走ることがあります。

long sum = 0;
for (int i = 0; i < 1_000_000; i++) {
    List<Integer> list = new ArrayList<>();
    list.add(i); // ここでオートボクシング(int → Integer)
    sum += list.get(0);
}
Java

このコードでは、
ループのたびに ArrayListInteger が生成されています。

特に Integer は、
intInteger に変換するたびにオブジェクトが増えます(キャッシュ範囲外ならなおさら)。

原則:プリミティブで済むならプリミティブで

例えば、単に合計を取りたいだけなら、
List<Integer> 自体が不要かもしれません。

long sum = 0;
for (int i = 0; i < 1_000_000; i++) {
    sum += i; // オブジェクト生成なし
}
Java

あるいは、どうしてもコレクションを使うなら、
「本当にラッパークラスが必要か?」を意識して設計することが大事です。


典型例4:使い捨てフォーマッタ・パーサの毎回生成

悪い例:毎回 new するフォーマッタ

for (int i = 0; i < 1_000; i++) {
    DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
    String s = LocalDate.now().format(formatter);
}
Java

DateTimeFormatter は不変(immutable)でスレッドセーフです。
毎回同じパターンで使うなら、1 個を使い回せます

改善例:static フィールドや再利用で抑える

private static final DateTimeFormatter FORMATTER =
        DateTimeFormatter.ofPattern("yyyy-MM-dd");

for (int i = 0; i < 1_000; i++) {
    String s = LocalDate.now().format(FORMATTER);
}
Java

こうするだけで、
1000 回のループで 1000 個のフォーマッタ生成を 1 個に減らせます。

「重いオブジェクト」「不変なオブジェクト」は、
static final で共有するのが定石です。


「不要なオブジェクト」を見抜く視点

質問1:このオブジェクトは「状態」を持つ必要があるか?

毎回 new するクラスが、
実は「ただの計算ロジック」しか持っていない場合、
インスタンス化自体が不要なことがあります。

Calculator c = new Calculator();
int result = c.add(a, b);
Java

Calculator が stateless(状態を持たない)なら、
メソッドを static にしてしまえば、
インスタンスを作る必要はありません。

int result = Calculator.add(a, b); // インスタンス不要
Java

「状態を持たないのに毎回 new しているクラス」は、
不要なオブジェクト生成の温床になりがちです。

質問2:このオブジェクトは「一時的な入れ物」か?

一時的な入れ物(DTO 的なもの)を、
ループの中で毎回 new している場合、
再利用できないかを考えてみてください。

ただし、再利用するときは「どこかに保持されないか」「スレッドセーフか」を必ず確認すること。
安易な再利用はバグの元なので、
「本当にローカルで完結しているか」をチェックするのが重要です。


不要なオブジェクト生成と GC の関係

短命オブジェクトは GC が得意、でもタダではない

JVM の世代別 GC は、
短命オブジェクトの回収を非常に効率よく行います。

だから、
「短命オブジェクトが多い=即アウト」ではありません。

ただし、
「本来いらないオブジェクト」まで大量に作っていると、その分だけ GC の仕事が増えるのは確かです。

Young GC の回数が増え、
場合によっては Old 領域への昇格も増え、
結果として Stop The World の時間が増える——
そういう形でじわじわ効いてきます。

「減らせる無駄」は減らす、が基本スタンス

大事なのは、
「オブジェクト生成をゼロにする」ことではなく、
「減らせる無駄はちゃんと減らす」という姿勢です。

読みやすさや安全性を犠牲にしてまで極端な最適化をする必要はありません。
でも、明らかに毎回 new しているもの、
明らかに再利用できるものについては、
一度立ち止まって設計を見直す価値があります。


まとめ:不要なオブジェクト生成を自分の言葉で説明するなら

あなたの言葉で整理すると、こうなります。

「不要なオブジェクト生成とは、本当は再利用できるのに毎回 new していたり、
そもそもオブジェクトにする必要がないのにラッパーや一時オブジェクトを作ってしまうこと。
それ自体は一回一回は小さなコストでも、積み重なると GC の負荷を増やし、Stop The World の時間を伸ばし、結果としてアプリ全体のパフォーマンスをじわじわ悪くする。

だから、
『このオブジェクトは本当に毎回別物である必要があるか?』
『状態を持たないのにインスタンスを作っていないか?』
『不変な重いオブジェクトを毎回 new していないか?』
と自分に問いかけながらコードを書くことが、
JVM と仲良くやるための基本になる。」

タイトルとURLをコピーしました