Java ランタイム環境

App Engine を使用すると、Google のスケーラブルなインフラストラクチャとサービスを使用する Java ウェブ アプリケーションをビルドできます。

はじめに

App Engine は、Java 7 JVM を使用して、安全な「サンドボックス」環境で Java ウェブ アプリケーションを実行します。App Engine は、アプリケーションのサーブレット クラスを呼び出して、この環境でリクエストの処理とレスポンスの準備を行います。

Cloud Tools for Eclipse を導入すると、App Engine プロジェクト用の Eclipse IDE に、新しいプロジェクト ウィザードとデバッグ設定が追加されます。App Engine プロジェクトを Eclipse 内からライブで本番環境にデプロイできます。

Cloud Tools for IntelliJ を使用すると、IntelliJ IDEA 内で App Engine アプリケーションを実行し、デバッグできます。IDE を終了しなくても App Engine プロジェクトをライブで本番環境にデプロイできます。

他の Java IDE 向けのサードパーティ製プラグインも使用できます。たとえば、NetBeans については、NetBeans plugin for Gaelyk Framework を参照してください。

App Engine は、ウェブ アプリケーションに Java サーブレット 2.5 標準を使用します。アプリケーションのサーブレット クラス、JavaServer Pages(JSP)、静的ファイル、データファイルとともに、デプロイ記述子(web.xml ファイル)などの設定ファイルを、標準 WAR ディレクトリ構造で用意します。App Engine は、デプロイ記述子に従ってサーブレットを呼び出すことでリクエストを処理します。

安全な「サンドボックス」環境は、サービスとセキュリティ上の理由から、作成したアプリケーションを隔離します。サンドボックス環境では、アプリケーションは、他のアプリケーションのパフォーマンスとスケーラビリティに影響しない処理しか実行できないようになります。たとえば、スレッドを生成したり、ローカル ファイル システムにデータを書き込んだり、任意でネットワークに接続したりすることはできません。また、JNI などのネイティブ コードを使用することもできません。JVM は、サンドボックスの制限内で動作するすべての Java バイトコードを実行できます。

App Engine プラットフォームには、コードから呼び出せる多数のサービスが用意されています。また、アプリケーションでは、指定の間隔で実行されるタスクのスケジュールを設定することもできます。

Java Guestbook アプリのチュートリアルでは、Java 技術と Google App Engine を使用したウェブ アプリケーション開発についてわかりやすく解説しています。

Java ランタイムの選択

App Engine API for Java は、App Engine SDK 付属の appengine-api-*.jar で表されます(* は API と App Engine SDK のバージョンを表します)。アプリケーションで使用する API のバージョンを選択するには、この JAR をアプリケーションの WEB-INF/lib/ ディレクトリに配置します。リリースされた新しいバージョンの Java ランタイム環境に、既存のアプリケーションとの互換性がない変更が導入された場合、この環境には新しいバージョン番号が付与されます。新しいバージョンの JAR(新しい SDK のもの)に置き替えてアプリケーションを再アップロードするまで、アプリケーションは以前のバージョンを使用し続けます。

サンドボックス

App Engine が複数のアプリケーションへのリクエストを複数のウェブサーバーに分散できるようにして、アプリケーションが相互に干渉しないようにするために、アプリケーションは制限された「サンドボックス」環境で実行されます。この環境では、アプリケーションはコードの実行、Cloud Datastore データストア内でのデータの保存とクエリ、App Engine メール、URL フェッチ、ユーザーのサービスの使用、ユーザーのウェブ リクエストの確認とレスポンスの準備が可能です。

App Engine アプリケーションでは、次のことはできません。

  • ファイルシステムへの書き込み。アプリケーションは、データを永続的に保存するために、Cloud Datastore を使用する必要があります。ファイルシステムからの読み取りは可能です。また、アプリケーションでアップロードされたアプリケーション ファイルはすべて利用可能です。

  • 遅いレスポンス。アプリケーションへのウェブ リクエストは、数秒以内に処理される必要があります。レスポンスまでに非常時に長い時間がかかるプロセスは、ウェブサーバーの過負荷を防ぐために終了します。

  • その他の種類のシステムコールの実行。

ファイルシステム

Java アプリケーションでは、ファイルシステムへの書き込みに使用される java.io.FileWriter などのクラスは使用できません。アプリケーションは、java.io.FileReader などのクラスを使用して、独自のファイルを読み取ることはできます。また、Class.getResource()ServletContext.getResource() などを使用して、「リソース」である独自のファイルにアクセスすることもできます。

アプリケーションは、「リソース ファイル」とみなされるファイルにのみ、ファイルシステムを通じてアクセスできます。デフォルトでは、WAR に含まれるすべてのファイルは「リソース ファイル」です。「リソース ファイル」のグループからファイルを除外するには、appengine-web.xml ファイルを使用します。

java.lang.System

java.lang.System クラスの機能のうち、App Engine に適用されない機能は無効になっています。

System メソッドのうち、exit()gc()runFinalization()runFinalizersOnExit() は、App Engine では機能しません。

System メソッドのうち、inheritedChannel()console()null を返します。

アプリケーションでネイティブ JNI コードを提供したり、直接呼び出したりすることはできません。System のメソッド load()loadLibrary()setSecurityManager()java.lang.SecurityException を発生させます。

リフレクション

アプリケーションは、自身のクラスに対して制限のない完全なリフレクション アクセスが許可されています。

プライベート メンバーへのクエリ、java.lang.reflect.AccessibleObject.setAccessible() メソッドの呼び出し、プライベート メンバーの読み取りや設定を行うことができます。

また、アプリケーションは、java.lang.Stringjavax.servlet.http.HttpServletRequest などの JRE クラスと API クラスでリフレクションを使用することもできます。ただし、アクセスできるのはこのようなクラスの public メンバーのみで、protected メンバーや private メンバーにはアクセスできません。

アプリケーションは、自身に属していないクラスでリフレクションを使用することはできません。setAccessible() メソッドを使用してこのような制限を回避することもできません。

カスタムクラスの読み込み

App Engine では、カスタムクラスの読み込みが完全にサポートされています。アプリケーションは、アプリケーション固有のクラス読み込みロジックを実装する独自の ClassLoader サブクラスを定義できます。ただし、App Engine ではすべての ClassLoader がオーバーライドされ、アプリケーションで読み込まれたすべてのクラスに同じ権限が割り当てられることに注意してください。カスタムクラスの読み込みを実行する場合、信頼できないサードパーティのコードを読み込むときには注意が必要です。

クラスローダーでの JAR の順序付け

クラス名の衝突を解決するために、JAR ファイルをスキャンしてクラスを検索する順序を定義し直す必要が生じることがあります。このような場合は、appengine-web.xml ファイルに、<priority-specifier> 要素を含む <class-loader-config> 要素を追加して、特定の JAR ファイルが優先的に読み込まれるようにすることができます。次に例を示します。

<class-loader-config>
	<priority-specifier filename="mailapi.jar"/>
</class-loader-config>

このようにすると、war/WEB-INF/classes/ ディレクトリ内の JAR ファイルではなく「mailapi.jar」が、クラスを検索する最初の JAR ファイルになります。

複数の JAR ファイルが優先されている場合は、元の読み込み順序(互いに対する順序)が使用されます。つまり、<priority-specifier> 要素自体の順序は関係ありません。

JRE ホワイトリスト

Java 標準ライブラリ(Java ランタイム環境、JRE)内のクラスへのアクセスは、App Engine JRE ホワイトリスト内のクラスに限定されます。

署名付き JAR ファイルには非対応

App Engine のプリコンパイルは、署名付き JAR ファイルとの互換性がありません。アプリケーションがプリコンパイルされている場合(デフォルト)、署名付き JAR ファイルは読み込めません。アプリケーションが署名付き JAR を読み込もうとすると、ランタイムの App Engine は次のような例外を生成します。

java.lang.SecurityException: SHA1 digest error for com/example/SomeClass.class
    at com.google.appengine.runtime.Request.process-d36f818a24b8cf1d(Request.java)
    at sun.security.util.ManifestEntryVerifier.verify(ManifestEntryVerifier.java:210)
    at java.util.jar.JarVerifier.processEntry(JarVerifier.java:218)
    at java.util.jar.JarVerifier.update(JarVerifier.java:205)
    at java.util.jar.JarVerifier$VerifierStream.read(JarVerifier.java:428)
    at sun.misc.Resource.getBytes(Resource.java:124)
    at java.net.URLClassLoader.defineClass(URLClassLoader.java:273)
    at sun.reflect.GeneratedMethodAccessor5.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:616)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:266)

これを回避するには、2 とおりの方法があります。

スレッド

Java アプリケーションでは、新しいスレッドを作成できますが、その作成方法には、いくつかの制限があります。作成したスレッドは、作成元のリクエストよりも「長く存続」することはできません。

アプリケーションでは次のことが可能です。

ただし、スレッドの作成には ThreadManager のメソッドのいずれかを使用する必要があります。new Thread() を直接呼び出すことや、デフォルトのスレッド ファクトリを使用することはできません。

アプリケーションは、thread.interrupt() など、現在のスレッドに対するオペレーションを実行できます。

各リクエストの同時リクエスト スレッド数は 50 までに制限されています。1 つのリクエストで 50 件を超えるスレッドを作成しようとすると、Java ランタイムによって java.lang.IllegalStateException がスローされます。

スレッドを使用するときには、ExecutorRunnable などの高水準の同時実行オブジェクトを使用してください。これらのオブジェクトは、割り込みスケジュールと記帳のような、細かいことであっても重要な同時実行の詳細の多くを処理します。

バックグラウンド スレッド

手動または基本のスケーリング インスタンスで実行するコードは、バックグラウンド スレッドを開始できます。このスレッドは、スレッドを生成したリクエストよりも長く存続できます。これにより、インスタンスは任意の周期またはスケジュールによってタスクを実行できるようなるほか、リクエストがユーザーに返された後でもバックグラウンドで作業を続行できるようになります。

Java の同時実行は、デバッグが困難な問題につながることがあります。スレッドの使用を避けるには、タスクキュータスクのスケジュールPub/Sub の使用を検討してください。

バックグラウンド スレッドのロギング エンティティは、スレッドを生成するエンティティとは無関係です。バックグラウンド スレッドの詳細は、App Engine の ThreadManager に関するドキュメントを参照してください。

import com.google.appengine.api.ThreadManager;
import java.util.concurrent.atomic.AtomicLong;

AtomicLong counter = new AtomicLong();

Thread thread = ThreadManager.createBackgroundThread(new Runnable() {
  public void run() {
    try {
      while (true) {
        counter.incrementAndGet();
        Thread.sleep(10);
      }
    } catch (InterruptedException ex) {
      throw new RuntimeException("Interrupted in loop:", ex);
    }
  }
});
thread.start();

自動スケーリング サービスで実行するコードで、バックグラウンド スレッドを開始しようとすると、例外が発生します。

同時実行バックグラウンド スレッドの最大数は、インスタンスごとに 10 個です。

App Engine SDK とツール

App Engine SDK for Java には、アプリケーションのテスト、アプリケーション ファイルのアップロード、ログデータのダウンロードを行うためのツールが含まれています。また、App Engine プロジェクトに共通するタスクを簡素化するための Apache Ant 用のコンポーネントも含まれています。

開発をサポートする Apache Maven 用のプラグインを使用することもできます。このプラグインは、Google Cloud SDK の app-engine-java コンポーネントに含まれています。

開発サーバーを使用すると、アプリケーションの開発とテストのために、ローカル コンピュータ上でアプリケーションを実行できます。サーバーは Cloud Datastore サービスとサンドボックス制限をシミュレートします。また、開発サーバーでは、テスト中にアプリケーションが実行するクエリに基づいて、Cloud Datastore インデックスの設定を生成することもできます。

App Engine で動作しているアプリケーションに対するすべてのコマンドライン操作は、AppCfg という多目的ツールで処理されます。AppCfg を使用すると、アプリケーションを App Engine にアップロードすることも、Cloud Datastore インデックス設定のみを更新することもできるため、コードを更新する前に新しいインデックスを作成できます。また、アプリケーションのログデータをダウンロードして、独自のツールでアプリケーションのパフォーマンスを分析することもできます。

同時実行とレイテンシ

トラフィックに対処するために必要なインスタンスの数に特に大きく影響するのはアプリケーションのレイテンシです。リクエストを短時間のうちに処理できるサービスであれば、1 つのインスタンスで多くのリクエストを処理できます。

現在、シングルスレッドのインスタンスでは、1 度に 1 件のリクエストしか処理できません。そのため、レイテンシと、1 つのインスタンスで処理できる 1 秒あたりのリクエスト数の間には直接の相関関係があります。たとえば、10 ミリ秒のレイテンシでは、1 つのインスタンスで 1 秒間に 100 件のリクエストを処理します。

マルチスレッドのインスタンスでは、多数のリクエストの同時処理が可能です。そのため、CPU 使用量と 1 秒あたりのリクエスト処理数の間に直接の相関関係があります。

Java アプリケーションは、同時リクエストをサポートしているため、1 つのインスタンスで他のリクエストが完了するまで待機している間に、新しいリクエストを処理できます。同時実行により、アプリケーションに必要なインスタンスの数は大幅に減りますが、アプリケーションの設計ではマルチスレッド処理に特に配慮する必要があります。

たとえば、B4 インスタンス(約 2.4 GHz)がリクエスト 1 件につき 10 M サイクルを使用する場合、1 つのインスタンスでの 1 秒あたりのリクエスト処理数は 240 件になります。リクエスト 1 件につき 100 M サイクルを使用する場合は、1 つのインスタンスでの 1 秒あたりのリクエスト処理数は 24 件になります。このような数字は理想的な条件を想定したものですが、インスタンスの処理能力という点ではかなり現実的な数字です。

このページは役立ちましたか?評価をお願いいたします。

フィードバックを送信...

Java の App Engine スタンダード環境