Class初期化:興味深い問題
9678 ワード
1問題の説明
1.1「null」or「Activityインスタンス参照」
次のコードを読んで考えてください:TestStatic.getActivity()の戻り値は「null」ですか、「Activityのインスタンス参照」ですか.public class TestStatic {
private static TestStatic sInstance = new TestStatic();
private static Activity sActivity = null;
private TestStatic() {
sActivity = new Activity();
}
public static Activity getActivity() {
return sActivity;
}
}
1.2問題分析
この問題は主に仮想マシン内部のクラス初期化プロセスの知識に関連しており、参考:[クラスロードリンク、初期化、インスタンス化](Class Load Links,Initialization,Instance)http://www.jianshu.com/p/0bff0413fd2f)クラスの初期処理には、クラスのロード、リンク、初期化、インスタンス化が含まれます.ここでは主に初期化フェーズに注目し、初期化フェーズは主に静的コードブロックを実行し、静的ドメインメンバーを初期化します.この2つの操作はクラス初期化方法で完了します.分析手順は次のとおりです.
次のコードを読んで考えてください:TestStatic.getActivity()の戻り値は「null」ですか、「Activityのインスタンス参照」ですか.
public class TestStatic {
private static TestStatic sInstance = new TestStatic();
private static Activity sActivity = null;
private TestStatic() {
sActivity = new Activity();
}
public static Activity getActivity() {
return sActivity;
}
}
1.2問題分析
この問題は主に仮想マシン内部のクラス初期化プロセスの知識に関連しており、参考:[クラスロードリンク、初期化、インスタンス化](Class Load Links,Initialization,Instance)http://www.jianshu.com/p/0bff0413fd2f)クラスの初期処理には、クラスのロード、リンク、初期化、インスタンス化が含まれます.ここでは主に初期化フェーズに注目し、初期化フェーズは主に静的コードブロックを実行し、静的ドメインメンバーを初期化します.この2つの操作はクラス初期化方法で完了します.分析手順は次のとおりです.
もしあなたの分析過程もそうであれば、答えが正しいことをおめでとうございますが、あまり早く喜ばないでください.分析過程が間違っているからです.[クラスロードリンク、初期化、インスタンス化](Class Load Links,Initialization,Instance)を真剣に読むと(http://www.jianshu.com/p/0bff0413fd2f)を選択すると、2ステップ目の解析で問題が発生していることがわかります.
private static TestStatic sInstance = new TestStatic();
ここでTestStaticクラスをインスタンス化すると、newの操作によってTestStaticクラスの初期化が発生します.まだ実行されていないため、クラスの初期化が完了していません.プリペイド方法(バイトコード):
.method static constructor ()V
.registers 1
.prologue
00000000 new-instance v0, TestStatic
00000004 invoke-direct TestStatic->()V, v0
0000000A sput-object v0, TestStatic->sInstance:TestStatic
0000000E const/4 v0, 0x0
00000010 sput-object v0, TestStatic->sActivity:Activity
00000014 return-void
.end method
.method private constructor ()V
.registers 2
.prologue
00000000 invoke-direct Object->()V, p0
00000006 new-instance v0, Activity
0000000A invoke-direct Activity->()V, v0
00000010 sput-object v0, TestStatic->sActivity:Activity
00000014 return-void
.end method
メソッドの最初のバイトコード:
00000000 new-instance v0, TestStatic
New-instanceはTestStaticクラスの初期化をトリガーします.すなわち、メソッドで呼び出されます.このようにしてここでデッドサイクルになったのでしょうか.もちろんそうではありません.その真相はどうなっていますか.この問題の背後には,クラス初期化に関する重要な知識点が隠されており,次にこの過程を完全に分析する.
2クラス初期化
クラスの初期化を引き起こす条件については、[クラスロードリンク、初期化、インスタンス化](Class Load Links,Initialization,Instance)を参照してください(http://www.jianshu.com/p/0bff0413fd2f)では,第2ステップの解析で問題が発生していることが分かるが,ここではこれ以上述べない.本問題に関連する2つの条件を選んで分析する.
2.1 invoke-staticバイトコードトリガのクラス初期化
仮想マシン(Dalvik)は、バイトコードを次のコードブロックで実行するように解釈する(関連しない部分は省略する).
GOTO_TARGET(invokeStatic, bool methodCallRange)
EXPORT_PC();
...
methodToCall = dvmDexGetResolvedMethod(methodClassDex, ref);
if (methodToCall == NULL) {
methodToCall = dvmResolveMethod(curMethod->clazz, ref, METHOD_STATIC);
if (methodToCall == NULL) {
ILOGV("+ unknown method");
GOTO_exceptionThrown();
}
...
}
...
GOTO_invokeMethod(methodCallRange, methodToCall, vsrc1, vdst);
GOTO_TARGET_END
dvmDexGetResolvedMethodはまずodexファイルから呼び出された静的メソッドを見つけ、dvmResolveMethod()は呼び出された静的メソッドが属するクラスが正しくロードされたかどうかを判断して初期化します.そうでなければ、クラスのロード、初期化などの操作がトリガーされます.
Method* dvmResolveMethod(const ClassObject* referrer, u4 methodIdx,
MethodType methodType)
{
...
resClass = dvmResolveClass(referrer, pMethodId->classIdx, false);
...
if (methodType == METHOD_DIRECT) {
resMethod = dvmFindDirectMethod(resClass, name, &proto);
} else if (methodType == METHOD_STATIC) {
resMethod = dvmFindDirectMethodHier(resClass, name, &proto);
} else {
resMethod = dvmFindVirtualMethodHier(resClass, name, &proto);
}
...
/*
* If we're the first to resolve this class, we need to initialize
* it now. Only necessary for METHOD_STATIC.
*/
if (methodType == METHOD_STATIC) {
if (!dvmIsClassInitialized(resMethod->clazz) &&
!dvmInitClass(resMethod->clazz))
{
assert(dvmCheckException(dvmThreadSelf()));
return NULL;
} else {
assert(!dvmCheckException(dvmThreadSelf()));
}
} else {
/*
* Edge case: if the for a class creates an instance
* of itself, we will call on a class that is still being
* initialized by us.
*/
assert(dvmIsClassInitialized(resMethod->clazz) ||
dvmIsClassInitializing(resMethod->clazz));
}
...
}
呼び出しdvmIsClassInitialized()は、クラスが正しく初期化されているかどうかを判断し、クラスのstatusがCLASS_にあるかどうかを判断します.INITIALIZED状態.
/*
* Determine if a class has been initialized.
*/
INLINE bool dvmIsClassInitialized(const ClassObject* clazz) {
return (clazz->status == CLASS_INITIALIZED);
}
クラスが初期化されていない場合は、dvmInitClass()メソッドを呼び出してクラスを初期化します.
bool dvmInitClass(ClassObject* clazz)
{
...
dvmLockObject(self, (Object*) clazz);
...
while (clazz->status == CLASS_INITIALIZING) {
if (clazz->initThreadId == self->threadId) {
//ALOGV("HEY: found a recursive ");
goto bail_unlock;
}
...
/*
* Wait for the other thread to finish initialization. We pass
* "false" for the "interruptShouldThrow" arg so it doesn't throw
* an exception on interrupt.
*/
dvmObjectWait(self, (Object*) clazz, 0, 0, false);
...
if (clazz->status == CLASS_INITIALIZING) {
ALOGI("Waiting again for class init");
continue;
}
...
goto bail_unlock;
}
...
clazz->initThreadId = self->threadId;
android_atomic_release_store(CLASS_INITIALIZING,
(int32_t*)(void*)&clazz->status);
dvmUnlockObject(self, (Object*) clazz);
...
initSFields(clazz);
/* Execute any static initialization code.*/
method = dvmFindDirectMethodByDescriptor(clazz, "", "()V");
if (method == NULL) {
LOGVV("No found for %s", clazz->descriptor);
} else {
LOGVV("Invoking %s.", clazz->descriptor);
JValue unused;
dvmCallMethod(self, method, NULL, &unused);
}
...
bail_unlock:
dvmUnlockObject(self, (Object*) clazz);
return (clazz->status != CLASS_ERROR);
}
dvmInitClass()実行プロセスを理解することは,この問題を認識する鍵である.コードブロックは関係のない部分を省略し、論理的に読みやすい.
上の3点の分析に基づいて、前の問題をはっきり説明することができます.で、メソッドを再呼び出しします.このスレッドの場合、後の呼び出しは直接終了します.そうしないと、ブロックされ、デッドループはありません.今回の例では,1番目のケース,すなわち同一スレッドにおけるループ呼び出し方法に属する.第2のケースは、マルチスレッドが1つの*Classを同時に初期化すると、仮想マシンが初期化操作が完了するまでブロックを選択します.Javaコードを書くときは、仮想マシンが手伝ってくれたので、このような初期化が同時に発生する問題を処理する必要はありません.
2.2 new-instanceトリガのクラス初期化
New-instanceは、仮想マシンによってコードブロックとして以下のように解釈される.
HANDLE_OPCODE(OP_NEW_INSTANCE /*vAA, class@BBBB*/)
{
...
clazz = dvmDexGetResolvedClass(methodClassDex, ref);
...
if (!dvmIsClassInitialized(clazz) && !dvmInitClass(clazz))
GOTO_exceptionThrown();
...
newObj = dvmAllocObject(clazz, ALLOC_DONT_TRACK);
...
}
新-instance命令の核心はインスタンスオブジェクトにメモリ領域を割り当てることであり、この操作の前にクラスが正しく初期化されていることを保証する必要があります.そうしないと、dvmInitClass()を呼び出してクラスを初期化します.例に戻ると、ここには理解に値する知識点があります.
したがって、ここでは、クラスのインスタンスオブジェクトの初期化がクラスの初期化の前に完了する可能性があります.私たちがjavaコードを書くときは、実は心配しなくてもいいです.同じスレッドでは、このような場合、クラスの初期化が完了するまで後の操作が行われますが、異なるスレッドでは、クラスの初期化が完了するまでブロックされます.