[讨论] 请问,jvm实现读取class文件常量池信息是怎样呢?
all_wmh
2011-06-19
在看class文件结构的时候,class文件中有一个常量池,我想问下 JVM实现 读取class文件常量池信息的时候,是不是根据class文件的常量池的tag信息分别读取到各个类型的常量表(比如CONSTANT_Utf8_info、CONSTANT_Class_info等等)中呢?
|
|
RednaxelaFX
2011-06-20
具体实现么…
================================================ HotSpot VM的话,当前的版本是这样的: HotSpot VM用instanceKlass来记录类的元数据,每个Java类有一个对应的instanceKlass。 每个instanceKlass上引用着一个constantPoolOopDesc对象,然后间接引用着一个constantPoolCacheOopDesc对象。前者跟Class文件里记录的常量池的结构类似,而后者是为了让解释器运行得更高效的一个缓存。 举例的话,用VisualVM里的SA Plugin来演示,java.lang.String的状况。 这里我用JDK 7的一个预览版,build 96来运行VisualVM 1.3和一个groovysh,并且用VisualVM里的SA Plugin来观察groovysh的运行状态: 图1:java.lang.String对应的一个instanceKlass 留意到instanceKlass里有个_constants字段,引用着一个constantPoolOopDesc对象(后面简称constantPool对象)。 图2:观察constantPool对象的内容: 留意到它是一个类似数组的对象,里面有_length字段描述常量池内容的个数,后面就是常量池项了。 各个类型的常量是混在一起放在常量池里的,跟Class文件里的基本上一样。 最不同的是在这个运行时常量池里,symbol是在类之间共享的;而在Class文件的常量池里每个Class文件都有自己的一份symbol内容,没共享。 图3:观察constantPool里其中一个Utf8常量的内容: 这张图的关注点是位于0x180188a8的一个symbol对象(内容是"intern"),它的结构跟数组类似,有_length来记录长度,后面是UTF-8编码的字节。 这些Utf8常量在HotSpot VM里以symbolOopDesc对象(下面简称symbol对象)来表现;它们可以通过一个全局的SymbolTable对象找到。注意:constantPool对象并不“包含”这些symbol对象,而只是引用着它们而已;或者说,constantPool对象只存了对symbol对象的引用,而没有存它们的内容。 让我们来看看原本的Class文件里内容是怎样的: 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看看,是不是正好对应上的? 图2里constantPool的第一个常量池项的内容是: JVM_CONSTANT_Methodref: 26738818 这个26738818数字是怎么来的呢? 实际上是:26738818 = 408 << 16 | 130 而原本Class文件里常量池的第一项内容正是#130.#408,也就是由一个Class_index和一个NameAndType_index组成的Methodref。 图2里还有个细节,可以看到原本Class文件里常量池第7项是一个Class,但在图2里显示的是一个“UnresolvedClass”。这正是动态类加载/链接的一个表现。这个项所指向的Class还没被String里的方法使用过,所以还没跟String链接起来,所以这里看到是unresolved。 我们可以故意在那个groovysh里执行一句: 'abc'.charAt(5) 这样会引发String.charAt()方法执行的过程中抛出一个java.lang.StringIndexOutOfBoundsException异常,那么就必须要完成链接的步骤。 然后再去看看String的常量池的样子: 就可以看到常量池的第7项已经解析(resolve)好了,从原本的符号引用变成了一个直接引用。 ================================================ 在JDK7以后的更新版中,HotSpot VM会逐渐去除PermGen,原本一些放在GC堆里的元数据会搬到GC管理之外的堆空间里。所以上面描述的实现会有些变化。具体会变成怎样还没真相。 ================================================ 至于其它JVM,其实运行时常量池想怎么组织都可以的,反正Java层面上看不出来JVM内部组织这些元数据的方式的差异。 |
|
xuhang1128
2011-06-29
这个visualvm是自带的吗,怎么功能好像强大很多啊
|
|
RednaxelaFX
2011-06-29
xuhang1128 写道 这个visualvm是自带的吗,怎么功能好像强大很多啊
虽然我用来截图的这个确实不是JDK自带的那个,但其实功能一样,主要是要多装些插件。上面的截图都是VisualVM的SA Plugin插件的 |
|
all_wmh
2011-06-30
引用 这个26738818数字是怎么来的呢? 实际上是:26738818 = 408 << 16 | 130 撒迦能否解释的更具体点呢? 还是没看明白。 |
|
RednaxelaFX
2011-06-30
all_wmh 写道 引用 这个26738818数字是怎么来的呢?
实际上是:26738818 = 408 << 16 | 130 撒迦能否解释的更具体点呢? 还是没看明白。 嗯,好啊。 先看JVM规范里Class文件格式中,CONSTANT_Field_info的结构: JVMS2 写道 CONSTANT_Fieldref_info { u1 tag; u2 class_index; u2 name_and_type_index; } 可以看到这个常量池项的结构就是一个字节的tag(= CONSTANT_Fieldref (9)),后面跟着两个字节的class_index,再跟着两个字节的name_and_type_index。 javap在显示常量池项的时候,这例子对应的项是: #1 = Methodref #130.#408 // java/lang/Object."<init>":()V 实际上在Class文件里的样子是(十六进制): 09 00 82 01 98 也就是: CONSTANT_Fieldref_info { u1 tag = 0x09; // CONSTANT_Fieldref u2 class_index = 0x0082; // 130 u2 name_and_type_index = 0x0198; // 408 } HotSpot VM加载了这个Class文件后,在constantPool对象里也存下了对应的内容。用SA Plugin我们可以看到显示的是: JVM_CONSTANT_Methodref: 26738818 实际上结构跟Class文件里原本的样子是一样的,也是有个tag来区分常量项的类型,后面指向别的常量项的索引号。 只不过SA Plugin在显示常量项内容的时候,把class_index与name_and_type_index的存放顺序跟Class文件里的正好反了过来组合成一个整数显示了出来而已。实际上26738818 = 408 << 16 | 130,也就是26738818作为一个4字节的整数,其中两字节是name_and_type_index,另外两字节是class_index,就这样。 |
|
all_wmh
2011-06-30
感谢撒迦的细心指导,非常感谢。
|