この章では、JRockit JVMのスレッドとロックに関する基本情報を示します。
実行中のアプリケーションは、通常、独自のメモリー領域を持つ1つのプロセスで構成されます。一般に、コンピュータは同時に複数のプロセスを実行しています。たとえば、ワード・プロセッサ・アプリケーション・プロセスがメディア・プレーヤ・アプリケーション・プロセスと同時に実行される場合があります。プロセスは同時に実行されている多数のスレッドで構成されます。Javaアプリケーションを実行すると、新しいJVMプロセスが開始されます。
各Javaプロセスには少なくとも1つのアプリケーション・スレッドがあります。アプリケーション・スレッドに加えて、Oracle JRockit JVMではガベージ・コレクション、コード生成、およびその他の内部目的のために内部スレッドが使用されます。
スレッド・ダンプ(アプリケーションの全スレッドのスタックの出力)を使用すると、問題を診断して、アプリケーションやJVMのパフォーマンスを最適化することができます。スレッド・ダンプの生成の詳細は、Oracle JRockit JDKツールのスレッド・ダンプの使用に関する項を参照してください。
次の各項で、JRockit JVMのスレッドとロックについて説明します。
Javaアプリケーションは、Javaコードを実行する1つ以上のスレッドで構成されます。JVMプロセスは、JavaスレッドといくつかのJVM内部スレッドで構成されます。たとえば、1つ以上のガベージ・コレクション・スレッド、1つのコード・オプティマイザ・スレッド、1つ以上のファイナライザ・スレッドなどがあります。
オペレーティング・システムは、アプリケーション・スレッドと同様にJavaスレッドを扱います。スレッドのスケジューリングおよび優先順位はオペレーティング・システムによって処理されます。
Java内で、Javaスレッドはスレッド・オブジェクトによって表されます。各スレッドには、実行時データを格納するスタックもあります。スレッド・スタックには一定のサイズが定められています。スレッドは、スタック・サイズの許容量を超える項目を格納しようとすると、スタック・オーバーフロー・エラーをスローします。
Javaアプリケーション・スレッドのデフォルト・スタック・サイズ
デフォルト・スタック・サイズは、使用するオペレーティング・システムによって異なります。表3-1に、各オペレーティング・システムのデフォルト・スタック・サイズの一覧を表示します。
表3-1 デフォルト・スタック・サイズ
オペレーティング・システム | デフォルト・スタック・サイズ |
---|---|
Windows IA32 |
64KB |
Windows x64 |
128KB |
Linux IA32 |
128KB |
Linux x64 |
256KB |
Solaris/SPARC |
512KB |
スレッド・スタック・サイズは-Xss
コマンド・ライン・オプションで変更できます(java -Xss:512k MyApplication)。
JVM内部スレッドのデフォルト・スタック・サイズ
ガベージ・コレクション・スレッドやコード生成スレッドなどのJVM内部スレッドでは、特別な「システム」スタック・サイズ(すべてのプラットフォームで256KB)が使用されます。
注意: -Xss コマンドライン・オプションは、アプリケーション・スレッドとJVM内部スレッドの両方のスタック・サイズを設定します。 |
プロセス内の複数のスレッドが同じデータを共有して更新する場合は、エラーが発生しないようにそのアクティビティを同期化する必要があります。Javaでは、synchronized
キーワード、またはwait
キーワードとnotify
キーワードを使用してこれを行います。同期化はロックを使用して行われます。各ロックはオブジェクトに関連付けられています。スレッドがオブジェクトを処理するには、それに関連付けられているロックを制御(保持)する必要があります。ロックを保持できるのは一度に1つのスレッドのみです。スレッドが別のスレッドに保持されているロックを取得しようとする場合は、そのロックが解放されるまで待機する必要があります。このような状況が発生した場合、ロックの競合があります。
4つのタイプのロックがあります。
シン・ロック
別のスレッドに保持されているロックを取得しようとしているスレッドは、わずかな時間、スピンします。スピンしている間、このスレッドは、必要としているロックがまだ別のスレッドで保持されているかどうかを継続的にチェックします。これはマルチCPUシステムのデフォルトの動作です。このようなロックをシン・ロックといいます。
ファット・ロック
ロックを取得しようとしているスレッドのスピンが長すぎると、CPUリソースが必要以上に消費され、他のコードを実行できなくなります。CPUリソースは、シン・ロックをファット・ロックに引き上げることで解放できます。そして、必要なロックが取得可能になるまでスレッドはオペレーティング・システムによって管理され、CPUもその他のタスクのために解放されます。
再帰的ロック
同期化されたコードが直接的または間接的に自らをコールする場合、ロックは再帰的です。
遅延ロック
遅延ロックは、重要なセクションが終了しても解放されません。スレッドが遅延ロックを取得した場合、そのロックを取得しようとする他のスレッドは、そのロックが解放されているか、または解放可能であるか確認する必要があります。
JRockit JVMでは複雑な一連のヒューリスティックを使用して、あるタイプのロックを他のタイプに変更するタイミングが決定されます。
ロック・チェーン
複数のスレッドがロック・チェーンと呼ばれる状態になる場合があります。ロック・チェーンは次のように定義できます。
スレッドBが必要としているロックをスレッドAが保持している場合、スレッドAとBはロック・チェーンを形成します。Aがロックを取得しようとしていない場合、ロック・チェーンは開いていると見なされます。
A-Bがロック・チェーンであり、B-Cがロック・チェーンである場合、A-B-Cはより完全なロック・チェーンになります。
Cに保持されているロックを待機する他のスレッドがない場合、A-B-Cは完全で開かれたロック・チェーンになります。
ロック・チェーンのタイプ
JRockit JVMはスレッドを分析して完全なロック・チェーンを形成します。ロック・チェーンには開いたロック・チェーン、デッドロック・チェーン、ブロックされたロック・チェーンの3つの可能なタイプがあります。
オープン・チェーン
開いたチェーンは、直線的な依存関係を表します。たとえば、スレッドAがBを待機し、BがCを待機している場合などです。長くて開いたチェーンがある場合、アプリケーションはロックを待機して時間を無駄に消費している可能性があります。その場合は、アプリケーションでロックがどのように同期化に使用されているのかを再検討する必要があります。
デッドロック・チェーン
デッドロックした(循環)チェーンは、スレッドのチェーンから成り、チェーンの最初のスレッドがチェーンの最後のスレッドを待機しています。最も単純な例では、スレッドAはスレッドBを待機し、スレッドBはスレッドAを待機しています。スレッド・ダンプでは、JRockit JVMが任意のスレッドを選択して、チェーンの最初のスレッドとして表示します。
デッドロックを解決しないと、アプリケーションが無限に待機する可能性があります。
ブロックされたチェーン
ブロックされたチェーンは、先頭のスレッドが他のロック・チェーン(オープン・チェーンまたはデッドロック・チェーン)の一部でもあるロック・チェーンです。たとえば、スレッドAがスレッドBを待機し、スレッドBがスレッドAを待機していて、スレッドCがスレッドAを待機している場合、スレッドAとBはデッドロックしたロック・チェーンを形成し、スレッドCとスレッドAはブロックされたロック・チェーンを形成します。