[zz]Java中的instanceof關鍵字

1.What is the 'instanceof' operator used for?

stackoverflow的一個回答:http://stackoverflow.com/questions/7313559/what-is-the-instanceof-operator-used-forhtml

instanceof keyword is a binary operator used to test if an object (instance) is a subtype of a given Type.java

Imagine:react

interface Domestic {} class Animal {} class Dog extends Animal implements Domestic {} class Cat extends Animal implements Domestic {}

Imagine a dog object, created with Object dog = new Dog(), then:git

dog instanceof Domestic // true - Dog implements Domestic dog instanceof Animal // true - Dog extends Animal dog instanceof Dog // true - Dog is Dog dog instanceof Object // true - Object is the parent type of all objects

However,github

animal instanceof Dog // false

because Animal is a supertype of Dog and possibly less "refined".面試

And,算法

dog instanceof Cat // does not even compile!

This is because Dog is neither a subtype nor a supertype of Cat, and it also does not implement it.express

Note that the variable used for dog above is of type Object. This is to show instanceof is a runtime operation and brings us to a/the use case: to react differently based upon an objects type at runtime.api

Things to note: expressionThatIsNull instanceof T is false for all Types T.數組

2.Java instanceof 關鍵字是如何實現的?

知乎上的一個回答(備份):http://www.zhihu.com/question/21574535

---------------------------------------------------------------------
情形1:
你 在面月薪3000如下的Java碼農職位。若是面試官也只是作作Java層開發的,他可能只是想讓你回答Java語言層面的 instanceof 運算符的語義。Java語言的「意思」就已是「底層」。這樣的話只要參考Java語言規範對 instanceof 運算符的定義就好:
15.20.2 Type Comparison Operator instanceof, Java語言規範Java SE 7版
固然這實際上回答的不是「如何實現的」,而是「如何設計的」。但面試嘛⋯
若是用Java的僞代碼來表現Java語言規範所描述的運行時語義,會是這樣:

// obj instanceof T boolean result; try { T temp = (T) obj; // checkcast result = true; } catch (ClassCastException e) { result = false; } 


用中文說就是:若是有表達式 obj instanceof T ,那麼若是 (T) obj 不拋 ClassCastException 異常則該表達式值爲 true ,不然值爲 false 。
注意這裏徹底沒提到JVM啊Class對象啊啥的。另外要注意 instanceof 運算符除了運行時語義外還有部分編譯時限制,詳細參考規範。
若是這樣回答被面試官說「這不是廢話嘛」,請見情形2。

---------------------------------------------------------------------

情形2:
你 在面月薪6000-8000的Java研發職位。面試官也知道JVM這麼個大致概念,但知道的也很少。JVM這個概念自己就是「底層」。JVM有一條名爲 instanceof 的指令,而Java源碼編譯到Class文件時會把Java語言中的 instanceof 運算符映射到JVM的 instanceof 指令上。

你能夠知道Java的源碼編譯器之一javac是這樣作的:

  1. instanceof 是javac能識別的一個關鍵字,對應到Token.INSTANCEOF的token類型。作詞法分析的時候掃描到"instanceof"關鍵字就映射到了一個Token.INSTANCEOF token。jdk7u/jdk7u/langtools: 5c9759e0d341 src/share/classes/com/sun/tools/javac/parser/Token.java
  2. 該編譯器的抽象語法樹節點有一個JCTree.JCInstanceOf類用於表示instanceof運算。作語法分析的時候解析到instanceof運算符就會生成這個JCTree.JCInstanceof類型的節點。jdk7u/jdk7u/langtools: 5c9759e0d341 src/share/classes/com/sun/tools/javac/parser/JavacParser.java term2Rest()
  3. 中途還得根據Java語言規範對instanceof運算符的編譯時檢查的規定把有問題的狀況找出來。
  4. 到最後生成字節碼的時候爲JCTree.JCInstanceof節點生成instanceof字節碼指令。jdk7u/jdk7u/langtools: 5c9759e0d341 src/share/classes/com/sun/tools/javac/jvm/Gen.java visitTypeTest()


(Java語言君說:「instanceof 這問題直接交給JVM君啦」)
(面試官:你還給我廢話⋯給我進情形3!)

其實能回答到這層面就已經能解決好些實際問題了,例如說須要手工經過字節碼加強來實現一些功能的話,知道JVM有這麼條 instanceof 指令或許正好就能讓你順利的使用 ASM 之類的庫完成工做。

---------------------------------------------------------------------

情形3:
你 在面月薪10000的Java高級研發職位。面試官對JVM有一些瞭解,想讓你說說JVM會如何實現 instanceof 指令。但他可能也沒看過實際的JVM是怎麼作的,只是臆想過一下而已。JVM的規定就是「底層」。這種狀況就給他JVM規範對 instanceof 指令的定義就好:
Chapter 6. The Java Virtual Machine Instruction Set, JVM規範Java SE 7版
根據規範來臆想一下實現就能八九不離十的混過這題了。

該層面的答案就照@敖琪前面給出的就差很少了,這邊再也不重複。

---------------------------------------------------------------------

情形4:
你 可能在面真的簡易JVM的研發職位,或許是啥嵌入式JVM的實現。面試官會但願你對簡易JVM的實現有所瞭解。JVM的直觀實現就是「底層」。這個基本上 跟情形3差很少,由於簡易JVM一般會用很直觀的方式去實現。但對具體VM實現得答對一些小細節,例如說這個JVM是如何管理類型信息的。

這個情形的話下面舉點例子來說講。

---------------------------------------------------------------------

情形5:
你在面試月薪10000以上的Java資深研發職位,注重性能調優啥的。這種職位雖然不直接涉及JVM的研發,但因爲性能問題常常源自「抽象泄漏」,對實際使用的JVM的實現的思路須要有所瞭解。面試官對JVM的瞭解可能也就在此程度。對付這個能夠用一篇論文:Fast subtype checking in the HotSpot JVM。以前有個討論帖裏討論過對這篇論文的解讀:請教一個share/vm/oops下的代碼作fast subtype check的問題

---------------------------------------------------------------------

情形6:
你在面試真的高性能JVM的研發職位,例如 HotSpot VM 的研發。JVM在實際桌面或服務器環境中的具體實現是「底層」。呵呵這要回答起來就複雜了,必須回答出JVM實現中可能作的優化具體的實現。另外找地方詳細寫。

---------------------------------------------------------------------

我以爲會問這種問題的仍是情形1和2的比例比較大,換句話說面試官也不知道真的JVM是如何實現這instanceof指令的,可能甚至連這指令的準確語義都沒法描述對。那隨便忽悠忽悠就好啦不用太認真。說不定他期待的答案自己就霧很大(逃

碰上情形四、6的話,沒有忽悠的餘地,是怎樣就得怎樣。
情形5可能還稍微有點忽悠餘地呃呵呵。

==============================================================

看倆實際存在的簡易JVM的實現,Kaffe和JamVM。它們都以解釋器爲主,JIT的實現很是簡單,主要功能仍是在VM runtime裏實現,因此方便考察。
主要考察的是:它們中Java對象的基本結構(如何找到類型信息),類型信息自身如何記錄(內部用的類型信息與Java層的java.lang.Class對象的關係),以及instanceof具體是怎樣實現的。

---------------------------------------------------------------------

Kaffe


Kaffe中Java對象由Hjava_lang_Object結構體表示,裏面有個struct _dispatchTable*類型的字段vtable,下面再說。


Java 層的java.lang.Class實例在VM裏由Hjava_lang_Class結構體表示。Kaffe直接使用Hjava_lang_Class來 記錄VM內部的類型信息。也就是說在Kaffe上運行的Java程序裏持有的java.lang.Class的實例就是該JVM內部存類型信息的對象。
前面提到的_dispatchTable結構體也在該文件裏定義。它是一個虛方法分派表,主要用於高效實現invokevirtual。
假若有Hjava_lang_Object* obj,要找到它對應的類型信息只要這樣:

obj->vtable->class 



instanceof的功能由soft.c的soft_instanceof()函數實現。該函數所調用的函數大部分都在這個文件裏。


這邊定義了softcall_instanceof宏用於在解釋器或者JIT編譯後的代碼裏調用soft_instanceof()函數


這邊定義了instanceof字節碼指令的處理要調用softcall_instanceof宏

---------------------------------------------------------------------

JamVM


JamVM 中Java對象由Object結構體表示,Java層的java.lang.Class實例在VM裏由Class表示(是個空Object),VM內部記 錄的類信息由ClassBlock結構體表示(類型名、成員、父類、實現的接口、類價值器啥的都記錄在ClassBlock裏)。比較特別的是每一個 Class與對應的ClassBlock其實是粘在一塊兒分配的,因此Class*與ClassBlock*能夠很直接的相互轉換。例如說若是有 Class* c想拿到它對應的ClassBlock,只要:

ClassBlock* cb = CLASS_CB(c); 

便可。
Object結構體裏有Class*類型的成員class,用於記錄對象的類型。


instanceof的功能由cast.c第68行的isInstanceOf()函數實現。該函數所調用的函數大部分都在這個文件裏。

解 釋器主循環的代碼主要在interp.c裏。把instanceof指令的參數所指定的常量池索引解析爲實際類指針的邏輯在OPC_INSTANCEOF 的實現裏。JamVM作了個優化,在解析好類以後會把instanceof字節碼改寫爲內部字節碼instanceof_quick;調用 isInstanceOf()的地方在2161行OPC_INSTANCEOF_QUICK的實現裏,能夠看到它調用的是 isInstanceOf(class, obj->class)。

---------------------------------------------------------------------

上面介紹了Kaffe與JamVM裏instanceof字節碼的實現相關的代碼在哪裏。接下來簡單分析下它們的實現策略。

二者的實現策略其實幾乎同樣,基本上按照下面的步驟:
(假設要檢查的對象引用是obj,目標的類型對象是T)

  1. obj若是爲null,則返回false;不然設S爲obj的類型對象,剩下的問題就是檢查S是否爲T的子類型
  2. 若是S == T,則返回true;
  3. 接 下來分爲3種狀況,S是數組類型、接口類型或者類類型。之因此要分狀況是由於instanceof要作的是「子類型檢查」,而Java語言的類型系統裏數 組類型、接口類型與普通類類型三者的子類型規定都不同,必須分開來討論。到這裏雖然例中兩個JVM的具體實現有點區別,但概念上都與JVM規範所描述的 instanceof的基本算法 幾乎同樣。其中一個細節是:對接口類型的instanceof就直接遍歷S裏記錄的它所實現的接口,看有沒有跟T一致的;而對類類型的 instanceof則是遍歷S的super鏈(繼承鏈)一直到Object,看有沒有跟T一致的。遍歷類的super鏈意味着這個算法的性能會受類的繼 承深度的影響。


關於Java語言裏子類型關係的定義,請參考:Chapter 4. Types, Values, and Variables
類類型和接口類型的子類型關係你們可能比較熟悉,而數組類型的子類型關係可能會讓你們有點意外。

4.10.3. Subtyping among Array Types

The following rules define the direct supertype relation among array types:


  • If S and T are both reference types, then S[] >1 T[] iff S >1 T.

  • Object >1 Object[]

  • Cloneable >1 Object[]

  • java.io.Serializable >1 Object[]

  • If P is a primitive type, then:


    • Object >1 P[]

    • Cloneable >1 P[]

    • java.io.Serializable >1 P[]

這裏稍微舉幾個例子。如下子類型關係都成立(「<:」符號表示左邊是右邊的子類型,「=>」符號表示「推導出」):

  1. String[][][] <: String[][][] (數組子類型關係的自反性)
  2. String <: CharSequence => String[] <: CharSequence[] (數組的協變)
  3. String[][][] <: Object (全部數組類型是Object的子類型)
  4. int[] <: Serializable (原始類型數組實現java.io.Serializable接口)
  5. Object[] <: Serializable (引用類型數組實現java.io.Serializable接口)
  6. int[][][] <: Serializable[][] <: Serializable[] <: Serializable (上面幾個例子的延伸⋯開始好玩了吧?)
  7. int[][][] <: Object[][] <: Object[] <: Object

好玩不?實際JVM在記錄類型信息的時候必須想辦法把這些相關類型都串起來以便查找。

另外補充一點:樓主可能會以爲很困惑爲啥說到這裏隻字未提ClassLoader——由於在這個問題裏還輪不到它出場。
在 一個JVM實例裏,"(類型的全限定名, defining class loader)"這個二元組才能夠惟一肯定一個類。若是有兩個類全限定名相同,也加載自同一個Class文件,但defining class loader不一樣,從VM的角度看它們就是倆不一樣的類,並且相互沒有子類型關係。instanceof運算符只關心「是否知足子類型關係」,至於類型名是 否相同之類的不須要關心。

經過Kaffe與JamVM兩個例子咱們能夠看到簡單的JVM實現不少地方就是把JVM規範直觀的實現了出來。這就解決了前面提到的情形4的需求。

==============================================================

至於情形五、6,細節講解起來稍麻煩因此這裏不想展開寫。高性能的JVM跟簡易JVM在細節上徹底不是一回事。

簡單來講,優化的主要思路就是把Java語言的特色考慮進來:因爲Java的類所繼承的超類與所實現的接口都不會在運行時改變,整個繼承結構是穩定的,某個類型C在繼承結構裏的「深度」是固定不變的。也就是說從某個類出發遍歷它的super鏈,老是會遍歷到不變的內容。
這樣咱們就能夠把本來要循環遍歷super鏈才能夠找到的信息緩存在數組裏,而且以特定的下標從這個數組找到咱們要的信息。同時,Java的類繼承深度一般不會很深,因此爲這個緩存數組選定一個固定的長度就足以優化大部分須要作子類型判斷的狀況。

HotSpot VM具體使用了長度爲8的緩存數組,記錄某個類從繼承深度0到7的超類。HotSpot把類繼承深度在7之內的超類叫作「主要超類型」(primary super),把全部其它超類型(接口、數組相關以及超過深度7的超類)叫作「次要超類型」(secondary super)。
對「主要超類型」的子類型判斷不須要像Kaffe或JamVM那樣沿着super鏈作遍歷,而是直接就能判斷子類型關係是否成立。這樣,類的繼承深度對HotSpot VM作子類型判斷的性能影響就變得很小了。
對 「次要超類型」,則是讓每一個類型把本身的「次要超類型」混在一塊兒記錄在一個數組裏,要檢查的時候就線性遍歷這個數組。留意到這裏把接口類型、數組類型之類 的子類型關係都直接記錄在同一個數組裏了,只要在最初初始化secondary_supers數組時就分狀況填好了,而不用像Kaffe、JamVM那樣 每次作instanceof運算時都分開處理這些狀況。

舉例來講,若是有下述類繼承關係:
Apple <: Fruit <: Plant <: Object
而且以Object爲繼承深度0,那麼對於Apple類來講,它的主要超類型就有:
0: Object
1: Plant
2: Fruit
3: Apple
這個信息就直接記錄在Apple類的primary_supers數組裏了。Fruit、Plant等類同理。

若是咱們有這樣的代碼:

Object f = new Apple(); boolean result = f instanceof Plant; 


也就是變量f實際指向一個Apple實例,而咱們要問這個對象是不是Plant的實例。
能夠知道f的實際類型是Apple;要測試的Plant類的繼承深度是1,拿Apple類裏繼承深度爲1的主要超類型來看是Plant,立刻就能得出結論是true。
這樣就不須要順着Apple的繼承鏈遍歷過去一個個去看是否跟Plant相等了。

對此感興趣的同窗請參考前面在情形5提到的兩個連接。先讀第一個連接那篇論文,而後看第二個連接裏的討論(沒有ACM賬號沒法從第一個連接下載到論文的同窗能夠在第二個連接裏找到一個鏡像)。

JDK6至今的HotSpot VM實際採用的算法是:

S.is_subtype_of(T) := {
  int off = T.offset;
  if (S == T) return true;
  if (T == S[off]) return true;
  if (off != &cache) return false;
  if ( S.scan_secondary_subtype_array(T) ) {
    S.cache = T;
    return true;
  }
  return false;
}

(具體是什麼意思請務必參考論文)

這邊想特別強調的一點是:那篇論文描述了HotSpot VM作子類型判斷的算法,但其實只有HotSpot VM的解釋器以及 java.lang.Class.isInstance() 的調用是真的完整按照那個算法來執行的。HotSpot VM的兩個編譯器,Client Compiler (C1) 與 Server Compiler (C2) 各自對子類型判斷的實現有更進一步的優化。實際上在這個JVM裏,instanceof的功能就實現了4份,VM runtime、解釋器、C一、C2各一份。

VM runtime的:
jdk7u/jdk7u/hotspot: e087a2088970 src/share/vm/oops/oop.inline.hpp oopDesc::is_a()
jdk7u/jdk7u/hotspot: e087a2088970 src/share/vm/oops/klass.hpp is_subtype_of()
jdk7u/jdk7u/hotspot: e087a2088970 src/share/vm/oops/klass.cpp Klass::search_secondary_supers()

inline bool oopDesc::is_a(klassOop k) const { return blueprint()->is_subtype_of(k); } 

 

bool is_subtype_of(klassOop k) const { juint off = k->klass_part()->super_check_offset(); klassOop sup = *(klassOop*)( (address)as_klassOop() + off ); const juint secondary_offset = in_bytes(secondary_super_cache_offset()); if (sup == k) { return true; } else if (off != secondary_offset) { return false; } else { return search_secondary_supers(k); } } bool search_secondary_supers(klassOop k) const; 

 

bool Klass::search_secondary_supers(klassOop k) const { // Put some extra logic here out-of-line, before the search proper. // This cuts down the size of the inline method. // This is necessary, since I am never in my own secondary_super list. if (this->as_klassOop() == k) return true; // Scan the array-of-objects for a match int cnt = secondary_supers()->length(); for (int i = 0; i < cnt; i++) { if (secondary_supers()->obj_at(i) == k) { ((Klass*)this)->set_secondary_super_cache(k); return true; } } return false; } 


解釋器的(以x86-64的template interpreter爲例):
jdk7u/jdk7u/hotspot: e087a2088970 src/cpu/x86/vm/templateTable_x86_64.cpp TemplateTable::instanceof()
jdk7u/jdk7u/hotspot: e087a2088970 src/cpu/x86/vm/interp_masm_x86_64.cpp InterpreterMacroAssembler::gen_subtype_check()
(太長,不把代碼貼出來了。要看代碼請點上面連接)

C1和C2對instanceof的優化分散在好幾個地方,以C1爲例,
C1 把Java字節碼parse成HIR(High-level Intermediate Representation)的邏輯在GraphBuilder::iterate_bytecodes_for_block(),它先把 instanceof字節碼解析成了InstanceOf節點:
jdk7u/jdk7u/hotspot: e087a2088970 src/share/vm/c1/c1_GraphBuilder.cpp
而後在優化過程當中,InstanceOf節點會觀察它的對象參數是否爲常量null或者是固定的已知類型,並相應的嘗試作常量摺疊:
jdk7u/jdk7u/hotspot: e087a2088970 src/share/vm/c1/c1_Canonicalizer.cpp Canonicalizer::do_InstanceOf
若是已常常量摺疊了的話就沒後續步驟了。反之則繼續下去生成LIR:
jdk7u/jdk7u/hotspot: e087a2088970 src/cpu/x86/vm/c1_LIRGenerator_x86.cpp LIRGenerator::do_InstanceOf
jdk7u/jdk7u/hotspot: e087a2088970 src/share/vm/c1/c1_LIR.cpp LIR_List::instanceof
最後生成機器碼:
jdk7u/jdk7u/hotspot: e087a2088970 src/cpu/x86/vm/c1_LIRAssembler_x86.cpp LIR_Assembler::emit_opTypeCheck
jdk7u/jdk7u/hotspot: e087a2088970 src/cpu/x86/vm/c1_LIRAssembler_x86.cpp LIR_Assembler::emit_typecheck_helper
生成的機器碼邏輯跟解釋器版本基本上是同樣的,只是寄存器使用上稍微不一樣。

而在C2中,
最初處理instanceof字節碼生成C2的內部節點的邏輯主要在:
jdk7u/jdk7u/hotspot: e087a2088970 src/share/vm/opto/graphKit.cpp GraphKit::gen_instanceof()
它會調用 GraphKit::gen_subtype_check() 來生成檢查邏輯的主體,然後者會根據代碼的上下文所能推導出來的類型信息把類型檢查儘可能優化到更簡單的形式,甚至直接就得出結論。

對這部分細節感興趣的同窗請單獨聯繫我或者另外開帖討論吧。

下面兩個patch是我對HotSpot VM在子類型檢查相關方面作的小優化,兩個都在JDK7u40/JDK8裏發佈:

[#JDK-7170463] C2 should recognize "obj.getClass() == A.class" code pattern
Request for review (S): C2 should recognize "obj.getClass() == A.class" code pattern


[#JDK-7171890] C1: add Class.isInstance intrinsic
Request for review (M): 7171890: C1: add Class.isInstance intrinsic
hsx/hotspot-comp/hotspot: 8f37087fc13f
lambda/lambda/hotspot: e1635876b206

舉個例子,通過JDK-7170463的patch以後,HotSpot VM的C2會把下面這樣的代碼:

if (obj.getClass() == A.class) { boolean isInst = obj instanceof A; } 

優化爲:

if (obj.getClass() == A.class) { boolean isInst = true; } 

那個instanceof運算就直接被常量摺疊掉了。樓主能夠看看,當時面試你的面試官是否瞭解到這種細節了,而他又是否真的要在面試種考察這種細節。

==============================================================

樓主的問題本來有提到 BytecodeInstanceOf.java 。它是 Serviceability Agent 的一部分,不是 HotSpot VM 內的邏輯。關於 Serviceability Agent 請從這帖裏的連接找資料來讀讀:記GreenTeaJUG第二次線下活動(杭州)

SA 是HotSpot VM自帶的一個用來調試、診斷HotSpot VM運行狀態的工具。它是一個「進程外」條調試器,也就是說假如咱們要調試的HotSpot VM運行在進程A裏,那麼SA要運行在另外一個進程B裏去調試進程A。這樣作的好處是SA與被調試進程不會相互干擾,因而調試就能夠更乾淨的進行;就算SA 本身崩潰了也(一般)不會連帶把被調試進程也弄崩潰。

SA在HotSpot VM內部的C++代碼裏嵌有一小塊,主要是把HotSpot的C++類的符號信息記錄下來;SA的主體則是用Java來實現的,把可調試的HotSpot 裏C++的類用Java再作一層皮。樓主看到的BytecodeInstanceOf類就是這樣的一層皮,它並不包含HotSpot的執行邏輯,純粹是爲 調試用的。

直接放些外部參考資料連接方便你們找:

The HotSpot™ Serviceability Agent: An Out-of-Process High-Level Debugger for a Java™ Virtual Machine, USENIX JVM '01
這篇是描述 HotSpot Serviceability Agent 的原始論文,要了解 SA 的背景必讀。

HotSpot source: Serviceability Agent (A. Sundararajan's Weblog)
提到了hotspot/agent目錄裏的代碼都是 Serviceability Agent 的實現。注意 SA 並非 HotSpot VM 運行時必要的組成部分。

Serviceability in HotSpot, OpenJDK這是OpenJDK館網上的相關文檔頁面。

相關文章
相關標籤/搜索