リアルタイム・コンピューティングと聞くと、高速なシステムでなければならないと思ったり、機械装置の制御に使われることがほとんどだと思ってしまいがちですが、これは誤りです。ほとんどの場合、要求される応答時間が速いのは確かですが、リアルタイム・システムを定義するものは処理速度ではありません。リアルタイム環境の真の中核をなすものは、システムの振る舞いが完全に決定的になるように、システムがあらかじめ設定された時間内にタスクを行うことを保証することです。
エンタープライズのレベルのアプリケーションにとって、リアルタイム・システムに配備することのメリット(そしてことによるとデメリット)は何でしょう?多くの場合、大きなメリットはありません。システムの非機能的要件(負荷容量、平均応答時間、最大負荷時の応答時間など)が満たされた時点で、アプリケーションの配置が可能になり、顧客は満足します。要求したくなるような何かが起きてもよいはずですが、たとえば、HRアプリケーションでいつもよりやや時間がかかっても、ユーザはただいつもより長く待たなければならないだけです。大きな影響はありません。しかし多くの金融関係者は、エンタープライズレベルのアプリケーションが要求された時間内に処理をし損ねることを、すぐに損害と(ことによると大きな損害と)同じであるとみなすでしょう。金融市場は、まさにその定義により、極めて変化しやすく、コンピュータ取引システムでは、価格が1秒間に何回もの割合で変更されることがあります。もしシステムのある部分が現在の価格にもとづいて取引をしようとしていて、そして実際の取引が何かしらの理由によって遅れたとしたら、わずかな変動でさえも重大な額の損失につながるのです。もしこうした遅延が頻繁に起きるとすれば、システムはすぐにメリットを失い、むしろ負債となるでしょう。
Java 言語やJavaEEのプラットフォームは、エンタープライズアプリケーションの開発で非常によく用いられることを証明してきました。Ease of Development(開発の容易性)や性能、そして信頼性といったものすべてが、開発者にとってJavaを非常に魅力的なものにしています。しかし、 Javaのプラットフォームはリアルタイム・アプリケーションをサポートしていません。そして、JavaアプリケーションをリアルタイムOSで実行しても、アプリケーションの動作は決定的にはならないでしょう。Javaアプリケーションの決定的な振る舞いの最も大きな障害は、Java仮想マシン(JVM)が処理するメモリ管理の方法です。CやC++等の早期の言語と異なり、Javaはガーベジコレクタを使用してアプリケーションで利用されなくなったメモリを再利用しています。この方法がとられているのは、開発者が利用されなくなったメモリを明示的に開放し忘れる、メモリリークを排除するためです。マシンのメモリは有限であり、もしこのミスがループのどこかで起これば、いずれはシステムがメモリを使い果たすでしょう。(Javaではメモリリークが完全に排除されていると言っているわけではありません。もしオブジェクトへの参照を持ち続ければ、ガーベジコレクタはメモリを再利用することができないのです。)
ガーベジコレクタはバックグラウンド・スレッドを使用してオブジェクトが記憶されているヒープスペースを監視しています。ガーベジコレクタは参照されなくなったオブジェクトを特定し、使用されていないメモリを再利用します。この作業の多くは、あるアドレスから別のアドレスへのヒープ内でのオブジェクトのコピーを伴います。起こりうるデータの破損を防ぐため、この作業中は(ミューテータ・スレッドとみなされた)すべてのアプリケーション・スレッドを停止しなければなりません。最新のJVMでは、適切に調整されたデスクトップアプリケーションについては、こうした停止は目につくものではないでしょう。たいていのエンタープライズアプリケーションについても、(低停止時間コレクタとしても知られている)CMS(Concurrent Mark Sweep)コレクタを利用することで、大部分のアプリケーションにとって容認できるレベルまで停止が減るでしょう。しかし、前述したような、あるレベルのエンタープライズアプリケーションにとっては、振る舞いは非決定的であり、したがってJavaは適していません。
この問題を解決するために、RTSJ(Real-Time Specification for Java)を設計・実装するためのJSR(Java Specification Request)が作られました。これは実際、本当に最初のJSRで、Java Community Processが1988年に設立されたときに作成されました。仕様の作成において、専門家のグループは指標となる方針を持っていました。これらの方針の3つは、エンタープライズアプリケーションへの適用を考慮する際に注目に値します。
- 下位互換性を保つこと。Java準拠のコンパイラが生成したclassファイルはどれでも、RTSJの仮想マシン上で実行できるようにするためです。
- 構文上の拡張はしないこと。Java言語に新しいキーワードを追加したり構文上の変更を行ったりしません。これは上記1.の結果でもありますが、Javaの開発者たちが簡単にRTSJに移行したり、NetBeansのような既存の開発ツールを利用できるようにするためには必要不可欠なことです。
- 予測可能な実行。これは設計上の決定をする際には最優先事項となります。この影響は、標準的な計算の性能が時折り下がることがありうるということです。このことは、一般的なエンタープライズアプリケーションについては、RTSJシステムを入念に検討しなければならない理由の一つとなっています。
RTSJの仕様では、Javaの拡張されたセマンティクスの8つの分野について述べています。
- スケジューリング
- メモリ管理
- 同期化
- 非同期イベント処理
- 非同期の転送制御
- 非同期スレッドの終了
- 物理メモリアクセス
- 例外
RTSJ のスレッドは次の3つのタイプのいずれかになります。非リアルタイム、ソフト・リアルタイム、そしてハード・リアルタイムです。非リアルタイムスレッドは多少分かりきったものであり、ある処理を完了するまでの特定の期限はありません。JVMが都合のいいときにスケジュールすることができ、ガーベジコレクタの影響は無関係です。ソフト・リアルタイムスレッドには、処理を完了するまでの期限が定められています。しかし、一定の余裕があり、完了すべき時点の直後に処理が完了したとしても、すべてのものが問題なく継続するでしょう。ハード・リアルタイムスレッドについては、絶対に期限までに処理を完了しなければなりません。もしできなければ、回復不能なエラーが発生しています。RTSJアプリケーションでは、これらの3タイプのスレッドすべてを同時に実行することができます。どのタイプのスレッドを使用するかを決めるのはアプリケーションの設計者と、スレッドの処理に割り当てられた重要度のレベルです。以下の図ではこれらのタイプのスレッド間の関係を示しています。
図1. RTJSのスレッド
通常、アプリケーションが処理を行うためには、スレッドはデータを交換する必要があるでしょう。ハード・リアルタイムスレッドはリアルタイムでの処理結果の受け渡しを非リアルタイムスレッドに頼ることができないので、ハード・リアルタイムスレッドが他のスレッドにブロックされないことを保証するために、待機なしのデータ転送キューが使用されます。
新しい非リアルタイムスレッドの生成には、既存のThreadクラスを変更せずに使用します。ソフト・リアルタイムスレッドには、 RealtimeThreadクラスを使用しますが、これはThreadクラスのサブクラスです。このクラスのコンストラクタにはオプションで優先順位とリリースのパラメータがあり、JVMのスレッドのスケジュールの仕方を明確にすることができます。ハード・リアルタイムスレッドには、 RealtimeThreadクラスのサブクラスであるNoHeapRealtimeThreadクラスを使用します。ハード・リアルタイムスレッドに使用するクラスの名前が、JVMでの実装方法のヒントになっていると思います。これについては、後ほどメモリ管理のところで話題にする予定です。
既存のアプリケーションをリアルタイムアプリケーションに改造する最も簡単な方法は、新たなスレッドを開始する箇所を、 RealtimeThreadクラスを使用するように単純に変更することです。この方法ではアプリケーションはソフト・リアルタイムアプリケーションに変更されるだけですが、簡単に変更できることを示しています。
Java の標準のThreadクラスには優先順位の概念が含まれているので、優先順位が低いものよりも、もっと重要なスレッドに優先権を与えることができます。 RTSJでは、この概念がさらに拡張されており、仕様では少なくとも28段階の別個の優先順位のレベルがサポートされなければならないとされています。優先順位は整数で表されているので、実装ではもっとたくさんのレベルを提供することができます。仕様ではまた、優先順位の高いスレッドは常に優先順位の低いものよりも優先して実行され、優先順位のより高いスレッドが実行可能になった場合は、現在実行中のスレッドより先に実行されるとしています。この情報を表すために使用されるクラスを以下の図2に示します。
図2. スレッド実行の優先順位
PriorityParametersクラスはスレッドの優先順位を示す整数値をカプセル化しています。同じ優先順位のスレッドが複数ある場合は、重要度をスレッドに関連付けてどのスレッドに優先権を与えるべきかを示すことができます。
リアルタイムシステムのキーコンセプトの一つは、システムがアプリケーションの要件を満たすか否かをあらかじめ測定できなければならないということです。そのためには、レートモノトニック解析法(Rate Monotonic Analysis:RMA)を通して、システムはタスクの詳細を知らなければなりません。これらはリリース・パラメータによってRTSJに提供されます。クラス階層を図3に示します。
図3. リリース・パラメータ
ReleaseParametersクラスには時間の観点から見たスレッドのコスト(これはプラットフォーム固有のものですので、アプリケーションが異なるプラットフォームに移動されると変化するでしょう。)や、このタスクを完了しなければいけない期限、コストを超過した場合や期限に間に合わなかった場合のハンドラが含まれています。処理されるタスクのタイプは周期的なものか非周期的なもののいずれかになります。周期的なものは設定されたペースで繰り返し発生し、非周期的なものの場合はいつでも発生する可能性があります。ハード・リアルタイムシステムに非周期的なタスクが含まれている場合は正確さを分析することは不可能なため、散発的なタスクの概念が含まれています。これにより、非周期的なタスクは周期的なタスクであるかのように処理されます。最低限の頻度を設定し、そうすることによってシステムの分析を可能にするのです。非周期的なタスクを散発的にすることは、アプリケーションの実行方法に影響を与えません。ただシステムがリアルタイムの要件を満たしているかどうかを測定できるようにするだけです。
RTSJは優先順位の逆転回避とよばれるものも実装しなければなりません。図4ではこの問題を説明しています。
図4. 優先順位の逆転
優先順位の低いスレッド(優先順位P3)がオブジェクトのロックを獲得します。そして優先順位がより高いスレッド(優先順位 P1)が先に実行されますが、これは仕様で規定されたとおりです。P1のスレッドは次に、同じオブジェクトを必要としますが、P3のスレッドがリリースするまでロックを獲得することができません。優先順位が低い他のタスク(P2)はP3のスレッドがロックをリリースするのを妨げます。最終的な影響としては、P2の優先順位のスレッドがP1の優先度で実行されてしまい、これは仕様が要求している動作ではありません。これを回避するためには2つの方法があります。1つ目は優先順位の継承です。システムはP3のスレッドがP1が必要とするロックを保持していることを検出すると、ロックがリリースされるまでの間、その優先順位をP1に上げます。これは開発者がコードを書き換えることなく機能します。2つ目の方法は優先度上限プロトコルです。この場合、開発者はロックを保持しているスレッドの優先順位を上げる必要があることを理解し、コード中で明確に実行しなければなりません。この手法はRTSJではオプションです。
メモリ管理には、RTSJはメモリ領域の概念を使用します。そのクラスの構造を図5に示します。.
図5. リアルタイムのメモリ領域
ガーベジコレクションは非決定的な停止の原因となるため、すべてのハード・リアルタイムスレッドはガーベジコレクションの対象とならないメモリを使用しなければなりません。これを行うためには2つの方法があります。スコープ・メモリ(Scoped Memory)とイモータル・メモリ(Immortal Memory)です。
スコープ・メモリはアプリケーションの開発者が決めた生存期間を持つメモリです。開発者は特定のサイズのスコープ・メモリの領域を作成し、オブジェクトがインスタンス化されると、この領域からメモリ空間が割り当てられます。スコープ・メモリにはリニアなものと変化するものの2種類があり、オブジェクトのインスタンス化に必要となる時間によります。LTMemoryクラスが表すメモリ領域では、オブジェクトをインスタンス化する時間が、一定の割り当て時間と可変の初期化時間との組み合わせとなっています。初期化にかかる時間はオブジェクトのサイズに直接比例するので、所要時間は一定というよりはむしろリニアです。VTMemoryクラスが表すメモリ領域では、割り当ての仕組みとしてどんなアルゴリズムでも使用することが可能なので、所要時間は様々です。スコープ・メモリ内の全てのオブジェクトが参照されなくなると、メモリは再利用するために解放されます。スコープ・メモリが使用されるシナリオのひとつとしては、スコープ・メモリ領域を作成し、NoHeapRealTimeThreadが利用するメモリとして提供するというものがあります。スレッドの実行が終了すると、全てのオブジェクトがもう参照されないことがわかり、この領域は解放されます。
イモータル・メモリは、その名前が示唆する通り、決して回収されることはありません。イモータル・メモリは全てのスレッドによって共有されるメモリです。JVMが終了するまで存在し続けることがわかっているオブジェクト以外には利用してはいけません。
RTSJの仕様では、標準のJavaではできないことも開発者に許しています。それは、物理メモリへの直接アクセスです。これは大部分の組み込みリアルタイムアプリケーションには適切ですが、エンタープライズ・アプリケーションには適切ではないため、この記事ではこれ以上扱いません。
おわかりのように、非リアルタイム、ソフト・リアルタイムそしてハード・リアルタイムを組み合わせてエンタープライズ・アプリケーションを実行できるように、RTSJでは様々な能力を提供しています。時間が重要である金融系のアプリケーションの場合、クリティカルな部分はハード・リアルタイムスレッドとして実行されるようにコードを書き、ガーベジ・コレクタの非決定的な振る舞いを避けます。RTSJ 2.0のリリースでは、現在参照実装の利用が可能です。自由に利用でき、リアルタイム・スケジューラを備えているオープンソースのSolaris10オペレーティング環境上で稼動します。(JVMでリアルタイムの機能を提供するためには、その下にあるOSがリアルタイムの概念をサポートしていなければなりません。)
エンタープライズ・アプリケーションの次の段階がリアルタイムに対応しているアプリケーションサーバの利用であることは明白です。SunのエンジニアはオープンソースのアプリケーションサーバであるGlassFishを(たったの5時間で)RTSJに移植しました。IBMにもリアルタイム対応の WebSphere製品があるように、すでに進んでいるのです。エンタープライズ・アプリケーションへのリアルタイムJavaの利用を評価する際には覚えておいて欲しいのですが、この状況では、制限の無いものは何もありません。おそらく応答時間は保証されるでしょうが、システムの全体的なスループットを犠牲にすることになるでしょう。しかし、もしあなたのJavaアプリケーションが、ガーベジコレクタの処理に関わらず、本当にある時間内に応答しなければならないのであれば、RTSJは蓋然性(見込み)というよりはむしろ可能性なのです。
リンク
http://rtsj.dev.java.nethttp://www.rtsj.org/
http://java.sun.com/javase/technologies/realtime/
http://www-306.ibm.com/software/webservers/realtime/
著者について
Simon Ritter氏は、グリッドコンピューティング、RFID、無線センサーネットワーク、ロボット工学やウェアラブルコンピューティング等を含む未来技術を見ている専門家です。Simon氏は1984年からITビジネスに関わっていて、イギリスのブルネル大学で物理学の理学士号を取得しています。当初は AT&T UNIX System Labsで、それからNovellでUnixの開発分野で働いていましたが、1996年にSunに入り、Javaテクノロジの仕事を始めました。彼は Javaの開発とコンサルタント業の両方を行っています。