USAGE
dx --dex [OPTIONS] FILES...
OVERVIEW
Convert Java class files into Dalvik dex format.
OPTIONS
--
Stops parsing options. Arguments after this are regarded as FILES
even if they start with "-". Note that parsing options is stopped
also when an argument that does not start with "-" is found.
--debug
--verbose
--verbose-dump
--no-files
Ignores input files. If this option is given, FILES specified on
the command line are ignored. To put it another way, if this
option is not given, FILES must be specified on the command line.
--no-optimize
Skips optimization on methods. The resultant dex file is not
optimized but is generated speedily. See --optimize-list option.
--no-strict
Skips the following checks.
1. Whether the format version of input class files are in the
range which this dx command can handle.
2. Whether path names of input class files match the declared
package/class names.
--core-library
Allows classes under the following packages to be contained as
input files. Note that javax sub packages that are not listed below
can be contained without this option except classes which directly
belong to javax package (e.g. javax.MyClass).
java
javax.accessibility
javax.crypto
javax.imageio
javax.management
javax.naming
javax.net
javax.print
javax.rmi
javax.security
javax.sound
javax.sql
javax.swing
javax.transaction
javax.xml
--statistics
--optimize-list=FILE
Specifies a file which contains a newline-separated list of method
names. Methods listed in the file are optimized. Note that if this
option is given, methods which are not explicitly listed are not
optimized.
This option and --no-optimize-list option are mutually exclusive.
If neither of this option nor --no-optimize-list option is given,
all methods are optimized by default unless --no-optimize option
is given.
--no-optimize-list=FILE
Specifies a file which contains a newline-separated list of method
names. Methods listed in the file are not optimized. Note that if
this option is given, methods which are not explicitly listed are
optimized.
This option and --optimize-list option are mutually exclusive. If
neither of this option nor --optimize-list option is given, all
methods are optimized by default unless --no-optimize option is
given.
--keep-classes
Adds input class files into the output dex file. Output files
generated with this option contain both classes.dex file and
.class files, so the size is larger than ones generated without
this option, but such files can be used both on Dalvik VM and
Java Standard Edition VM.
--output=FILE
Specifies the name of the output file. The extension of FILE must
be one of the following: ".zip", ".jar", ".apk" and ".dex". "-" is
also allowed as FILE and if it is specified, the output goes to the
standard output.
--dump-to=FILE
Specifies where human-readable output should go. If --dump-method
option is not given, the default value used when this option is
not explicitly specified is "-" which means that human-readable
output is written to the standard output.
--dump-width=N
Specifies the maximum width of the human-oriented output. N must
be equal to or greater than 40.
--dump-method=METHOD
Dumps information about a specified method in human-readable format.
If this option is given, the normal dex output is not generated.
METHOD is a fully-qualified method name. The last letter of METHOD
can be '*', and in such a case, '*' is regareded as a wildcard and
all methods that match the pattern are dumped.
--positions=POSITION
Specifies how much position information should be preserved.
Possible values of POSTION and their meanings are as follows.
The default value is "lines".
"none" No position information is preserved.
"lines" Line number information is preserved.
"important" Important position information is preserved.
This contains block starts and instructions
that might throw.
--no-locals
Discards information about local variables.
FILES
Class files, jar files or directories which contain class files.
-------------------------------------------------
■ 使い方
dx --dex [オプション] ファイル...
■ 概要
Java クラスファイルを Dalvik DEX フォーマットに変換する。
■ オプション
--
オプション解析を終了する。これに続く引数は、たとえ "-" で始まって
いてもファイルとみなされる。"-" で開始しない引数が見つかったときにも
オプション解析は終了する。
--debug
--verbose
--verbose-dump
--no-files
入力ファイルを無視する。このオプションが与えられると、コマンド
ライン上に指定されたファイル群は無視される。別の言い方をすると、
このオプションを与えない場合は、コマンドライン上でファイル群を
必ず指定しなければならない。
--no-optimize
メソッドの最適化をおこなわない。出力 DEX ファイルは最適化されないが、
生成処理は速くなる。--optimize-list オプションを参照のこと。
--no-strict
次のチェックをおこなわない。
1. 入力クラスファイルのフォーマットバージョンが、dx コマンドが
扱える範囲にあるかどうか。
2. 入力クラスファイルのパス名と、宣言されているパッケージ/
クラス名が合っているかどうか。
--core-library
下記のパッケージ下のクラスが入力ファイルに含まれていても許容する。
このオプションを与えなくても、下記にリストされていない javax サブ
パッケージを含めることはできるが、javax パッケージに直接属するクラス
(javax.MyClass 等) は除く。
java
javax.accessibility
javax.crypto
javax.imageio
javax.management
javax.naming
javax.net
javax.print
javax.rmi
javax.security
javax.sound
javax.sql
javax.swing
javax.transaction
javax.xml
--statistics
--optimize-list=FILE
改行で区切られたメソッド名のリストを含むファイルを指定する。その
ファイルにリストされているメソッド群は最適化される。このオプションが
指定された場合、明示的にリストされていないメソッドは最適化されない
ので注意。
このオプションと --no-optimize-list オプションは相互排他である。
このオプションも --no-optimize-list オプションも与えられなかった
場合、--no-optimize オプションが与えられない限り、デフォルトで
全てのメソッドが最適化される。
--no-optimize-list=FILE
改行で区切られたメソッド名のリストを含むファイルを指定する。その
ファイルにリストされているメソッド群は最適化されない。このオプションが
指定された場合、明示的にリストされていないメソッド群は最適化される。
このオプションと --optimize-list オプションは相互排他である。この
オプションも --optimize-list オプションも与えられなかった場合、
--no-optimize オプションが与えられない限り、デフォルトで全ての
メソッドが最適化される。
--keep-classes
入力クラスファイル群を出力 DEX ファイルに追加する。このオプションを
与えて生成されたファイルは classes.dex ファイルと .class ファイル群の
両方を含む。そのため、このオプション無しで生成されたものよりもサイズは
大きくなるが、Dalvik VM と Java Standard Edition VM の両方で使用する
ことができるようになる。
--output=FILE
出力ファイルの名前を指定する。FILE の拡張子は、".zip", ".jar", ".apk",
".dex" のいずれかでなければならない。FILE として "-" を指定することも
可能で、その場合、出力は標準出力に対してなされる。
--dump-to=FILE
人間が読める形式の出力の送り先を指定する。--dump-method オプションが
与えられていない場合、このオプションが明示的に指定されていないときの
デフォルト値は "-" であり、これは、人間が読める形式の出力が標準出力に
書き込まれることを意味する。
--dump-width=N
人間が読める形式の出力の最大幅を指定する。N は 40 以上でなければならない。
--dump-method=METHOD
指定されたメソッドに関する情報を人間が読める形式で出力する。この
オプションが与えられた場合、通常の DEX 出力は生成されない。
METHOD は完全修飾メソッド名である。METHOD の最後の文字を '*' にする
ことができ、その場合は '*' はワイルドカードとみなされ、そのパターンに
適合する全てのメソッドがダンプされる。
--position=POSITION
位置情報をどの程度保持するかを指定する。POSITION に取りうる値とそれらの
意味は下記のとおり。デフォルト値は "lines"。
"none" 位置情報を保持しない。
"lines" 行番号情報を保持する。
"important" 重要な位置情報を保持する。これには、ブロックの
開始や例外を投げうるインストラクションが含まれる。
--no-locals
ローカル変数に関する情報を破棄する。
■ ファイル...
クラスファイル、JAR ファイル、もしくはクラスファイルを含むディレクトリ。
2011年3月18日金曜日
2011年3月11日金曜日
SQLiteDatabase is closed automatically under some conditions
To say the result of investigation first, "A database (android.database.sqlite.SQLiteDatabase) used to create a cursor (android.database.Cursor) is automatically closed if some simple conditions are satisfied."
The flow which triggers the auto close is as follows.
causes the database (SQLiteDatabase) to be closed automatically because the implementation of ListView.setAdapter() calls getCount() method of the given cursor and so the steps from (1) to (11) are performed. On the contrary, if the step (1) is not started after a cursor is created, the auto close won't occur and the database remains open.
If you encounter one of the following error messages, there is a possibility that the error is caused by the database auto close. (These are error messages that are contained in the current or past implementations of SQLiteClosable.acquireReference() method.)
If you want to prevent the database from being automatically closed, one possible solution would be to call SQLiteClosable.acquireReference() to prevent the reference count of the database from getting down to 0. In such a case, the database needs to be closed later manually, of course.
ある条件下で SQLiteDatabase は自動的にクローズされる
調査結果を先に言うと、「カーソル(android.database.Cursor)作成時に使用したデータベース(android.database.sqlite.SQLiteDatabase)は、ある要件を満たすと、自動的にクローズされる。」
自動クローズが発生するときの処理の流れは次のようになっている。
そういうわけで、
というコーディングをすると、リストビューの setAdapter() メソッドの実装がカーソルの getCount() メソッドを呼ぶので、結果上記 (1)~(11) の処理が走り、データベース (SQLiteDatabase) は自動的にクローズされることになる。逆に言うと、カーソルを作成したものの、上記 (1) が発生しないと、データベースの自動クローズ処理は走らないので、放っておくとリークしてしまう。
もしも次のいずれかのエラーメッセージに遭遇したなら、データベースの自動クローズが走ってしまったことが原因の可能性がある。(これらは過去もしくは現在の SQLiteClosable.acquireReference() の実装に含まれていたエラーメッセージである。)
自動でクローズさせたくない場合は、一つの解決方法として、データベースの参照カウントがゼロにならないように SQLiteClosable.acquireReference() を呼ぶという手がある。もちろん後で自分でデータベースをクローズする必要がある。
The flow which triggers the auto close is as follows.
- Actual data fetch is triggered when either Cursor.getCount() method or Cursor.onMove() method is called.
- It triggers SQLiteQuery.fillWindow(CursorWindow, int, int) method to be called.
- SQLiteQuery.fillWindow() calls SQLiteQuery.releaseReferences() as its last step.
- The implementation of SQLiteQuery.releaseReference() is in its indirect super class, SQLiteClosable. (SQLiteQuery extends SQLiteProgram, and SQLiteProgram extends SQLiteClosable.)
- SQLiteClosable.releaseReference() decrements the reference count.
- If the reference count becomes 0, SQLiteClosable calls an abstract method, onAllReferencesReleased().
- The super class of SQLiteQuery, SQLiteProgram, has the implementation of onAllReferencesReleased().
- SQLiteProgram.onAllReferencesReleased() calls releaseReference() method on an SQLiteDatabase instance it holds.
- SQLiteDatabase also extends SQLiteClosable. As a result, SQLiteDatabase.onAllReferencesReleased() is called.
- SQLiteDatabase.onAllReferencesReleased() calls a native method, dbclose().
- dbclose() closes the underlying database by calling sqlite3_close().
- Create a cursor.
- Create a cursor adapter using the created cursor.
- Pass the cursor adapter to a ListView using ListView.setAdapter() method.
causes the database (SQLiteDatabase) to be closed automatically because the implementation of ListView.setAdapter() calls getCount() method of the given cursor and so the steps from (1) to (11) are performed. On the contrary, if the step (1) is not started after a cursor is created, the auto close won't occur and the database remains open.
If you encounter one of the following error messages, there is a possibility that the error is caused by the database auto close. (These are error messages that are contained in the current or past implementations of SQLiteClosable.acquireReference() method.)
"attempt to re-open an already-closed object: " + getObjInfo()
"attempt to acquire a reference on an already-closed " + getObjInfo()
"attempt to acquire a reference on an already-closed SQLiteClosable obj."
"attempt to acquire a reference on a close SQLiteClosable"
If you want to prevent the database from being automatically closed, one possible solution would be to call SQLiteClosable.acquireReference() to prevent the reference count of the database from getting down to 0. In such a case, the database needs to be closed later manually, of course.
ある条件下で SQLiteDatabase は自動的にクローズされる
調査結果を先に言うと、「カーソル(android.database.Cursor)作成時に使用したデータベース(android.database.sqlite.SQLiteDatabase)は、ある要件を満たすと、自動的にクローズされる。」
自動クローズが発生するときの処理の流れは次のようになっている。
- カーソルの Cursor.getCount() メソッドもしくは onMove() メソッドが呼ばれると、実際のデータ取得処理が走る。
- これにより SQLiteQuery.fillWindow(CursorWindow, int, int) メソッドが呼ばれることになる。
- SQLiteQuery.fillWindow() は、最後に SQLiteQuery.releaseReference() を呼ぶ。
- SQLiteQuery.releaseReference() の実体は、親(SQLiteProgram)の親クラスの SQLiteClosable にある。
- SQLiteClosable.releaseReference() は、参照カウントを減らす。
- 参照カウントがゼロになると、SQLiteClosable は抽象メソッド onAllReferencesReleased() を呼ぶ。
- SQLiteQuery の親クラス SQLiteProgram が onAllReferencesReleased() を実装している。
- SQLiteProgram.onAllReferencesReleased() は、保持している SQLiteDatabase のインスタンスの releaseReference() メソッドを呼ぶ。
- SQLiteDatabase も SQLiteClosable を継承しているので、結果、SQLiteDatabase.onAllReferencesReleased() が呼ばれる。
- SQLiteDatabase.onAllReferencesReleased() は、ネイティブメソッド dbclose() を呼ぶ。
- dbclose() は、sqlite3_close() を呼んでデータベースを閉じる。
そういうわけで、
- カーソルを作成する。
- 作成したカーソルをもとにカーソルアダプタを作成する。
- カーソルアダプタをリストビューの setAdapter() メソッドを用いてリストビューに渡す。
というコーディングをすると、リストビューの setAdapter() メソッドの実装がカーソルの getCount() メソッドを呼ぶので、結果上記 (1)~(11) の処理が走り、データベース (SQLiteDatabase) は自動的にクローズされることになる。逆に言うと、カーソルを作成したものの、上記 (1) が発生しないと、データベースの自動クローズ処理は走らないので、放っておくとリークしてしまう。
もしも次のいずれかのエラーメッセージに遭遇したなら、データベースの自動クローズが走ってしまったことが原因の可能性がある。(これらは過去もしくは現在の SQLiteClosable.acquireReference() の実装に含まれていたエラーメッセージである。)
"attempt to re-open an already-closed object: " + getObjInfo()
"attempt to acquire a reference on an already-closed " + getObjInfo()
"attempt to acquire a reference on an already-closed SQLiteClosable obj."
"attempt to acquire a reference on a close SQLiteClosable"
自動でクローズさせたくない場合は、一つの解決方法として、データベースの参照カウントがゼロにならないように SQLiteClosable.acquireReference() を呼ぶという手がある。もちろん後で自分でデータベースをクローズする必要がある。
2011年3月7日月曜日
Android エミュレータが圏外状態になる問題(解決?)
Android エミュレータの電波強度が圏外になってしまうという現象が時折発生し、困っていた。エミュレータ内の設定画面をいろいろ操作しても問題が解決しなかった。問題解決方法を求めて長いことネットを検索していたところ、次のページを見つけた。
そのページには「Android エミュレータの電波強度が圏外になってしまう」件について質問があり、それに対する回答の一つに次のようなものがあった。
要点は、「Android エミュレータを動かしているマシンに VPN の設定があり、その名前が、アルファベット順に並べたときに『Local Network』よりも先に来るものだった場合、圏外問題が発生することがある」というものだ。回答者は、名前を(アルファベット順で並べたときに「Local Network」よりも後にくる)「VPN」に変更したら問題が解決した、と言っている。
私の環境にも、アルファベット順に並べた時に「Local Network」よりも前に来る VPN の名前があった。ただ、その名前を変更しなくても、エミュレータを使うときにその VPN を使用していなければ圏外問題は発生しないように見える。そのため、エミュレータが圏外状態になったときは、もし VPN が動いていれば止めて、それでも圏外状態から復帰しなければエミュレータを起動し直す、などとしている。これが正しい問題解決方法なのかどうか分からないが、上記の回答を見つけて VPN に対して注意を払うようになって以降、Android エミュレータの圏外問題で悩むことはなくなった。
Get "No Service" on Android Emulator
http://stackoverflow.com/questions/3098923/get-no-service-on-android-emulator
そのページには「Android エミュレータの電波強度が圏外になってしまう」件について質問があり、それに対する回答の一つに次のようなものがあった。
I ran into this exact problem tonight. My research showed that the emulator tries to connect to the first network adapter it can find. Success or failure is all based on that first adapter. For me, I had a VPN with a name that started with A. I renamed it to "VPN" so that it was below the "Local Network" adapter, restarted the emulator and voila! it worked.
Silly? Yes. Stupid? Yes. Works? Yep.
要点は、「Android エミュレータを動かしているマシンに VPN の設定があり、その名前が、アルファベット順に並べたときに『Local Network』よりも先に来るものだった場合、圏外問題が発生することがある」というものだ。回答者は、名前を(アルファベット順で並べたときに「Local Network」よりも後にくる)「VPN」に変更したら問題が解決した、と言っている。
私の環境にも、アルファベット順に並べた時に「Local Network」よりも前に来る VPN の名前があった。ただ、その名前を変更しなくても、エミュレータを使うときにその VPN を使用していなければ圏外問題は発生しないように見える。そのため、エミュレータが圏外状態になったときは、もし VPN が動いていれば止めて、それでも圏外状態から復帰しなければエミュレータを起動し直す、などとしている。これが正しい問題解決方法なのかどうか分からないが、上記の回答を見つけて VPN に対して注意を払うようになって以降、Android エミュレータの圏外問題で悩むことはなくなった。
2011年3月1日火曜日
Android System.exit(int): 驚愕の実装
Android の System.exit(int) を実行すると、次の順序で処理が進む。
System.exit(int) of Android leads to the code flow shown as below.
(1) java.lang.System.exit(int code)
(2) java.lang.Runtime.exit(int code)
(3) java.lang.Runtime.nativeExit(int code, boolean isExit)
(4) Dalvik_java_lang_Runtime_nativeExit(const u4* args, JValue* pResult)
(5) exit(int status)
何に驚愕したかというと、System.exit(int) を実行すると、Dalvik VM の終了処理など全くおこなわれず、Dalvik VM インタプリタの文脈でC言語インターフェースの exit(int) 関数が実行され、そこで Dalvik VM プロセスが終了してしまう点だ。Dalvik VM 終了処理関数である dvmShutdown() は呼ばれないので、dvmShutdown() 経由で呼び出されるはずのあらゆる終了処理が実行されない。メモリやスレッドなどのシステムリソースの解放処理が走ることはなく、それらの解放は完全にOS任せである。valgrind 下で実行すると、メモリリークが大量に検出される。OpenJDK の System.exit(int) が慎重に終了処理をおこなっているのとは大違いだ。やはり Dalvik VM は品質が良くない(「Android で OSGi を使うべきでない理由」も参照のこと)。
What made me aghast was that if System.exit(int) is executed, the shutdown steps of Dalvik VM are not performed at all and the C function "exit(int)" is executed in the context of the Dalvik VM interpreter and the Dalvik VM process terminates there. Because the shutdown function of Dalvik VM, dvmShutdown(), is not called, none of shutdown steps that are supposed to be called via dvmShutdown() is executed. No steps to release memory, threads and other system resources are executed, so freeing such resources is totally left to the OS. Many memory leaks are reported by valgrind. Great difference from OpenJDK whose System.exit(int) performs shutdown processing very carefully. After all, quality of Dalvik VM is not good (See "The reason that OSGi should not be used on Android", too).
下記のコードは、Android における、System.exit(int) から exit(int) までのソースコードの流れである。
The code below is the flow from System.exit(int) to exit(int) in Android.
java.lang.System.exit(int code)
public static void exit(int code) {
Runtime.getRuntime().exit(code);
}
java.lang.Runtime.exit(int code)
public void exit(int code) {
// Security checks
SecurityManager smgr = System.getSecurityManager();
if (smgr != null) {
smgr.checkExit(code);
}
// Make sure we don't try this several times
synchronized(this) {
if (!shuttingDown) {
shuttingDown = true;
Thread[] hooks;
synchronized (shutdownHooks) {
// create a copy of the hooks
hooks = new Thread[shutdownHooks.size()];
shutdownHooks.toArray(hooks);
}
// Start all shutdown hooks concurrently
for (int i = 0; i < hooks.length; i++) {
hooks[i].start();
}
// Wait for all shutdown hooks to finish
for (Thread hook : hooks) {
try {
hook.join();
} catch (InterruptedException ex) {
// Ignore, since we are at VM shutdown.
}
}
// Ensure finalization on exit, if requested
if (finalizeOnExit) {
runFinalization(true);
}
// Get out of here finally...
nativeExit(code, true);
}
}
}
java.lang.Runtime.nativeExit(int code, boolean isExit)
static void Dalvik_java_lang_Runtime_nativeExit(const u4* args, JValue* pResult)
{
int status = args[0];
bool isExit = (args[1] != 0);
if (isExit && gDvm.exitHook != NULL) {
dvmChangeStatus(NULL, THREAD_NATIVE);
(*gDvm.exitHook)(status); // not expected to return
dvmChangeStatus(NULL, THREAD_RUNNING);
LOGW("JNI exit hook returned\n");
}
LOGD("Calling exit(%d)\n", status);
#if defined(WITH_JIT) && defined(WITH_JIT_TUNING)
dvmCompilerDumpStats();
#endif
exit(status);
}
Dalvik VM に手を加えて、dvmShutdown() の文脈で何か処理をおこなおうとしても、アプリが System.exit(int) を呼ぶと dvmShutdown() 自体がスキップされるので要注意。 なお、下記のコードは dvmShutdown() の実装である。System.exit(int) で、この内容が全部スキップされるということである。
When you modify Dalvik VM to do something in the context of dvmShutdown(), you have to keep in mind that the entire dvmShutdown() function is skipped if System.exit(int) is called by applications. The code below is the implementation of dvmShutdown(). System.exit(int) skips all of the content.
void dvmShutdown(void)
{
LOGV("VM shutting down\n");
if (CALC_CACHE_STATS)
dvmDumpAtomicCacheStats(gDvm.instanceofCache);
/*
* Stop our internal threads.
*/
dvmGcThreadShutdown();
if (gDvm.jdwpState != NULL)
dvmJdwpShutdown(gDvm.jdwpState);
free(gDvm.jdwpHost);
gDvm.jdwpHost = NULL;
free(gDvm.jniTrace);
gDvm.jniTrace = NULL;
free(gDvm.stackTraceFile);
gDvm.stackTraceFile = NULL;
/* tell signal catcher to shut down if it was started */
dvmSignalCatcherShutdown();
/* shut down stdout/stderr conversion */
dvmStdioConverterShutdown();
#ifdef WITH_JIT
if (gDvm.executionMode == kExecutionModeJit) {
/* shut down the compiler thread */
dvmCompilerShutdown();
}
#endif
/*
* Kill any daemon threads that still exist. Actively-running threads
* are likely to crash the process if they continue to execute while
* the VM shuts down.
*/
dvmSlayDaemons();
if (gDvm.verboseShutdown)
LOGD("VM cleaning up\n");
dvmDebuggerShutdown();
dvmReflectShutdown();
dvmProfilingShutdown();
dvmJniShutdown();
dvmStringInternShutdown();
dvmExceptionShutdown();
dvmThreadShutdown();
dvmClassShutdown();
dvmVerificationShutdown();
dvmRegisterMapShutdown();
dvmInstanceofShutdown();
dvmInlineNativeShutdown();
dvmGcShutdown();
dvmAllocTrackerShutdown();
dvmPropertiesShutdown();
/* these must happen AFTER dvmClassShutdown has walked through class data */
dvmNativeShutdown();
dvmInternalNativeShutdown();
free(gDvm.bootClassPathStr);
free(gDvm.classPathStr);
freeAssertionCtrl();
/*
* We want valgrind to report anything we forget to free as "definitely
* lost". If there's a pointer in the global chunk, it would be reported
* as "still reachable". Erasing the memory fixes this.
*
* This must be erased to zero if we want to restart the VM within this
* process.
*/
memset(&gDvm, 0xcd, sizeof(gDvm));
}
System.exit(int) of Android leads to the code flow shown as below.
(1) java.lang.System.exit(int code)
(2) java.lang.Runtime.exit(int code)
(3) java.lang.Runtime.nativeExit(int code, boolean isExit)
(4) Dalvik_java_lang_Runtime_nativeExit(const u4* args, JValue* pResult)
(5) exit(int status)
何に驚愕したかというと、System.exit(int) を実行すると、Dalvik VM の終了処理など全くおこなわれず、Dalvik VM インタプリタの文脈でC言語インターフェースの exit(int) 関数が実行され、そこで Dalvik VM プロセスが終了してしまう点だ。Dalvik VM 終了処理関数である dvmShutdown() は呼ばれないので、dvmShutdown() 経由で呼び出されるはずのあらゆる終了処理が実行されない。メモリやスレッドなどのシステムリソースの解放処理が走ることはなく、それらの解放は完全にOS任せである。valgrind 下で実行すると、メモリリークが大量に検出される。OpenJDK の System.exit(int) が慎重に終了処理をおこなっているのとは大違いだ。やはり Dalvik VM は品質が良くない(「Android で OSGi を使うべきでない理由」も参照のこと)。
What made me aghast was that if System.exit(int) is executed, the shutdown steps of Dalvik VM are not performed at all and the C function "exit(int)" is executed in the context of the Dalvik VM interpreter and the Dalvik VM process terminates there. Because the shutdown function of Dalvik VM, dvmShutdown(), is not called, none of shutdown steps that are supposed to be called via dvmShutdown() is executed. No steps to release memory, threads and other system resources are executed, so freeing such resources is totally left to the OS. Many memory leaks are reported by valgrind. Great difference from OpenJDK whose System.exit(int) performs shutdown processing very carefully. After all, quality of Dalvik VM is not good (See "The reason that OSGi should not be used on Android", too).
下記のコードは、Android における、System.exit(int) から exit(int) までのソースコードの流れである。
The code below is the flow from System.exit(int) to exit(int) in Android.
java.lang.System.exit(int code)
public static void exit(int code) {
Runtime.getRuntime().exit(code);
}
java.lang.Runtime.exit(int code)
public void exit(int code) {
// Security checks
SecurityManager smgr = System.getSecurityManager();
if (smgr != null) {
smgr.checkExit(code);
}
// Make sure we don't try this several times
synchronized(this) {
if (!shuttingDown) {
shuttingDown = true;
Thread[] hooks;
synchronized (shutdownHooks) {
// create a copy of the hooks
hooks = new Thread[shutdownHooks.size()];
shutdownHooks.toArray(hooks);
}
// Start all shutdown hooks concurrently
for (int i = 0; i < hooks.length; i++) {
hooks[i].start();
}
// Wait for all shutdown hooks to finish
for (Thread hook : hooks) {
try {
hook.join();
} catch (InterruptedException ex) {
// Ignore, since we are at VM shutdown.
}
}
// Ensure finalization on exit, if requested
if (finalizeOnExit) {
runFinalization(true);
}
// Get out of here finally...
nativeExit(code, true);
}
}
}
java.lang.Runtime.nativeExit(int code, boolean isExit)
static void Dalvik_java_lang_Runtime_nativeExit(const u4* args, JValue* pResult)
{
int status = args[0];
bool isExit = (args[1] != 0);
if (isExit && gDvm.exitHook != NULL) {
dvmChangeStatus(NULL, THREAD_NATIVE);
(*gDvm.exitHook)(status); // not expected to return
dvmChangeStatus(NULL, THREAD_RUNNING);
LOGW("JNI exit hook returned\n");
}
LOGD("Calling exit(%d)\n", status);
#if defined(WITH_JIT) && defined(WITH_JIT_TUNING)
dvmCompilerDumpStats();
#endif
exit(status);
}
Dalvik VM に手を加えて、dvmShutdown() の文脈で何か処理をおこなおうとしても、アプリが System.exit(int) を呼ぶと dvmShutdown() 自体がスキップされるので要注意。 なお、下記のコードは dvmShutdown() の実装である。System.exit(int) で、この内容が全部スキップされるということである。
When you modify Dalvik VM to do something in the context of dvmShutdown(), you have to keep in mind that the entire dvmShutdown() function is skipped if System.exit(int) is called by applications. The code below is the implementation of dvmShutdown(). System.exit(int) skips all of the content.
void dvmShutdown(void)
{
LOGV("VM shutting down\n");
if (CALC_CACHE_STATS)
dvmDumpAtomicCacheStats(gDvm.instanceofCache);
/*
* Stop our internal threads.
*/
dvmGcThreadShutdown();
if (gDvm.jdwpState != NULL)
dvmJdwpShutdown(gDvm.jdwpState);
free(gDvm.jdwpHost);
gDvm.jdwpHost = NULL;
free(gDvm.jniTrace);
gDvm.jniTrace = NULL;
free(gDvm.stackTraceFile);
gDvm.stackTraceFile = NULL;
/* tell signal catcher to shut down if it was started */
dvmSignalCatcherShutdown();
/* shut down stdout/stderr conversion */
dvmStdioConverterShutdown();
#ifdef WITH_JIT
if (gDvm.executionMode == kExecutionModeJit) {
/* shut down the compiler thread */
dvmCompilerShutdown();
}
#endif
/*
* Kill any daemon threads that still exist. Actively-running threads
* are likely to crash the process if they continue to execute while
* the VM shuts down.
*/
dvmSlayDaemons();
if (gDvm.verboseShutdown)
LOGD("VM cleaning up\n");
dvmDebuggerShutdown();
dvmReflectShutdown();
dvmProfilingShutdown();
dvmJniShutdown();
dvmStringInternShutdown();
dvmExceptionShutdown();
dvmThreadShutdown();
dvmClassShutdown();
dvmVerificationShutdown();
dvmRegisterMapShutdown();
dvmInstanceofShutdown();
dvmInlineNativeShutdown();
dvmGcShutdown();
dvmAllocTrackerShutdown();
dvmPropertiesShutdown();
/* these must happen AFTER dvmClassShutdown has walked through class data */
dvmNativeShutdown();
dvmInternalNativeShutdown();
free(gDvm.bootClassPathStr);
free(gDvm.classPathStr);
freeAssertionCtrl();
/*
* We want valgrind to report anything we forget to free as "definitely
* lost". If there's a pointer in the global chunk, it would be reported
* as "still reachable". Erasing the memory fixes this.
*
* This must be erased to zero if we want to restart the VM within this
* process.
*/
memset(&gDvm, 0xcd, sizeof(gDvm));
}
登録:
投稿 (Atom)