Javaの動作原理を知ろう1 –ヒープ編–

Javaの動作原理を知ろう1 –ヒープ編–

Javaテクノロジが登場してから早20年以上経過しますが、その動作原理は大きくは変わっていません。 クラウド化に伴い、マシンリソースのスケールアップやスケールアウトが臨機応変に、場合によっては自動でできるようになってはいますが、アプリケーションレイヤやプラットフォームレイヤにおいては、実利用に際しての調整はやはり必要です。 お金で解決?いえいえ、むやみやたらとお金を使えばいいというわけではありません。Java の原理をしっかりと学んだ上で、ぜひSave your moneyな運用を実践しましょう。

オブジェクト指向言語固有の懸念事項

Java の原理をご紹介していく前に、オブジェクト指向 とは何でしょうか?
オブジェクト指向とは、現実世界をオブジェクト中心にモデル化する考え方 です。これによる恩恵は数多く存在し、こと顧客要求が随時変化する、または完全に定まらない状況下で、システム開発を強いられる現状においての適用度は、非常に高いといわれております。

とはいえ、実開発や運用においては 無数に存在するオブジェクトの管理 に頭を悩ませられるケースも多いようで、しばしば メモリリーク の原因となったり、深刻なパフォーマンス劣化 の原因となったりするケースがあります。

オブジェクトを生成すると、システム上(JVM内のヒープ)の リソースを消費 します。生成されたオブジェクトはいつか不要になり、適切なタイミングで破棄 する必要があります。

オブジェクトのライフサイクルとスコープ

プログラム実行時に生成される オブジェクトの種類 は多岐にわたり、コード内で生成されるオブジェクトに関わらず、アプリケーションサーバが生成 するもの 、利用しているフレームワークによって生成 されるもの 、JVM 自身が生成 するものがあり、それぞれによって特性が異なります。

これら全てのオブジェクトについては、JVM 内部の実行時データ領域 である ヒープ領域 に割り当てられます。ヒープ領域に割り当てられたオブジェクトはどのオブジェクトからも参照されなくなると、ガベージコレクション によって 回収 されます。その後、別のオブジェクトを割り当てることができるようにその領域は 解放 されます。

ガベージコレクションが動作するタイミングは、JVM 仕様では明確に決められておらず、HotSpot や、JRockit 等のJVM 実装に委ねられています。

ガベージコレクションの動作1 – 基本形 –

ガベージコレクションの動作 には様々なアルゴリズムがありますが、基本は Mark-Sweep-Compaction になります。
Mark-Sweep-Compaction

(1)Mark      参照可能なオブジェクトにフラグを付ける
(2)Sweep     オブジェクトの使用している領域を解放する
(3)Compaction  連続したヒープ領域にオブジェクトが配置されるようにして圧縮する

しかしながら、この動作は 動作コスト が大きく、一度発生すると アプリケーションの応答が停止 することが知られています(Stop-The-World)。
また、この停止時間は ヒープのサイズが大きければ大きいほど長くなり(数十秒~数分)、Javaシステムの性能用件上の大きな問題としてしばしば話題にあがります。ヒープサイズが小さく常にオーバーフローと隣り合わせの状況ではその頻発によりアプリケーションの動作がほとんど行われない状況に陥る可能性もあります。

オブジェクトの世代を考慮しないガベージコレクタ

 

ガベージコレクションの動作2- 世代別管理 –

さて、基本形のガベージコレクションの動作コストでは、応答性の確保などにおいて厳しい機能外要求があるアプリケーションにとっては、致命的になります。

そのため、過去20年余りのブラッシュアップにて、オブジェクトの生存期間にもとづいたガベージコレクションのアルゴリズム が整備されてきました。その中でも一つのキーポイントになるのが、ヒープ領域を二つに大きく大別 した 世代別管理 になります。

アプリケーションのアーキテクチャや特性にもよるのですが、70~80%のオブジェクトは短命 であり、残り 20~30%のオブジェクトは長命 であることが統計的に知られています。また、長命なオブジェクトほど自身にクライアント毎の情報を保持するケースや、アプリケーション全体で使用する情報をキャッシュすることもあり、サイズも比較的大きくなりがち です。

ヒープを一枚岩のようにとらえて、短命長命数多のオブジェクトを一色単にスキャンし回収しようとしても、長命のオブジェクトに対してはせっかくのガベージコレクションでの回収ができず、アプリケーションの応答性能に影響を与える ことになります。

そのことに目をつけ、ヒープを 短命用 (NewまたはYoung世代) 、長命用 (OldまたはTenured世代) に分割し、それぞれに動作コストの異なるガベージコレクタを適用することで 効率よくオブジェクトを回収するアルゴリズム が検討されました。

New世代のオブジェクト回収 には動作コストの小さい マイナーGC を使用し、Old世代のオブジェクトの回収 には動作コストの大きい メジャーGC を使用します。ヒープサイズ如何にもよりますが、両者の動作コストは数百倍~数千倍となります。

マイナーGCによる複数回のオブジェクト回収において、参照され続けたオブジェクトは ある程度の回数 (殿堂入り閾値) に達すると、長命オブジェクトとして Old世代へ移動 し、マイナーGCの脅威に晒されることはなくなります。いわば、老後です。

オブジェクトの世代を考慮したガベージコレクタ

 

老人を大切にしろ、若者は使い捨てろ

どのようなアプリケーションに対して、どのようなヒープ設定やガベージコレクションのアルゴリズムを適用すればよいか、ベストプラクティスはありません。

敢えて申し上げると、メジャーGCの発生を悪とするのであれば、マイナーGCが積極的に行われるような調整をし、極力メジャーGCが発生しないようにする、これに尽きます。見出しタイトルに深い意味はありませんが、いたずらにオブジェクトがOld世代に移動しないよう、マイナーGCにてオブジェクトを極力回収できるように努めることでしょう。

Young 世代のサイズ は、小さすぎると Young 世代にあふれが生じる ことになり、短命なオブジェクトが Old 世代に移動 しやすくなります。大きすぎると、いざ寿命が長くサイズの大きいオブジェクトが多かった場合には、メジャーGCが頻発しやすくなる 影響が心配されます。
Old 世代 の空き容量に余裕 を持たせつつ、Young 世代についても、その 平均的な生存期間で、発生するマイナーGCにより回収されるための十分な容量を確保 すること、これが定石です。

具体的なヒープ設定およびガベージコレクションの設定オプションはJVMベンダに依存するため、詳しくはJVM ベンダが提供するドキュメントをご参照ください。

JVM ベンダによるドキュメント
HotSopt VM
JRockit VM

まとめ

パフォーマンスチューニング上の注意事項はいろいろありますが、基本原理は次のような3つの段階を踏みます。

  1. 今割り当てられているリソースの範囲内で調整する
  2. それでもだめならスケールアップ(容量増加)
  3. それでもだめならスケールアウト(冗長化と負荷分散)する

いたずらにスケールアップやスケールアウトを行うと、コストの増加 や ただトラブルが発生することを先送りするだけになり、経済的のみならず人的リソースのコストの無駄遣い になりかねません。
適切な対処で、Save your Money な運用を実現できるような知識を身につけましょう!

次回は、各種 主要なガベージコレクションのアルゴリズム をご紹介できればと思います。

 

記事は、予告なく変更または削除される場合があります。
記載された情報は、執筆・公開された時点のものであり、予告なく変更されている場合があります。
また、社名、製品名、サービス名などは、各社の商標または登録商標の場合があります。