[转载]Class文件在JVM中如何存储

JDK6 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.CharSequence  
  Signature: #405                         // Ljava/lang/Object;Ljava/io/Serializable;Ljava/lang/Comparable;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."":()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."":(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."":(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内部组织这些元数据的方式的差异。

原文地址: https://hllvm-group.iteye.com/group/topic/26412#post-187861

你可能感兴趣的:([转载]Class文件在JVM中如何存储)