• u4:cafebabe —魔数
魔数:所以的由java编译器而成的class文件的前4个字节都是"oxCAFABABE"
• u2:minor_version—JDK次版本号------ 00 00
• u2:major_version—JDK主版本号------ 00 34
• u2:constant_pool_count—常量池数量—00 19
这个容量计数是从1而不是0开始的,ox0019—>25,这代表常量池中有21个常量,索引范围为1~21
设计者将第0项常量空出来是有特殊考虑的,这样做的目的在于满足后面某些指向常量池的索引值的数据在特定
情况下需要表达“不引用任何一个常量池项目”的含义,这种情况就可以把索引值置为0来表示
• cp_info:constan_pool—常量池表---------数量: constant_pool_count-1
• u2 access_flags-----------访问标志---------------------1
• u2 this_class--------------类引用------------------------1
• u2 super_class------------父类引用---------------------1
• u2 interfaces_count------接口数量----------------------1
• u2 interfaces-------------接口数组------------- interfaces_count
• u2 fields_count-----------字段数量--------------------- 1
• field_info fields------------字段表--------------fields_count
• u2 methods_count---------方法数量--------------------1
• method_info methods-------方法表------------methods_count
• u2 attributes_count----------属性数量--------------- 1
• attribute_info attributes------属性表----------- attributes_count
cp_info的结构
tag对应图
注意:
tag:1~6是字面量型结构体,info存储的是字面量值
tag:7~18是引用型结构体,info存储的是某些字面量型结构体的索引
int和float数据类型的常量在常量池中的存储
如果有多个相同的常量,只有一个是存储一个结构体,其他的都指向这个索引值
关于int类型是否放入常量池问题:
①如果final int----->会被放入常量池
②如果int的值在short的范围内(-32768~32767)----->则会直接放入栈中
③如果int的值不在short的范围内------>会被放入常量池中
Long和double的数据类型在常量池中的存储
如果有多个相同的常量,只有一个是存储一个结构体,其他的都指向这个索引值
关于double和long的原子性问题
由于long和double有 high_bytes4个字节,和low_bytes4个字节组成,如果用32位的jvm会存在原子性问题,需要用volatile进行修饰才行.64位的jvm不存在此问题
String类型的字符串常量在常量池的存储(引用性结构体)
我们写的String s="";便会存储成CONSTANT_STRING_info,其结构中的string_index会指向某个CONSTANT_UTF8_info的索引,CONSTANT_UTF8_info存储的是具体的string字符串
CONSTANT_Utf8_info结构体(所有的引用型结构体都会指CONSTANT_Utf8_info结构体)
常量池中的其他info结构体与CONSTANT_String_info一样都指向CONSTANT_Utf8_info,只是内容不同
类文件中定义的类名和类中使用到的类在常量池中的存储 类的完全限定名和二进制形式的完全限定名
在某个Java源码中,我们会使用很多个类,比如我们定义了一个 ClassTest的类,并把它放到 comjvm 包下,则
ClassTest类的完全限定名为com.jvm.ClassTest,将JVM编译器将
类编译成class文件后,此完全限定名在class文件中,是以二进制形式的完全限定名存储的,即它 会把完全限定符的".“换成”/"
,即在class文件中存储的 ClassTest类的完全限定名称
是"com/jvm/ClassTest"。因为这种形式的完全限定名是放在了class二进制形式的字节码 文件中,所以就称之为
二进制形式的完全限定名。
我们定义一个很简单的ClassTest类,来看一下常量池是怎么对类的完全限定名进行存储的
package com.jvm;
import java.util.Date;
public class ClassTest {
private Date date =new Date();
}
将Java源码编译成ClassTest.class文件后,在此文件的目录下执行 javap -v ClassTest 命令,会看到如下的常量池信息的轮廓:
对于某个类而言,其class文件中至少要有两个CONSTANT_Class_info常量池项,用来表示自己的类
信息和其父类信息。(除了java.lang.Object类除外,其他的任何类都会默认继承自
java.lang.Object)如果类声明实现了某些接口,那么接口的信息也会生成对应的 CONSTANT_Class_info常量池项。
符号引用和直接引用
符号引用与虚拟机的内存布局无关,引用的目标并不一定加载到内存中
org.simple.People类 引用了 org.simple.Language类 ,在编译时People类并不知道Language
类的实际内存地址,因此只能使用符号 org.simple.Language
(假设是这个,当然实际中是由类似于CONSTANT_Class_info的常量来表示的)来表示Language类的地址。
直接引用是和虚拟机的布局相关的
直接引用可以是:
- 直接指向目标的指针(比如,指向“类型”【Class对象】、类变量、类方法的直接引用可能是指向 方法区的指针)
- 相对偏移量(比如,指向实例变量、实例方法的直接引用都是偏移量)
- 一个能间接定位到目标的句柄
引用替换的时机
符号引用替换为直接引用的操作发生在类加载过程(加载 -> 连接(验证、准备、解析) ->
初始化)中的解析阶段,会将符号引用转换(替换)为对应的直接引用,放入运行时常量池中。
访问标识(常量池之后)
在常量池结束之后,紧接着的两个字节代表访问标志(access_flags),这个标志用于识别一些类或者接口层次的访问信息,包括:这个Class是类还是接口;是否定义为public类型;是否定义为abstract类型;如果是类的话,是否被声明为final等
类引用和父类引用
类索引、父类索引和接口索引集合都按顺序排列在访问标志之后,类索引和父类索引用两个u2类型的索引值表示,它们各自指向一个类型为CONSTANT_Class_info的类描述符常量,通过CONSTANT_Class_info类型的常量中的索引值可以找到定义在CONSTANT_Utf8_info类型的常量中的全限定名字符串