Ⅰ 常量池的具体结构
在java程序中,有很多的东西是永恒的,不会在运行过程中变化。比如一个类的名字,一个类字段的名字/所属类型,一个类方法的名字/返回类型/参数名与所属类型,一个常量,还有在程序中出现的大量的字面值。
比如下面小段源码中粗体代码显示的部分:
public class ClassTest {
private String itemS ="我们 ";
private final int itemI =100 ;
public void setItemS (String para ){...}
}
而这些在JVM解释执行程序的时候是非常重蚂余要的。那么编译器将源程序编译成class文件后,会用一部分字节分类存储这些粗体代码。而这些字节我们就称为常量池。事实上,只有JVM加载class后,在方法区中为它们开辟了空间才更像一个“池”。
正如上面所示,一个程序中有很多永恒的类似粗体代码显示的部分。每一个都是常量池中的一个常量表(常量项)。而这些常量表之间又有不同,class文件共有11种常量表,如下所示: 常量表类型 标志值(占1 byte) 描述 CONSTANT_Utf8 1 UTF-8编码的Unicode字符串 CONSTANT_Integer 3 int类型的字面值 CONSTANT_Float 4 float类型的字面值 CONSTANT_Long 5 long类型的字面值 CONSTANT_Double 6 double类型的字液弊面值 CONSTANT_Class 7 对一个类或接口的符号引用 CONSTANT_String 8 String类型字面值的引用 CONSTANT_Fieldref 9 对一个字段的符号引用 CONSTANT_Methodref 10 对一个类中方法的符号引用 CONSTANT_InterfaceMethodref 11 对一个接口中方法的符号引用 CONSTANT_NameAndType 12 对一个字段或方法的部分符号引用 (1) CONSTANT_Utf8 用UTF-8编码方式来表示程序中所有的重要常量字符串。这些字符串闷埋滚包括: ①类或接口的全限定名, ②超类的全限定名,③父接口的全限定名, ④类字段名和所属类型名,⑤类方法名和返回类型名、以及参数名和所属类型名。⑥字符串字面值
表格式: tag(标志1:占1byte) length(字符串所占字节的长度,占2byte) bytes(字符串字节序列)
(2) CONSTANT_Integer、 CONSTANT_Float、 CONSTANT_Long、 CONSTANT_Double 所有基本数据类型的字面值。比如在程序中出现的1用CONSTANT_Integer表示。3.1415926F用 CONSTANT_Float表示。
表格式: tag bytes(基本数据类型所需使用的字节序列)
(3) CONSTANT_Class 使用符号引用来表示类或接口。我们知道所有类名都以 CONSTANT_Utf8表的形式存储。但是我们并不知道 CONSTANT_Utf8表中哪些字符串是类名,那些是方法名。因此我们必须用一个指向类名字符串的符号引用常量来表明。
表格式: tag name_index(给出表示类或接口名的CONSTANT_Utf8表的索引)
(4) CONSTANT_String 同 CONSTANT_Class,指向包含字符串字面值的 CONSTANT_Utf8表。
表格式: tag string_index(给出表示字符串字面值的CONSTANT_Utf8表的索引)
(5) CONSTANT_Fieldref 、 CONSTANT_Methodref、 CONSTANT_InterfaceMethodref 指向包含该字段或方法所属类名的 CONSTANT_Utf8表,以及指向包含该字段或方法的名字和描述符的 CONSTANT_NameAndType 表
表格式: tag class _index(给出包含所属类名的CONSTANT_Utf8表的索引) name_and_type_index(包含字段名或方法名以及描述符的 CONSTANT_NameAndType表 的索引)
(6) CONSTANT_NameAndType 指向包含字段名或方法名以及描述符的 CONSTANT_Utf8表。
表格式: tag name_index(给出表示字段名或方法名的CONSTANT_Utf8表的索引) type_index(给出表示描述符的CONSTANT_Utf8表的索引)
在Java源代码中的每一个字面值字符串,都会在编译成class文件阶段,形成标志号为8(CONSTANT_String_info)的常量表 。 当JVM加载 class文件的时候,会为对应的常量池建立一个内存数据结构,并存放在方法区中。同时JVM会自动为CONSTANT_String_info常量表中的字符串常量的字面值 在堆中创建新的String对象(intern字符串对象 ,又叫拘留字符串对象)。然后把CONSTANT_String_info常量表的入口地址转变成这个堆中String对象的直接地址(常量池解析)。
拘留字符串对象
源代码中所有相同字面值的字符串常量只可能建立唯一 一个拘留字符串对象。 实际上JVM是通过一个记录了拘留字符串引用的内部数据结构来维持这一特性的。在Java程序中,可以调用String的intern()方法来使得一个常规字符串对象成为拘留字符串对象。
(1)String s=new String("Hello world"); 编译成class文件后的指令(在myeclipse中查看):
事实上,在运行这段指令之前,JVM就已经为"Hello world"在堆中创建了一个拘留字符串( 值得注意的是:如果源程序中还有一个"Hello world"字符串常量,那么他们都对应了同一个堆中的拘留字符串)。然后用这个拘留字符串的值来初始化堆中用new指令创建出来的新的String对象,局部变量s实际上存储的是new出来的堆对象地址。
(2)String s="Hello world";
这跟(1)中创建指令有很大的不同,此时局部变量s存储的是早已创建好的拘留字符串的堆地址。
java常量池技术 java中的常量池技术,是为了方便快捷地创建某些对象而出现的,当需要一个对象时,就可以从池中取一个出来(如果池中没有则创建一个),则在需要重复创建相等变量时节省了很多时间。常量池其实也就是一个内存空间,常量池存在于方法区中。
String类也是java中用得多的类,同样为了创建String对象的方便,也实现了常量池的技术。
测试代码如下:
public class Test{
public static void main(String[] args){
//s1,s2分别位于栈中,指向堆中不同的空间
String s1=new String("hello");
String s2=new String("hello");
System.out.println(s1==s2);//输出false
//s3,s4位于池中同一空间
String s3="hello" String s4="hello";
System.out.println(s3==s4);//输出true
}
}
用new String()创建的字符串不是常量,不能在编译期就确定,所以new String()创建的字符串不放入常量池中,他们有自己的地址空间。
String 对象(内存)的不变性机制会使修改String字符串时,产生大量的对象,因为每次改变字符串,都会生成一个新的String。 java 为了更有效的使用内存,常量池在编译期遇见String 字符串时,它会检查该池内是否已经存在相同的String 字符串,如果找到,就把新变量的引用指向现有的字符串对象,不创建任何新的String 常量对象,没找到再创建新的。所以对一个字符串对象的任何修改,都会产生一个新的字符串对象,原来的依然存在,等待垃圾回收。
代码:
String a = “test”;
String b = “test”;
String b = b+"java";
a,b同时指向常量池中的常量值"test",b=b+"java"之后,b原先指向一个常量,内容为"test”,通过对b进行+"java" 操作后,b之前所指向的那个值没有改变,但此时b不指向原来那个变量值了,而指向了另一个String变量,内容为”test java“。原来那个变量还存在于内存之中,只是b这个变量不再指向它了。
八种基本类型的包装类和对象池 java中基本类型的包装类的大部分都实现了常量池技术,这些类是Byte,Short,Integer,Long,Character,Boolean,另外两种浮点数类型的包装类则没有实现。另外Byte,Short,Integer,Long,Character这5种整型的包装类也只是在对应值小于等于127时才可使用常量池,也即对象不负责创建和管理大于127的这些类的对象。 一些对应的测试代码:
public class Test{ public static void main(String[] args){
//5种整形的包装类Byte,Short,Integer,Long,Character的对象,
//在值小于127时可以使用常量池
Integer i1=127;
Integer i2=127;
System.out.println(i1==i2); //输出true
//值大于127时,不会从常量池中取对象
Integer i3=128;
Integer i4=128;
System.out.println(i3==i4); //输出false
//Boolean类也实现了常量池技术
Boolean bool1=true;
Boolean bool2=true;
System.out.println(bool1==bool2); //输出true
//浮点类型的包装类没有实现常量池技术
Double d1=1.0;
Double d2=1.0;
System.out.println(d1==d2); //输出false
}
}
对Integer对象的代码补充
public static Integer valueOf(int i) {
final int offset = 128;
if (i >= -128 && i <= 127) {
return IntegerCache.cache[i + offset];
}
return new Integer(i);
}
当你直接给一个Integer对象一个int值的时候,其实它调用了valueOf方法,然后你赋的这个值很特别,是128,那么没有进行cache方法,相当于new了两个新对象。所以问题中定义a、b的两句代码就类似于:
Integer a = new Integer(128);
Integer b = new Integer(128);
这个时候再问你,输出结果是什么?你就知道是false了。如果把这个数换成127,再执行:
Integer a = 127;
Integer b = 127;
System.out.println(a == b);
结果就是:true
进行对象比较时最好还是使用equals,便于按照自己的目的进行控制。这里引出equals()和==,equals比较的是字符串字面值即比较内容,==比较引用。
看一下IntegerCache这个类里面的内容:
private static class IntegerCache {
private IntegerCache() {
}
static final Integer cache[] = new Integer[-(-128) + 127 + 1];
static {
for (int i = 0; i < cache.length; i++)
cache[i] = new Integer(i - 128);
}
}
由于cache[]在IntegerCache类中是静态数组,也就是只需要初始化一次,即static{......}部分,所以,如果Integer对象初始化时是-128~127的范围,就不需要再重新定义申请空间,都是同一个对象---在IntegerCache.cache中,这样可以在一定程度上提高效率。
针对String方面的补充
在同包同类下,引用自同一String对象.
在同包不同类下,引用自同一String对象.
在不同包不同类下,依然引用自同一String对象.
在编译成.class时能够识别为同一字符串的,自动优化成常量,所以也引用自同一String对象.
在运行时创建的字符串具有独立的内存地址,所以不引用自同一String对象.
String的intern()方法会查找在常量池中是否存在一份equal相等的字符串,
如果有则返回一个引用,没有则添加自己的字符串进入常量池,注意:只是字符串部分。
所以这时会存在2份拷贝,常量池的部分被String类私有并管理,自己的那份按对象生命周期继续使用。
返回字符串对象的规范化表示形式
一个初始值为空的字符串池,它由类 String 私有地维护。
当调用 intern 方法时,如果池已经包含一个等于此 String 对象的字符串(该对象由 equals(Object) 方法确定),则返回池中的字符串引用。否则,将此 String 对象添加到池中,并且返回此 String 对象的引用。
它遵循对于任何两个字符串 s 和 t,当且仅当 s.equals(t) 为 true 时,s.intern() == t.intern() 才为 true。
所有字面值字符串和字符串赋值常量表达式都是内部的。
------------------------------------代码演示补充-------------------------------------
String s0= "java";
String s1=new String("java");
String s2=new String("java");
s1.intern();
s2=s2.intern(); //把常量池中"java"的引用赋给s2
System.out.println( s0==s1);//false “ intern返回的引用没有引用变量接收~ s1.intern();等于废代码.”
System.out.println( s0==s1.intern() );//true
System.out.println( s0==s2 );//true
------------------------------------代码演示补充-------------------------------------
String s1=new String("java");
String s2=s1.intern();//s1 检查常量池,发现没有就拷贝自己的字符串进去
//s2 引用该字符串常量池的地址
System.out.println(s2 == s1);//false
System.out.println( s2==s1.intern());//true
System.out.println( s1==s1.intern());// false
Ⅱ 常量池是什么
可以理解为class文件之中的资源仓库,它是class文件结构中与其他项目关联最多的数据类型,也是占用class文件空间最大的数早芹高据项目之一,同时它还是class文件中第一个出现表类型的数据项目。
由于常量池的数量是不固定的,所以在常量池入口需要放置一项u2(即2个字节)类型的数据,代表常量池容量计数值(constant-pool-count)(从1开始,将0表示不引用任何常量)。
常量池中主要存放两大类常量:
● 字面量(Literal):比较接近首隐于Java语言层面的常量概念,如文本字符串,声明为final的常量值。
● 符号引用(Synbolic Reference):包括如下三类常陆尺量:
① 类和接口的全限定名(Fully Qualified Name)
② 字段的名称和描述符(Descriptor)
③ 方法的名称和描述符
Ⅲ 手机java虚拟机能运行.class文件吗
jre下也有bin\java.exe这个程序
java GreedSnake
这样就可以,如果你这个类有包的话就要麻烦一点。
要在包的顶层目录中执行。
java packages.packages.GreedSnake
这样
packages是你具体的包名。
Ⅳ [转载]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文件里内容是怎样的:
再对比图2看看,是不是正好对应上的?
图2里constantPool的第一个常量池项的内容是:
这个26738818数字是怎么来的呢?
实际上是:26738818 = 408 << 16 | 130
而原本Class文件里常量池的第一项内容正是#130.#408,也就是由一个Class_index和一个NameAndType_index组成的Methodref。
图2里还有个细节,可以看到原本Class文件里常量池第7项是一个Class,但在图2里显示的是一个“UnresolvedClass”。这正是动态类加载/链接的一个表现。这个项所指向的Class还没被String里的方法使用过,所以还没跟String链接起祥握漏来,所以这里看到是unresolved。
我们可以故意在那个groovysh里执行一句:
这样会引发String.charAt()方法执行的过程中抛出一个java.lang.异常,那么就必须要完成链接的步骤。
然后再去看看String的常量池的样子:
就可以看到常量池的第7项已经解析(resolve)好了,从原本的符号引用变成了一个直接引用。
在JDK7以后的更新版中,HotSpot VM会逐渐去除谨烂PermGen,原本一些放在GC堆里的元数据会搬到GC管理之外的堆空间里。所以上面描述的实现会有些变化。具体会变成怎样还没真相。
至于其它JVM,其实运行时常量池想怎么组织都可以的,反正Java层面上看不出来JVM内部组织这些元数据的方式的差异。
原文地址: https://hllvm-group.iteye.com/group/topic/26412#post-187861
Ⅳ JVM_字节码文件(ClassFile)详解
我们知道 javac 命令可以将 .java 文件编译成 .class 文件,而这个 Class 文件 中包含了 Java虚拟机 指令集、符号表以及若干其他辅助信息;最终将在 Java虚拟机 运行。
本文是以 JVM8 为例的。
每一个 Class文件 都有如下的 ClassFile 文件结构:
先简单介绍一下 ClassFile 文件结构各部分含义:
描述符是表示字段或方法类型的字符串。
字段描述符表示类、实例或局部变量的类型。
从上面文法可以看出,字段描述符中一共有三个类型:
方法描述符包含 0 个或者多个参数描述符以及一个返回值描述符。
看了描述符,可能大家有点疑惑,泛型信息怎么表示啊?
常量池的通用格式如下:
目前 JVM8 中一共用 14 种常量类型,分别如下:
我们知道要使用一个字段或者调用一个方法,就必须知道字段或者方法所属类符号引用,和字段的名字和类型,方法的名字和方法参数类型以及方法返回值类型。
但是我们知道类是能继承的,那么子类调用父类的方法或者字段,这里的所属类符号引用,到底是子类本身还是父类的呢?
我们知道类,方法,字段都有不同的访问标志,在 Class 文件 中使用一个 u2 类型数据项来存储,也就是最多可以有 16 个不同标志位。
在类,方法,字段中有相同的标志,也有不同的标志,总体规划,我们可以借助 Modifier 类的源码来了解:
在 Modifier 类中,类的访问标志:
我们知道在 java 中类可以用的修饰符有: public , protected , private , abstract , static , final , strictfp 。
但是我们再看 Class 文件 中类的访问标志:
仔细看,你会发现有些不同点:
在 Modifier 类中,字段的访问标志:
我们知道在 java 中字段可以用的修饰符有: public , protected , private , static , final , transient 和 volatile 。
但是我们再看 Class 文件 中字段的访问标志:
Class 文件 中字段的访问标志和 java 中字段的修饰符差不多,只是多了 ACC_SYNTHETIC 和 ACC_ENUM 两个标志。
在 Modifier 类中,方法的访问标志:
我们知道在 java 中方法可以用的修饰符有:
public , protected , private , abstract , static , final , synchronized , synchronized 和 strictfp 。
但是我们再看 Class 文件 中方法的访问标志:
字段详情 field_info 的格式如下:
方法详情 method_info 的格式如下:
关于 Class 文件 中属性相关信息,我们再后面章节介绍。
我们可以通过 javap 的命令来阅读 Class 文件 中相关信息。
这个是最简单的一个类,没有任何字段和方法,只继承 Object 类,我们来看看它编译后的字节码信息,通过 javap -p -v T.class 的命令:
我们重点关注常量池相关信息,会发现虽然 T.class 很干净,但是也有 15 个常量,来我们依次分析:
与之前的例子相比较,多了一个字段和方法,那么得到的字节码信息如下:
但是你会发现常量池中怎么没有这个字段 name 的 CONSTANT_Fieldref_info 类型的常量呢?
那是因为我们没有使用这个字段。
多写了一个方法 test1 来调用 name 字段和 test 方法,那么得到的字节码信息如下:
这里定义一个父类 TParent ,有一个公共字段 name 和方法 say 。子类
Ⅵ java常量池是什么
1. 首先String不属于8种基本数据类型,String是一个对象。
因为对象的默认值是null,所以String的默认值也是null;但它又是一种特殊的对象,有其它对象没有的一些特性。
2. new String()和new String(“”)都是申明一个新的空字符串,是空串不是null;
3. String str=”kvill”;
String str=new String (“kvill”);的区别:
在这里,我们不谈堆,也不谈栈,只先简单引入常量池这个简单的概念。
常量池(constant pool)指的是在编译期被确定,并被保存在已编译的.class文件中的一些数据。它包括了关于类、方法、接口等中的常量,也包括字符串常量。
看例1:
Java代码
String s0=”kvill”;
String s1=”kvill”;
String s2=”kv” + “ill”;
System.out.println( s0==s1 );
System.out.println( s0==s2 );
String s0=”kvill”;
String s1=”kvill”;
String s2=”kv” + “ill”;
System.out.println( s0==s1 );
System.out.println( s0==s2 );
结果为:
true
true
首先,我们要知道Java会确保一个字符串常量只有一个拷贝。
因为例子中的s0和s1中的”kvill”都是字符串常量,它们在编译期就被确定了,所以s0==s1为true;而”kv”和”ill”也都是字符串常量,当一个字符串由多个字符串常量连接而成时,它自己肯定也是字符串常量,所以s2也同样在编译期就被解析为一个字符串常量,所以s2也是常量池中”kvill”的一个引用。
所以我们得出s0==s1==s2;
用new String() 创建的字符串不是常量,不能在编译期就确定,所以new String() 创建的字符串不放入常量池中,它们有自己的地址空间。
看例2:
Java代码
String s0=”kvill”;
String s1=new String(”kvill”);
String s2=”kv” + new String(“ill”);
System.out.println( s0==s1 );
System.out.println( s0==s2 );
System.out.println( s1==s2 );
String s0=”kvill”;
String s1=new String(”kvill”);
String s2=”kv” + new String(“ill”);
System.out.println( s0==s1 );
System.out.println( s0==s2 );
System.out.println( s1==s2 ); 结果为:
false
false
false
例2中s0还是常量池中”kvill”的应用,s1因为无法在编译期确定,所以是运行时创建的新对象”kvill”的引用,s2因为有后半部分new String(“ill”)所以也无法在编译期确定,所以也是一个新创建对象”kvill”的应用;明白了这些也就知道为何得出此结果了。
4. String.intern():
再补充介绍一点:存在于.class文件中的常量池,在运行期被JVM装载,并且可以扩充。String的intern()方法就是扩充常量池的一个方法;当一个String实例str调用intern()方法时,Java查找常量池中是否有相同Unicode的字符串常量,如果有,则返回其的引用,如果没有,则在常量池中增加一个Unicode等于str的字符串并返回它的引用;看例3就清楚了
例3:
Java代码
String s0= “kvill”;
String s1=new String(”kvill”);
String s2=new String(“kvill”);
System.out.println( s0==s1 );
System.out.println( “**********” );
s1.intern();
s2=s2.intern(); //把常量池中“kvill”的引用赋给s2
System.out.println( s0==s1);
System.out.println( s0==s1.intern() );
System.out.println( s0==s2 );
String s0= “kvill”;
String s1=new String(”kvill”);
String s2=new String(“kvill”);
System.out.println( s0==s1 );
System.out.println( “**********” );
s1.intern();
s2=s2.intern(); //把常量池中“kvill”的引用赋给s2
System.out.println( s0==s1);
System.out.println( s0==s1.intern() );
System.out.println( s0==s2 ); 结果为:
false
**********
false //虽然执行了s1.intern(),但它的返回值没有赋给s1
true //说明s1.intern()返回的是常量池中”kvill”的引用
true
最后我再破除一个错误的理解:
有人说,“使用String.intern()方法则可以将一个String类的保存到一个全局String表中,如果具有相同值的Unicode字符串已经在这个表中,那么该方法返回表中已有字符串的地址,如果在表中没有相同值的字符串,则将自己的地址注册到表中“如果我把他说的这个全局的String表理解为常量池的话,他的最后一句话,“如果在表中没有相同值的字符串,则将自己的地址注册到表中”是错的:
看例4:
Java代码
String s1=new String("kvill");
String s2=s1.intern();
System.out.println( s1==s1.intern() );
System.out.println( s1+" "+s2 );
System.out.println( s2==s1.intern() );
String s1=new String("kvill");
String s2=s1.intern();
System.out.println( s1==s1.intern() );
System.out.println( s1+" "+s2 );
System.out.println( s2==s1.intern() );
结果:
false
kvill kvill
true
在这个类中我们没有声名一个”kvill”常量,所以常量池中一开始是没有”kvill”的,当我们调用s1.intern()后就在常量池中新添加了一个”kvill”常量,原来的不在常量池中的”kvill”仍然存在,也就不是“将自己的地址注册到常量池中”了。
s1==s1.intern()为false说明原来的“kvill”仍然存在;
s2现在为常量池中“kvill”的地址,所以有s2==s1.intern()为true。
5. 关于equals()和==:
这个对于String简单来说就是比较两字符串的Unicode序列是否相当,如果相等返回true;而==是比较两字符串的地址是否相同,也就是是否是同一个字符串的引用。
6. 关于String是不可变的
这一说又要说很多,大家只要知道String的实例一旦生成就不会再改变了,比如说:String str=”kv”+”ill”+” “+”ans”;
就是有4个字符串常量,首先”kv”和”ill”生成了”kvill”存在内存中,然后”kvill”又和” “ 生成 ”kvill “存在内存中,最后又和生成了”kvill ans”;并把这个字符串的地址赋给了str,就是因为String的“不可变”产生了很多临时变量,这也就是为什么建议用StringBuffer的原因了,因为StringBuffer是可改变的
Ⅶ class文件详解
能够被JVM识别,加载并执行的文件格式
1.通过IDE自动帮我们build。
2.手动通过javac去生成class文件。
记录一个类文件的所有信息。
1.一种8位字节的二进制流文件
2.各个数据按顺序紧密地排列,无间隙 (这样做的好处可以减少class文件的体积,jvm加载我们class文件的时候更加快速)
3.每个类、接口和枚举都单独占据一个class文件(这样做的好处是每个累接口等都可以独自管理自己内部的内容而无需相互交叉)
整体文件格式:
格式详解:
1.magic
无符号4字节,用来表示class文件的开头,加密段,给虚拟机用来判断当前的 class文件是否被串改过。
2.minor_version
class文件最小可以被哪个版本的jdk所加载,也就是最小适配的jdk
3.major_version
表示我们当前class文件是由哪个版本的jdk生成的。
4.constant_pool_count
class文件中常量池的数量,通常只有一个常量池。
5.constant_pool
代表常量池,类型为cp_info(结构体类型)。
常量池中主要包含的内容:
首先列举三个比较简单的
CONSTANT_Integer_info:存储class文件中的int类型。
CONSTANT_Long_info:存储class文件中的long类型。
CONSTANT_String_info:存储class文件中的string类型。
它们分别存储字节码中的int、long、string类型,当然还有CONSTANT_Short_info、CONSTANT_Float_info等。
下面列举几个稍微复杂的
CONSTANT_Class_info:记录类中相关的信息、不仅记录了当前类的信息,还记录了引用到的一些类的信息。
CONSTANT_Fieldref_info:记录类中Field相关的信息。
CONSTANT_Methodref_info:记录类中Method相关的信息。
这三个里面存储的并不是真正的内容,都是一些索引,这些索引指向的又是CONSTANT_String_info等。
6.access_flags
表示class文件的作用域标志,比如:public 、public final
取宴老烂值范围:
7.this_class
this_class是指向constant pool的索引值,该值必须是CONSTANT_Class_info类型,指定当前字节码定义的类或接口。
8.super_class
super_class是指向constant pool的索引值,该值必须是CONSTANT_Class_info类型,指定当前字节码定义的类或接口的直接父类。只有Object类才没有直接父晌漏类,此时该索引值为0。并且父类不能是final类型。接口的父类都是Object类。
9.interfaces_count
当前class文件直接实现的接口数量。
10.interfaces
当前class文件直接实现的接口,只记录直接实现的,不会记录间接实现的。
11.fields_count
class文件中成员变量的数量。
12.fields
class文件中所有的成员变量,field_info类型的结构体,该结构体中主要包含了每个成员变量的name、所属的类以及类型含败。
13.methods_count
记录class文件中方法的数量。
14.methods
记录class文件中所有的方法,类型为method_info结构体类型,主要包含了方法的名字、类型、access_flags等信息。
15.attribute_count
记录了class文件属性的数量。
16.attributes
记录class文件的一些属性,除去上面的一些信息剩下的都包含在attributes中,比如 说注解。
1.内存占用大,不适合移动端。
2.堆栈的加载模式,加载速度慢。
3.文件IO操作多,类查找慢。