依賴順序的場景
如果大家看過或者實現過序列化反序列化的代碼,這個問題就不難回答了,今天碰到的這個問題其實是發生在大家可能最常用的fastjson
庫里的,所以如果大家在使用這個庫,請務必檢查下你的代碼,以免踩到此坑
對象序列化
大家都知道當我們序列化好一個對象之后,要反序列回來,那問題就來了,就拿這個json序列化來說吧,我們要將對象序列化成json串,那意味著我們要先取出這個對象的屬性,然后寫成鍵值對的形式,那取值就意味著我們要遵循java bean的規范通過getter方法來取,那其實getter方法有兩種,一種是boolean類型的,一種是其他類型的,如果是boolean類型的,那我們通常是isXXX()
這樣的方法,如果是其他類型的,一般是getXXX()
這樣的方法。那假如說我們的類里針對某個屬性a,同時存在兩個方法isA()
和getA()
,那究竟我們會調用哪個來取值?這個就取決于具體的序列化框架實現了,比如導致我們這篇文章誕生的fastjson
,就是利用我們這篇文章的主角java.lang.Class.getMethods
返回的數組,然后挨個遍歷,先找到哪個就是哪個,如果我們的這個數組正好因為jvm本身實現沒有保證順序,那么可能先找到isA()
,也可能先找到getA()
,如果兩個方法都是返回a這個屬性其實問題也不大,假如正好是這兩個方法返回不同的內容呢?
private A a;public A getA(){ return a;
}public boolean isA(){ return false;
}public void setA(A a){ this.a=a;
}
如果是上面的內容,那可能就會悲劇了,如果選了isA()
,那其實是返回一個boolean類型的,將這個boolean寫入到json串里,如果是選了getA()
,那就是將A這個類型的對象寫到json串里
對象反序列化
在完成了序列化過程之后,需要將這個字符串進行反序列化了,于是就會去找json串里對應字段的setter方法,比如上面的setA(A a)
,假如我們之前選了isA()
序列化好內容,那我們此時的值是一個boolean值false,那就無法通過setA
來賦值還原對象了。
解決方案
相信大家看完我上面的描述,知道這個問題所在了,要避免類似的問題,方案其實也挺多,比如對方法進行先排序,又比如說優先使用isXXX()
方法,不過這種需要和開發者達成共識,和setter要對應得起來
jvm里為什么不保證順序
JDK層面的代碼我就暫時不說了,大家都能看到代碼,從java.lang.Class.getMethods
一層層走下去,相信大家細心點還是能抓住整個脈絡的,我這里主要想說大家可能比較難看到的一些實現,比如JVM里的具體實現
正常情況下大家跟代碼能跟到調用了java.lang.Class.getDeclaredMethods0
這個native方法,其具體實現如下
JVM_ENTRY(jobjectArray, JVM_GetClassDeclaredMethods(JNIEnv *env, jclass ofClass, jboolean publicOnly))
{
JVMWrapper("JVM_GetClassDeclaredMethods"); return get_class_declared_methods_helper(env, ofClass, publicOnly, /*want_constructor*/ false,
SystemDictionary::reflect_Method_klass(), THREAD);
}
JVM_END
其主要調用了get_class_declared_methods_helper
方法
static jobjectArray get_class_declared_methods_helper(
JNIEnv *env,
jclass ofClass, jboolean publicOnly,
bool want_constructor,
Klass* klass, TRAPS) {
JvmtiVMObjectAllocEventCollector oam; // Exclude primitive types and array types
if (java_lang_Class::is_primitive(JNIHandles::resolve_non_null(ofClass))
|| java_lang_Class::as_Klass(JNIHandles::resolve_non_null(ofClass))- >oop_is_array()) { // Return empty array
oop res = oopFactory::new_objArray(klass, 0, CHECK_NULL); return (jobjectArray) JNIHandles::make_local(env, res);
} instanceKlassHandle k(THREAD, java_lang_Class::as_Klass(JNIHandles::resolve_non_null(ofClass))); // Ensure class is linked
k- >link_class(CHECK_NULL);
Array< Method* >* methods = k- >methods(); int methods_length = methods- >length(); // Save original method_idnum in case of redefinition, which can change
// the idnum of obsolete methods. The new method will have the same idnum
// but if we refresh the methods array, the counts will be wrong.
ResourceMark rm(THREAD);
GrowableArray< int >* idnums = new GrowableArray< int >(methods_length); int num_methods = 0; for (int i = 0; i < methods_length; i++) { methodHandle method(THREAD, methods- >at(i)); if (select_method(method, want_constructor)) { if (!publicOnly || method- >is_public()) {
idnums- >push(method- >method_idnum());
++num_methods;
}
}
} // Allocate result
objArrayOop r = oopFactory::new_objArray(klass, num_methods, CHECK_NULL); objArrayHandle result (THREAD, r); // Now just put the methods that we selected above, but go by their idnum
// in case of redefinition. The methods can be redefined at any safepoint,
// so above when allocating the oop array and below when creating reflect
// objects.
for (int i = 0; i < num_methods; i++) { methodHandle method(THREAD, k- >method_with_idnum(idnums- >at(i))); if (method.is_null()) { // Method may have been deleted and seems this API can handle null
// Otherwise should probably put a method that throws NSME
result- >obj_at_put(i, NULL);
} else {
oop m; if (want_constructor) {
m = Reflection::new_constructor(method, CHECK_NULL);
} else {
m = Reflection::new_method(method, UseNewReflection, false, CHECK_NULL);
}
result- >obj_at_put(i, m);
}
} return (jobjectArray) JNIHandles::make_local(env, result());
}
從上面的k->method_with_idnum(idnums->at(i))
,我們基本知道方法主要是從klass里來的
Method* InstanceKlass::method_with_idnum(int idnum) {
Method* m = NULL; if (idnum < methods()- >length()) {
m = methods()- >at(idnum);
} if (m == NULL || m- >method_idnum() != idnum) { for (int index = 0; index < methods()- >length(); ++index) {
m = methods()- >at(index); if (m- >method_idnum() == idnum) { return m;
}
} // None found, return null for the caller to handle.
return NULL;
} return m;
}
因此InstanceKlass里的methods是關鍵,而這個methods的創建是在類解析的時候發生的
instanceKlassHandle ClassFileParser::parseClassFile(Symbol* name,
ClassLoaderData* loader_data,
Handle protection_domain,
KlassHandle host_klass,
GrowableArray< Handle >* cp_patches,
TempNewSymbol& parsed_name,
bool verify,
TRAPS) {
...
Array< Method* >* methods = parse_methods(access_flags.is_interface(),
&promoted_flags,
&has_final_method,
&declares_default_methods,
... CHECK_(nullHandle));// sort methodsintArray* method_ordering = sort_methods(methods);
...
this_klass- >set_methods(_methods);
...
}
上面的parse_methods
就是從class文件里挨個解析出method,并存到_methods字段里,但是接下來做了一次sort_methods
的動作,這個動作會對解析出來的方法做排序
intArray* ClassFileParser::sort_methods(Array< Method* >* methods) { int length = methods- >length(); // If JVMTI original method ordering or sharing is enabled we have to
// remember the original class file ordering.
// We temporarily use the vtable_index field in the Method* to store the
// class file index, so we can read in after calling qsort.
// Put the method ordering in the shared archive.
if (JvmtiExport::can_maintain_original_method_order() || DumpSharedSpaces) { for (int index = 0; index < length; index++) {
Method* m = methods- >at(index); assert(!m- >valid_vtable_index(), "vtable index should not be set");
m- >set_vtable_index(index);
}
} // Sort method array by ascending method name (for faster lookups & vtable construction)
// Note that the ordering is not alphabetical, see Symbol::fast_compare
Method::sort_methods(methods);
intArray* method_ordering = NULL; // If JVMTI original method ordering or sharing is enabled construct int
// array remembering the original ordering
if (JvmtiExport::can_maintain_original_method_order() || DumpSharedSpaces) {
method_ordering = new intArray(length); for (int index = 0; index < length; index++) {
Method* m = methods- >at(index); int old_index = m- >vtable_index(); assert(old_index >= 0 && old_index < length, "invalid method index");
method_ordering- >at_put(index, old_index);
m- >set_vtable_index(Method::invalid_vtable_index);
}
} return method_ordering;
}// This is only done during class loading, so it is OK to assume method_idnum matches the methods() array// default_methods also uses this without the ordering for fast find_methodvoid Method::sort_methods(Array< Method* >* methods, bool idempotent, bool set_idnums) { int length = methods- >length(); if (length > 1) {
{
No_Safepoint_Verifier nsv;
QuickSort::sort< Method* >(methods- >data(), length, method_comparator, idempotent);
} // Reset method ordering
if (set_idnums) { for (int i = 0; i < length; i++) {
Method* m = methods- >at(i);
m- >set_method_idnum(i);
m- >set_orig_method_idnum(i);
}
}
}
}
從上面的Method::sort_methods
可以看出其實具體的排序算法是method_comparator
// Comparer for sorting an object array containing// Method*s.static int method_comparator(Method* a, Method* b) { return a- >name()- >fast_compare(b- >name());
}
比較的是兩個方法的名字,但是這個名字不是一個字符串,而是一個Symbol對象,每個類或者方法名字都會對應一個Symbol對象,在這個名字第一次使用的時候構建,并且不是在java heap里分配的,比如jdk7里就是在c heap里通過malloc來分配的,jdk8里會在metaspace里分配
// Note: this comparison is used for vtable sorting only; it doesn't matter// what order it defines, as long as it is a total, time-invariant order// Since Symbol*s are in C_HEAP, their relative order in memory never changes,// so use address comparison for speedint Symbol::fast_compare(Symbol* other) const { return (((uintptr_t)this < (uintptr_t)other) ? -1
: ((uintptr_t)this == (uintptr_t) other) ? 0 : 1);
}
從上面的fast_compare方法知道,其實對比的是地址的大小,因為Symbol對象是通過malloc來分配的,因此新分配的Symbol對象的地址就不一定比后分配的Symbol對象地址小,也不一定大,因為期間存在內存free的動作,那地址是不會一直線性變化的,之所以不按照字母排序,主要還是為了速度考慮,根據地址排序是最快的。
綜上所述,一個類里的方法經過排序之后,順序可能會不一樣,取決于方法名對應的Symbol對象的地址的先后順序
JVM為什么要對方法排序
其實這個問題很簡單,就是為了快速找到方法呢,當我們要找某個名字的方法的時候,根據對應的Symbol對象,能根據對象的地址使用二分排序的算法快速定位到具體的方法。
-
JVM
+關注
關注
0文章
158瀏覽量
12252 -
Symbol
+關注
關注
1文章
7瀏覽量
9194
發布評論請先 登錄
相關推薦
評論