JDK6 HotSpot VM用instanceKlass來記錄類的元數(shù)據(jù),每個Java類有一個對應(yīng)的instanceKlass橘券。
每個instanceKlass上引用著一個constantPoolOopDesc對象沧卢,然后間接引用著一個constantPoolCacheOopDesc對象。前者跟Class文件里記錄的常量池的結(jié)構(gòu)類似像吻,而后者是為了讓解釋器運行得更高效的一個緩存堕汞。
舉例的話勺爱,用VisualVM里的SA Plugin來演示,java.lang.String的狀況讯检。
這里我用JDK 7的一個預(yù)覽版琐鲁,build 96來運行VisualVM 1.3和一個groovysh,并且用VisualVM里的SA Plugin來觀察groovysh的運行狀態(tài):
圖1:java.lang.String對應(yīng)的一個instanceKlass
留意到instanceKlass里有個_constants字段人灼,引用著一個constantPoolOopDesc對象(后面簡稱constantPool對象)围段。
圖2:觀察constantPool對象的內(nèi)容:
留意到它是一個類似數(shù)組的對象,里面有_length字段描述常量池內(nèi)容的個數(shù)投放,后面就是常量池項了奈泪。
各個類型的常量是混在一起放在常量池里的,跟Class文件里的基本上一樣灸芳。
最不同的是在這個運行時常量池里涝桅,symbol是在類之間共享的;而在Class文件的常量池里每個Class文件都有自己的一份symbol內(nèi)容烙样,沒共享冯遂。
圖3:觀察constantPool里其中一個Utf8常量的內(nèi)容:
這張圖的關(guān)注點是位于0x180188a8的一個symbol對象(內(nèi)容是"intern"),它的結(jié)構(gòu)跟數(shù)組類似谒获,有_length來記錄長度蛤肌,后面是UTF-8編碼的字節(jié)壁却。
這些Utf8常量在HotSpot VM里以symbolOopDesc對象(下面簡稱symbol對象)來表現(xiàn);它們可以通過一個全局的SymbolTable對象找到裸准。注意:constantPool對象并不“包含”這些symbol對象展东,而只是引用著它們而已;或者說炒俱,constantPool對象只存了對symbol對象的引用琅锻,而沒有存它們的內(nèi)容。
讓我們來看看原本的Class文件里內(nèi)容是怎樣的:
D:\temp\jdk7b96\jdk1.7.0\fastdebug\bin>javap -verbose -private java.lang.String | more
Classfile jar:file:/D:/temp/jdk7b96/jdk1.7.0/fastdebug/jre/lib/rt.jar!/java/lang/String.class
Last modified 2010-6-3; size 23741 bytes
MD5 checksum 293ab9f6781f6cd7d8f1dcaeabf1701c
Compiled from "String.java"
public final class java.lang.String extends java.lang.Object implements java.io.
Serializable, java.lang.Comparable<java.lang.String>, java.lang.CharSequence
Signature: #405 // Ljava/lang/Object;Ljava/io/Serializable;Ljava/lang/Comparable<Ljava/lang/String;>;Ljava/lang/CharSequence;
SourceFile: "String.java"
InnerClasses:
static #134 of #40; //class java/lang/String$1 of class java/lang/String
private static #137= #128 of #40; //CaseInsensitiveComparator=class java/lang/String$CaseInsensitiveComparator of class java/lang/String
minor version: 0
major version: 51
flags: ACC_PUBLIC, ACC_FINAL, ACC_SUPER
Constant pool:
#1 = Methodref #130.#408 // java/lang/Object."<init>":()V
#2 = Fieldref #40.#409 // java/lang/String.offset:I
#3 = Fieldref #40.#410 // java/lang/String.count:I
#4 = Fieldref #40.#411 // java/lang/String.value:[C
#5 = Methodref #412.#413 // java/util/Arrays.copyOfRange:([CII)[C
#6 = Methodref #412.#414 // java/util/Arrays.copyOf:([CI)[C
#7 = Class #415 // java/lang/StringIndexOutOfBoundsException
#8 = Methodref #7.#416 // java/lang/StringIndexOutOfBoundsException."<init>":(I)V
#9 = Integer 65536
#10 = Methodref #417.#418 // java/lang/Character.isSupplementaryCodePoint:(I)Z
#11 = Class #419 // java/lang/IllegalArgumentException
#12 = Methodref #420.#421 // java/lang/Integer.toString:(I)Ljava/lang/String;
#13 = Methodref #11.#422 // java/lang/IllegalArgumentException."<init>":(Ljava/lang/String;)V
再對比圖2看看向胡,是不是正好對應(yīng)上的?
圖2里constantPool的第一個常量池項的內(nèi)容是:
JVM_CONSTANT_Methodref: 26738818
這個26738818數(shù)字是怎么來的呢惊完?
實際上是:26738818 = 408 << 16 | 130
而原本Class文件里常量池的第一項內(nèi)容正是#130.#408僵芹,也就是由一個Class_index和一個NameAndType_index組成的Methodref。
圖2里還有個細(xì)節(jié)小槐,可以看到原本Class文件里常量池第7項是一個Class拇派,但在圖2里顯示的是一個“UnresolvedClass”。這正是動態(tài)類加載/鏈接的一個表現(xiàn)凿跳。這個項所指向的Class還沒被String里的方法使用過件豌,所以還沒跟String鏈接起來,所以這里看到是unresolved控嗜。
我們可以故意在那個groovysh里執(zhí)行一句:
'abc'.charAt(5)
這樣會引發(fā)String.charAt()方法執(zhí)行的過程中拋出一個java.lang.StringIndexOutOfBoundsException異常茧彤,那么就必須要完成鏈接的步驟。
然后再去看看String的常量池的樣子:
就可以看到常量池的第7項已經(jīng)解析(resolve)好了疆栏,從原本的符號引用變成了一個直接引用曾掂。
在JDK7以后的更新版中,HotSpot VM會逐漸去除PermGen壁顶,原本一些放在GC堆里的元數(shù)據(jù)會搬到GC管理之外的堆空間里珠洗。所以上面描述的實現(xiàn)會有些變化。具體會變成怎樣還沒真相若专。
至于其它JVM许蓖,其實運行時常量池想怎么組織都可以的,反正Java層面上看不出來JVM內(nèi)部組織這些元數(shù)據(jù)的方式的差異调衰。
原文地址: https://hllvm-group.iteye.com/group/topic/26412#post-187861