类文件结构
ClassFile {
u4 magic; //Class 文件的标志
u2 minor_version;//Class 的小版本号
u2 major_version;//Class 的大版本号
u2 constant_pool_count;//常量池的数量
cp_info constant_pool[constant_pool_count-1];//常量池
u2 access_flags;//Class 的访问标记
u2 this_class;//当前类
u2 super_class;//父类
u2 interfaces_count;//接口
u2 interfaces[interfaces_count];//一个类可以实现多个接口
u2 fields_count;//Class 文件的字段属性
field_info fields[fields_count];//一个类会可以有个字段
u2 methods_count;//Class 文件的方法数量
method_info methods[methods_count];//一个类可以有个多个方法
u2 attributes_count;//此类的属性表中的属性数
attribute_info attributes[attributes_count];//属性表集合
}
静态常量池
所谓静态常量池,即*.class文件中的常量池。
- 字面量
- 符号引用
类和接口的全限定名
字段的名称和描述符
方法的名称和描述符 - 好处:常量池是为了避免频繁的创建和销毁对象而影响系统性能,其实现了对象的共享。
什么是直接引用、符号引用、字面量?
- 字面量:
int i = 1 ; string s = "abc"这里的 1 和 abc 都是属于字面量。 - 符号引用(Symbolic References):符号引用以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可。方法名,类名,字段名都是符号引用
- 直接引用(Direct References):直接引用可以是直接指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄。如果有了直接引用,那么引用的目标一定是已经存在于内存中。
运行时常量池
所谓的运行时常量池,其实就是Class文件被加载之后将静态常量池中的内容移动到了方法区中的运行时常量池中,这个过程中会将一部分的符号引用转变为直接引用,比如:静态方法、实例构造方法、父类方法、私有方法等,因为这些方法是不能够被重写为其他的方法了,所以这个时候就可以将符号引用转化为直接引用。其他的方法的话是当它们第一次被调用的时候才转化为直接引用。
字符串常量池
字符串常量池的存在使JVM提高了性能和减少了内存开销。
每当我们使用字面量(String s=“1”;)创建字符串常量时,JVM会首先检查字符串常量池,如果该字符串已经存在常量池中,那么就将此字符串对象的地址赋值给引用s(引用s在Java栈。如果字符串不存在常量池中,就会实例化该字符串并且将其放到常量池中,并将此字符串对象的地址赋值给引用s(引用s在Java栈中)。
每当我们使用关键字new(String s=new String(”1”);)创建字符串常量时,JVM会首先检查字符串常量池,如果该字符串已经存在常量池中,那么不再在字符串常量池创建该字符串对象,而直接堆中创建该对象的副本,然后将堆中对象的地址赋值给引用s,如果字符串不存在常量池中,就会实例化该字符串并且将其放到常量池中,然后在堆中创建该对象的副本,然后将堆中对象的地址赋值给引用s。
不同版本的变化
不同版本的静态常量池都是在Class文件中的
1.6
- 运行时常量池在Perm Gen区(也就是方法区)中。
- 字符串常量池在运行时常量池中。
1.7
- 运行时常量池依然在Perm Gen区(也就是方法区)中。在JDK7版本中,永久代的转移工作就已经开始了,将譬如符号引用转移到了native heap(可以理解为一个类似于元空间的本地内存);字面量转移到了java heap;类的静态变量转移到了java heap。但是运行时常量池依然还存在,只是很多内容被转移,其只存着这些被转移的引用。
- 字符串常量池被分配到了Java堆的主要部分。也就是字符串常量池从运行时常量池分离出来了。
1.8
- JVM已经将运行时常量池从方法区中移了出来,在Java 堆(Heap)中开辟了一块区域存放运行时常量池。同时永久代被移除,以元空间代替。元空间并不在虚拟机中,而是使用本地内存。因此,默认情况下,元空间的大小仅受本地内存限制。其主要用于存放一些元数据。
- 方法区经过分裂之后的元空间已经几乎只存储类和类加载器的元数据信息了
- 字符串常量池存在于Java堆中。