「java」タグアーカイブ

JavaのGC(Garbage Collection)とは?仕組みと注意点

GCとは何か

Java で開発をしていると、よく耳にする「GC(Garbage Collection)」。
これは 不要になったオブジェクトを自動で回収してメモリを解放する仕組み のことです。C言語のように手動で free() を呼ぶ必要はなく、Java VM が裏側でメモリ管理を行います。

 

ざっくり構造・最近のGC

  • 世代別回収:Eden/Survivor(若世代)→ Old(老世代)

  • Minor GC:Edenが埋まったら短命オブジェクト中心に回収

  • Major/Full GC:Oldが逼迫、断片化、クラス/メタ領域逼迫などで広域回収

  • 既定GC:G1GC(Java 9+)。低停止要求は ZGC / Shenandoah も選択肢

主なトリガ

  • Eden満杯(Minor) / Old高水準(Major)

  • 巨大配列(Humongous)割当て(G1)

  • System.gc() 明示呼び出し

  • メタスペース/オフヒープ圧迫(DirectByteBuffer/JNI など)


“悪い例 → 良い例”で学ぶメモリ/GC対策

1) 無制限キャッシュ(静的Map地獄)

悪い例

良い例(上限+期限+統計)

ポイント:上限なしは必ずOldを膨らませる。キャッシュは 容量・期限・エビクションを設計。


2) リスナ/コールバック未解除

悪い例

良い例(ライフサイクルで必ず解除 / AutoCloseable化)

補足WeakReference リスナはイベント強度低下意図せぬ解放のリスク。基本は明示解除


3) ThreadLocal の放置(プールスレッドに張り付く)

悪い例

良い例(finallyで確実に除去)

ポイントスレッドプール=長寿命remove() を忘れると実質グローバル保持


4) System.gc() 乱用

悪い例

良い例


5) ラムダ/内部クラスが外側(巨大オブジェクト)をキャプチャ

悪い例

良い例(必要最小限のデータだけ渡す・static化)

ポイントキャプチャ=保持。意図せず大物を延命していないか疑う。


6) ループ内の大量一時オブジェクト

悪い例

良い例(StringBuilder再利用・ボクシング回避)


7) finalize/Cleaner頼み(遅延・不確実)

悪い例

良い例(確実な即時解放) 


8) クラスローダ・アプリ再デプロイ時のリーク

悪い例

良い例(クラスローダ境界を越える参照を断つ)

 

9) 巨大配列・Humongous割当ての長期保持(G1)

悪い例

良い例(分割・ストリーミング・寿命短縮)

ポイント:巨大ブロックは断片化回収コスト増の温床。


10) 無制限のキュー/バッファ

悪い例

良い例(有界+バックプレッシャ)


GCログ・計測の始め方(JDK 9+)

  • まずはコードの割当て削減 → その後にヒープ/GC調整

  • 監視:jcmd <pid> GC.heap_info / jstat -gc <pid> 1000

  • ボトルネック特定:JFR(Java Flight Recorder) で割当てホットスポットを把握

  • 必要なら ZGC/Shenandoah も評価(レイテンシ目標に応じて)


実務チェックリスト(配布推奨)

  1. System.gc() を禁止/抑制

  2. キャッシュ・キューは有界+期限

  3. ThreadLocal は finally で remove

  4. リスナ/コールバックは確実に解除(AutoCloseable化が効く)

  5. ループ内の一時オブジェクトを減らす(Builder再利用/ボクシング回避)

  6. 巨大配列は分割・短命化

  7. クラスローダ境界を跨ぐ静的参照禁止、Executor停止・ドライバ解除

  8. try-with-resourcesでオフヒープ即時解放

  9. GCログ/JFRで事実ベースに調整

  10. 目標停止時間(例:MaxGCPauseMillis)を定めて検証


まとめ

GCは“自動”でも“万能”ではありません。
「GCが働きやすいコード」(不要参照を残さない・波及して大物を掴ませない・ピークメモリを避ける)を心がけ、ログ/計測で改善ループを回すのが最短距離です。

Javaのバージョンアップ手順

しばらくJavaのバージョンアップを実施してなかったのでバージョンアップ時の手順をメモしておきます。
今回はJava 1.6.0_45 ⇒1.8.0_331へバージョンアップしてみます。
※2022年5月時点でJavaの最新バージョンは18ですが開発で使用してるのは8なので今回最新版にはしてません。

jdkのダウンロード

  • Oracleの「Javaアーカイブ」ページからダウンロードする事が可能です。
    ダウンロードする場合Oracleアカウントが必要となります。
    ⇒Oracle Java Archiveページ

jdkのインストール手順

  1. インストールする前にまずは現在適用されているJavaのバージョンを確認します。コマンドプロンプトの画面で「javac -version」と入力すれば現在適用されているJavaのバージョンを確認できます。
  2. OracleのアーカイブページでJavaのバージョンを選択します。今回は「Java SE 8(8u211 and later)」を選択します。
  3. 次にjdkのインストーラーを選択します。今回は64ビット版の「jdk-8u331-windows-x64.exe」を選択します。
  4. ダウンロードした「jdk-8u331-windows-x64.exe」を実行してセットアップ画面の「次」ボタンを選択します。
  5. インストール先を変更したい場合は変更ボタンから指定してから「次へ」ボタンを選択します。
  6. インストールが終了するの以下の画面が表示されるので「閉じる」ボタンを選択します。
  7. コントロールパネル ⇒ システム ⇒ システムの詳細設定から環境変更を設定します。
  8. システム環境変数の「JAVA_HOME」を選択しjdkをインストールしたフォルダを指定します。
  9. 次にシステム環境変数の「Path」を選択肢jdkのフォルダが指定されている箇所を変更します。
  10. 環境変数の設定が完了したら再度コマンドプロンプト画面でJavaのバージョンを確認して値が変更されていればバージョンアップ作業完了です。

 

 

インスタンス変数とローカル変数の違い

Webシステムは基本的に複数人で同時利用されるのが前提のため、マルチスレッドアクセスを考慮した設計・実装を行う必要があります。業務でJavaの各変数とスレッドセーフについて考える機会があったので、「インスタンス変数とローカル変数の違いとスレッドセーフとの関係」について今回は整理してみようと思います。

Javaでの変数毎のメモリ管理イメージ


    上記は変数毎のメモリ管理イメージとなります。Javaではクラス変数やインスタンス変数はヒープ領域と呼ばれる共有メモリ領域へ保存されます。複数のスレッドで共有され別のスレッドに書き換えられる可能性があり,スレッドセーフではありません。対してローカル変数はJavaスタックと呼ばれるスレッド固有メモリ領域へ保存されます。スレッド固有領域なので別のスレッドに書き換えられる可能性はないのでスレッドセーフとなります。

インスタンス変数とは

  • サンプルコード
  • メソッドの外に記述します。staticは付けません(staticが付くとクラス変数になります)。
  • ヒープ領域と呼ばれる共有メモリ領域へ保存される為、宣言しただけではスレッドセーフにはなりません。
  • スレッドセーフを保つ為には初期化する必要があります。
    • コンスラクタで初期化
    • インスタンス宣言時に初期化
    • 最初のget時に初期化
  • 他のスレッドからの更新を防ぐ為、修飾子はprivateにする必要があります。
  • 当該クラス内の任意のメソッドから参照可能となります。

ローカル変数とは

  • サンプルコード
  • メソッド内に記述します。
  • そのメソッドが実行中の間だけ有効となります。
  • Javaスタックと呼ばれるスレッド固有メモリ領域へ保存される為、スレッドセーフとなります。