Android ランタイム(ART)でのアプリ動作の確認

Android ランタイム(ART)は、Android 5.0(API レベル 21)以降を搭載しているデバイスのデフォルトのランタイムです。このランタイムは、Android プラットフォームとアプリのパフォーマンスとスムーズさを改善する多くの機能を備えています。ART の新機能の詳細については、ART の概要をご覧ください。

ただし、Dalvik で機能する一部の手法は ART では機能しません。このドキュメントでは、既存のアプリを移行して ART との互換性を維持するために注意すべき点について説明します。ほとんどのアプリは、ART で実行している場合にのみ動作します。

ガベージ コレクション(GC)の問題への対処

Dalvik では、多くの場合、System.gc() を明示的に呼び出してガベージ コレクション(GC)を促す方法が便利です。ART では、特に GC_FOR_ALLOC タイプの発生を防ぐためまたは断片化を減らすためにガベージ コレクションを呼び出す場合は、必要となる処理が大幅に軽減されます。使用中のランタイムを確認するには、System.getProperty("java.vm.version") を呼び出します。ART が使用されている場合、プロパティの値は "2.0.0" 以上です。

ART は同時コピー(CC)コレクタを使用して、Java ヒープを同時に圧縮します。そのため、GC の圧縮と互換性のない手法(オブジェクト インスタンス データへのポインタの保存など)は使用しないでください。これは、Java Native Interface(JNI)を使用するアプリでは特に重要です。詳細については、JNI の問題の防止をご覧ください。

JNI の問題の防止

ART の JNI は Dalvik の JNI よりもやや厳格です。一般的な問題の検出には、CheckJNI モードの使用が特におすすめです。アプリで C/C++ コードを使用している場合は、以下の記事をご覧ください。

CheckJNI による Android JNI のデバッグ

ガベージ コレクションの問題に関する JNI コードの確認

同時コピー(CC)コレクタは、コンパクションのためにメモリ内のオブジェクトを移動する場合があります。C/C++ コードを使用する場合は、GC の圧縮と互換性のないオペレーションを実行しないでください。いくつかの潜在的な問題を特定できるように CheckJNI を強化しました(ICS での JNI ローカル参照の変更をご覧ください)。

特に注意すべき点は、Get...ArrayElements() 関数と Release...ArrayElements() 関数の使用です。非圧縮 GC を使用するランタイムでは、Get...ArrayElements() 関数は通常、配列オブジェクトをサポートする実際のメモリへの参照を返します。返された配列要素のいずれかを変更すると、配列オブジェクト自体が変更されます(Release...ArrayElements() の引数は通常無視されます)。ただし、圧縮 GC が使用されている場合、Get...ArrayElements() 関数はメモリのコピーを返すことがあります。GC の圧縮中に参照を誤って使用すると、メモリの破損やその他の問題が発生する可能性があります。次に例を示します。

  • 返された配列要素に変更を加えた場合は、作業後に適切な Release...ArrayElements() 関数を呼び出して、変更内容が基となる配列オブジェクトに正しくコピーされるようにする必要があります。
  • メモリ配列要素を解放するときは、変更の内容に応じて適切なモードを使用する必要があります。
    • 配列要素を変更していない場合は、JNI_ABORT モードを使用します。このモードでは、基になる配列オブジェクトに変更をコピーせずにメモリを解放します。
    • 配列に変更を加えて参照が不要になった場合は、コード 0 を使用します(配列オブジェクトを更新してメモリのコピーを解放します)。
    • commit する配列に変更を加えて、配列のコピーを保持する場合は、JNI_COMMIT を使用します(基になる配列オブジェクトを更新し、コピーを保持します)。
  • Release...ArrayElements() を呼び出すと、Get...ArrayElements() によって最初に返されたポインタと同じポインタが返されます。たとえば、(返された配列要素をスキャンするために)元のポインタをインクリメントしてから、インクリメントしたポインタを Release...ArrayElements() に渡すことは安全ではありません。この変更されたポインタを渡すと、間違ったメモリが解放され、メモリが破損する可能性があります。

エラー処理

ART の JNI は、Dalvik がスローしない多数のケースでエラーをスローします。(繰り返しになりますが、CheckJNI を使用してテストすることにより、このような多くのケースをキャッチできます)。

たとえば、存在しないメソッドで RegisterNatives が呼び出された場合(たとえば、そのメソッドが ProGuard などのツールによって削除された場合)、ART は NoSuchMethodError を適切にスローするようになりました。

08-12 17:09:41.082 13823 13823 E AndroidRuntime: FATAL EXCEPTION: main
08-12 17:09:41.082 13823 13823 E AndroidRuntime: java.lang.NoSuchMethodError:
    no static or non-static method
    "Lcom/foo/Bar;.native_frob(Ljava/lang/String;)I"
08-12 17:09:41.082 13823 13823 E AndroidRuntime:
    at java.lang.Runtime.nativeLoad(Native Method)
08-12 17:09:41.082 13823 13823 E AndroidRuntime:
    at java.lang.Runtime.doLoad(Runtime.java:421)
08-12 17:09:41.082 13823 13823 E AndroidRuntime:
    at java.lang.Runtime.loadLibrary(Runtime.java:362)
08-12 17:09:41.082 13823 13823 E AndroidRuntime:
    at java.lang.System.loadLibrary(System.java:526)

メソッドなしで RegisterNatives が呼び出された場合も、ART はエラーをログに記録します(logcat に表示されます)。

W/art     ( 1234): JNI RegisterNativeMethods: attempt to register 0 native
methods for <classname>

さらに、JNI 関数 GetFieldID() および GetStaticFieldID() は、単に null を返すのではなく、NoSuchFieldError を適切にスローするようになりました。同様に、GetMethodID()GetStaticMethodID()NoSuchMethodError を適切にスローするようになりました。これにより、未処理の例外や、ネイティブ コードの Java 呼び出し元に例外がスローされるため、CheckJNI エラーが発生する可能性があります。このため、CheckJNI モードで ART 対応アプリをテストすることが特に重要になります。

ART では、JNI 仕様の要件に従い、JNI CallNonvirtual...Method() メソッド(CallNonvirtualVoidMethod() など)のユーザーは、サブクラスではなくメソッドの宣言しているクラスを使用することを想定しています。

スタックサイズの問題の防止

Dalvik にはネイティブ コードと Java コード用の個別のスタックがあり、デフォルトの Java スタックサイズは 32 KB、デフォルトのネイティブ スタックサイズは 1 MB でした。ART は、局所性を高めるためにスタックを統合しています。通常、ART Thread スタックサイズは Dalvik とほぼ同じである必要があります。ただし、スタックサイズを明示的に設定した場合は、ART で実行されているアプリについて、それらの値の再使用が必要になる場合があります。

  • Java では、明示的なスタックサイズを指定する Thread コンストラクタの呼び出しを確認します。たとえば、StackOverflowError が発生した場合は、サイズを大きくする必要があります。
  • C/C++ で、JNI 経由で Java コードを実行するスレッドの pthread_attr_setstack()pthread_attr_setstacksize() の使用を確認します。pthread が小さすぎる場合にアプリが JNI AttachCurrentThread() を呼び出そうとしたときにログに記録されるエラーの例を次に示します。
    F/art: art/runtime/thread.cc:435]
        Attempt to attach a thread with a too-small stack (16384 bytes)

オブジェクト モデルの変更

Dalvik が、package-private メソッドのオーバーライドをサブクラスに誤って許可していました。 ART は次のような場合に警告を発行します。

Before Android 4.1, method void com.foo.Bar.quux()
would have incorrectly overridden the package-private method in
com.quux.Quux

別のパッケージでクラスのメソッドをオーバーライドする場合は、メソッドを public または protected として宣言します。

Object に非公開フィールドが追加されました。クラス階層のフィールドに反映するアプリは、Object のフィールドを確認しないように注意する必要があります。たとえば、シリアル化フレームワークの一部としてクラス階層を反復処理している場合は、

Class.getSuperclass() == java.lang.Object.class

メソッドが null を返すまで続行せずに済みます。

引数がない場合、プロキシ InvocationHandler.invoke() が空の配列ではなく null を受け取るようになりました。この動作は以前にドキュメント化されましたが、Dalvik では正しく処理されませんでした。以前のバージョンの Mockito ではこの処理が難しいため、ART でテストする場合は更新された Mockito バージョンを使用してください。

AOT コンパイルの問題を解決する

ART の事前(AOT)Java コンパイルは、すべての標準 Java コードで機能します。コンパイルは ART の dex2oat ツールによって実行されます。インストール時に dex2oat に関連する問題が発生した場合は、速やかに修正できるよう Google にお知らせください(問題を報告するをご覧ください)。注意すべき問題:

  • ART は、インストール時に Dalvik よりも厳密なバイトコード検証を行います。Android ビルドツールによって生成されたコードは問題ありません。ただし、一部の後処理ツール(特に難読化を行うツール)では、Dalvik では許容されても ART では拒否される無効なファイルが生成されることがあります。Google はツールベンダーと協力して、このような問題を発見し、修正しています。多くの場合、こうした問題は、ツールの最新バージョンを取得して DEX ファイルを再生成することで解決できます。
  • ART 検証ツールによって報告される一般的な問題には、次のようなものがあります。
    • 無効な制御フロー
    • monitorenter/monitorexit のバランスがとれていません
    • 長さ 0 のパラメータの型リストサイズ
  • アプリによっては、/system/framework/data/dalvik-cache、または DexClassLoader の最適化された出力ディレクトリにインストールされた .odex ファイル形式と依存関係があります。これらのファイルは、DEX ファイルの拡張形式ではなく、ELF ファイルになりました。ART は Dalvik と同じ命名規則およびロックルールに従うよう努めていますが、アプリがファイル形式に依存しないようにする必要があります。ファイル形式は予告なく変更される場合があります。

    注: Android 8.0(API レベル 26)以降では、DexClassLoader 最適化出力ディレクトリは非推奨になりました。詳細については、DexClassLoader() コンストラクタのドキュメントをご覧ください。

問題の報告

アプリの JNI の問題が原因ではない問題に遭遇した場合は、Android オープンソース プロジェクトの Issue Tracker(https://code.google.com/p/android/issues/list)で報告してください。 その際は、"adb bugreport" と、Google Play ストアにアプリへのリンクがある場合はそのリンクを含めます。Google Play ストアにアプリがない場合は、可能であれば、問題を再現する APK を添付してください。問題(添付ファイルを含む)は一般公開されます。