class文件详解(全过程举例加图解)

目录:
java虚拟机汇总

  1. class文件结构分析 <<== 现在位置
    1).class文件常量池中的常量项结构
    2). 常用的属性表的集合
  2. 类加载过程
    1).类加载器的原理以及实现
  3. 虚拟机结构分析
    1).jdk1.7和1.8版本的方法区构造变化
    2).常量池简单区分
  4. 对象结构分析
    1).压缩指针详解
  5. gc垃圾回收
  6. 对象的定位方式

内容
jvm运行时都是运行.java编译好的.class文件
这里先了解下class文件长什么样以便后续的理解

本文外引的链接为(class文件常量项结构)
(常用的属性表种类集合)

下面开始,文章篇幅较长,而且比较干,萌新必须静下心来看,大佬随意(doge)

例如:创建一个类文件

class Test{
    
    
	int a;
	public static void main(String []args){
    
    
	
	}
}

编译后的class文件如图所示
在这里插入图片描述
在这里我们把一个字节的大小用u1表示, 则占用u4就是占用4个字节,对应上图4个红圈
整个class文件的结构如下图所示,(再此感谢此图的制作者) 建议将此图保存到一旁以便查看
在这里插入图片描述
下面开始讲解各部分的内容

魔数

占用4个字节,则下图画红圈部分表示为魔数
在这里插入图片描述
截取下来表示为cafeBabe 翻译为咖啡宝贝(可以看出取名挺幽默)

魔数(Magic Number),它的唯一作用是确定这个文件是否是能被虚拟机接受的Class文件。使用魔数而不是扩展名来进行识别主要是基于安全方面的考虑,因为文件扩展名可以随意地改动,说白了就是,不是以这个开头的文件不处理

次版本号和主版本号

每个版本号占用u2,即两个字节,则下图画红圈部分分别表示次版本号和主版本号
在这里插入图片描述
截取下来为主:52, 次:0,即52.0版本 对应Java的jdk1.8
JavaSE 8 = 52 (0x34 hex),
JavaSE 7 = 51 (0x33 hex),
JavaSE 6 = 50 (0x32 hex),
一句话来说次版本号和主版本号就是给jvm明确此类编译的jdk版本,以便判断是否支持某些操作

常量池个数

在这里插入图片描述
截取下来表示为常量池有17个数据项,但实际后面只有16个数据项
(设置成1–16 16个的话,如果某些引用需要指向空则就直接指向0,表示空,到后面讲属性和方法的时候会遇到),
此后会跟上[0]–[15]个数据项,总共16个以数组形式表示
注:此时xx_index指向3,则表示寻找第3个数据项[2]

,每一个数据项都将被表示成表,那紧接着下面的16个常量就是这个常量池中所有的内容了,下面讲解每个常量项的结构

常量项(数据项)

在这里插入图片描述
在这里为什么我会知道下一项的长度呢,其实每一个常量项都是有结构的,先不要着急,往后看
在这里插入图片描述
每个常量项的第一个字节 对应上图中的tag标志 找到对应的结构 (在此分析前四项作为举例)
——————————————————————————————————————
在这里插入图片描述
比如画红圈的第一项,开头是0A则为10,查找上表
在这里插入图片描述

下面代码是CONSTANT_Methodref 的结构 占用五个字节 (读注释!
(在此标注了所有的常量项结构)
此Methodref显然想要表示的是一个方法

CONSTANT_Methodref_info {
    
    
    u1 tag;   //Methodref固定是10,也是这个类型的常量项的唯一标志
    u2 class_index;   //此时是00 03  即3 表示引用常量池中第3个常量项 
    				  
    u2 name_and_type_index;   //此时是00 0E  即14 表示引用常量池中第14个常量项
}

注意:此时我们在分析的是第一个常量项
在这里直接指出
------第14个常量项表示的是一个0C标识符的nameAndType 它又指向了第6,7 个常量项 和 ()V
------第3个常量项表示的是一个标志07的类名,它又指向了一个以01标识的utf8字符串 java.lang.Object
总之 0A 00 03 00 0E 表示了一个 方法名称,构造方法(隐式声明了) java.lang.Object 包里的 void()初始化方法
以后不再赘述分析
—————————————————————————————————————————————
同理分析出第二项
在这里插入图片描述
在这里插入图片描述

tag为7,查找对应表
在这里插入图片描述
下面代码是CONSTANT_Class的结构 占用三个字节

CONSTANT_Class_info {
    
    
    u1 tag;			// 同理,此类型的就固定为7
    u2 name_index;	// 索引值,在此可以发现所有以**_index结尾的都是指向第n个常量项,此时n是00 0F
}

这里指出第0F ,第15个常量项是 Test字符串 其实这个Class就表示了一个类的名称
————————————————————————————————————
同理三
在这里插入图片描述
tag是7,则结构和第二项一样,不做赘述
——————————————————————————————————————
第四项
在这里插入图片描述
发现tag是1,此类型就是utf-8字符串类型
在这里插入图片描述

CONSTANT_Utf8_info {
    
    
    u1 tag;				// 1
    u2 length;			// length表示接下来 字符串 有多少个字节,此时是00 01,下面有1个字节长度字符
    u1 bytes[length];	// 字符串字节数组 上面length如果是8则这一项就是8字节的字符数组 此时是1个长度 61 用utf-8解码是 a
    					// 61表示为a即对应我们int a;的变量名字
}

————————————————————————————————————————————
分析到此结束
此图为全部的16个常量项,
一句话:这部分就是常量池,装载了全部类的字段,方法,类名称的信息;

访问标志(access_flags)

来到这里不妨回头看看主图,我们现在分析到了访问标志,不要忘记回头看此图
在这里插入图片描述

访问标识来表明这个class有什么修饰符 只有两个字节
以下为访问标识符,
举例: 原来是00 20(看下表中ACC_SUPER描述,最低限度必须有此标志)
如果:类是public类型-的,则第一个ACC_PUBLIC为true 则加上00 01 ,结果为00 21
如果:类还是 final类型-的,则第二个为true,再加上00 10 ,结果为00 31
如果此时不是其他的任何选项(不是接口则第4个02 00不用加,不是abstract类型则第5个不用加
。。。) 那么该标识符就是 00 31

在这里插入图片描述
来看我们的class文件
在这里插入图片描述
00 20
表示其他的标识符都没有
我们做如下改动
将类改为public,重新编译,再看class
在这里插入图片描述
在这里插入图片描述
00 21 = 00 20 + 00 01 ( 加了public)
表示为public 的类
设置的数值刚好不会出现相加和其他状态相等的情况,很巧妙
一句话 访问标识就是只用两个字节表示了这个类的修饰符有哪些

类、父类(This class,Super class)

(看主图)不要忘记我们进度到哪了
这个很简单,记得我们在常量池时会有 xx_index 引用引向常量池中的第n个常量项吗,
在这里插入图片描述
下面看看我们class文件中的This class,Super class是哪几个在这里插入图片描述

这两个标识符也都是一个两字节的引用,我们class文件中,分别引向常量池中00 02的第二个常量项和00 03 第三个常量项,这里就不带大家去找第二,三个常量项了

接口个数

。。。。就表示该类文件实现了接口的数量;没有就是00 00 ,有一个就是 00 01没啥好讲的
在这里插入图片描述
在这里对我们的class文件作出修改
在这里插入图片描述
使其实现一个接口,则看下图,变为了00 01
在这里插入图片描述

接口1,接口2.。。。

这个就没得说了,前面接口数量是几,后边这个两字节的接口的长度就为几,这两个字节也是指向常量池的索引
在这里插入图片描述

因为前面长度是1,所以接下来的一个字节的长度是索引,00 04 ,指向常量池第4个常量项

字段个数,字段1,字段2。。。。

进行到这里我们就只差三个了:字段 方法 和属性 加油

和上面的一样,字段个数表示我们有几个字段,此时为00 01,1个 因为我们只定义了一个变量 int a;

以后跟着n(此时是1)个字段,当然这个字段没有固定字节数,因为和常量项类别一样,有很多种类型,所以长度也不确定,接下来我们看下通用结构

field_info {
    
    
    u2             access_flags;		// 字段访问标志
    u2             name_index;		// 字段名称
    u2             descriptor_index;	// 字段类型
    u2             attributes_count;	// 属性数量
    attribute_info attributes[attributes_count];  // 属性项
}

在这里因为我们第四个 attributes_count 为0 ,所以总长度(算字段个数)为10字节
下图所示
在这里插入图片描述

分析第一个 access_flags
----其实跟之前我们讲访问标识的时候一样,不一样的是这次的访问标志修饰符是修饰字段的
所以在标志名称上有些不同
在这里插入图片描述

这里加一张图用另一种方式表示我们的修饰符(在此感谢此图的制作者)
在这里插入图片描述
我们的访问标识符为00 00 表示什么修饰也没有 毕竟我们只定义了一个 int a;
在这里插入图片描述
分析第二个 name_index(此字段的字段名称)
CONSTANT_Utf8类型常量项的索引,里面存储了字段描述符
这个没啥说的就一个两字节的,指向字符串常量池的索引
在这里插入图片描述
此时我们是00 05,表示引用常量池中的第5个常量项
在这里直接指出 是一个CONSTANT_Utf8类型常量项 其值为 a ,表示我们字段的名字是a
分析第三个 descriptor_index(含义字段类型)
CONSTANT_Utf8类型常量项的索引,里面存储了字段描述符
和上面一样,一个两字节的,指向字符串常量池的索引
在这里插入图片描述
此时我们是00 06,表示引用常量池中的第6个常量项
但是这个我们要说一下
通过这个索引查找到第6常量项,如下图,类型为1,则为字符串类型的 长度为1 则下面49为字符串的值 ,对应右边翻译表的:大写 I,I 有什么意义呢
在这里插入图片描述
在这里插入图片描述
上表表示的就是类型了,可以看到我们大写I表示了一个int类型的含义,descriptor_index含义为这个字段的字段类型 ,则表示此字段为int类型 int a

分析第四个 attributes_count
表示这个字段所拥有的attribute类型的个数,如果是00 01 就表示一个
分析第五个 attributes[attributes_count]
就是一个长度为 attributes_count,类型是:属性类型的数组
这个类型我们暂时不分析,到后面讲到 属性 时一起讲

一句话总结
就是一个装有我们定义的所有属性的数组,长度为字段个数,里面的每一个表信息都表示一个字段

方法个数,方法1,方法2。。。。

这个就不说了因为结构和字段一摸一样,减轻大家的负担
在这里插入图片描述
上图为方法数量,3个方法
下面为方法表的结构

method_info {
    
      
    u2 access_flags; //访问修饰符。。参照下图
    u2 name_index;  // 一样。。。。
    u2 descriptor_index; //一样。。。
    u2 attributes_count; //一样。。。
    attribute_info attributes[attributes_count];
}

access_flags的修饰类型
在这里插入图片描述
则 方法个数,方法1,方法2。。。。对应我们class文件中的哪些呢
在这里插入图片描述
这些在属性中会讲

一句话总结
就是一个装有我们定义的所有方法的数组,长度为方法个数,里面的每一个表信息都表示一个方法

属性个数,属性1,属性2。。。。

这是最后一个了,但是最难的才开始(属性要讲很多,,一次理解不了的可以收藏过后再看)
上面所说的字段方法的最后都有一项:属性项数组,里面装的就是这个类型了
其实class文件还有一个属性表,就是上面那张图没有覆盖掉的最后一部分
官方的话:
在Class文件、字段表、方法表都可以携带自己的属性表集合,用于描述某些场景的专有信息。属性表中不要求各个属性表具有严格的顺序,只要不与已有属性重名即可。下表列举了一些java虚拟机预定的属性。
不去管他什么意思,就只了解字段,方法中都有属性数组就可以了,下面我们来分析方法中的属性表(因为我们字段表的属性表长度为0,所以分析方法表)
在这里插入图片描述
还是上面这张图,我们来一个一个分析
在这里插入图片描述
上面的图高亮部分为方法长度 ,表示有多少个方法表如图是三个
第一个
在这里插入图片描述
上图高亮是第一个方法表的前三个字段
在这里插入图片描述
access_flag为00 01
name_in…等,(将方法时说过了不说了)
则第四个属性 attributes_count为
在这里插入图片描述
一个长度
接下来的就是一个属性表数组了,里面只有一个属性表
在这里插入图片描述

上图是整个这个属性表的内容,直接看前两个字节 00 09,这是一个引用,引用常量池里的,utf-8类型常量项
00 09 第9个常量项 我们看下
在这里插入图片描述
这是第9个常量项,其值是Code表示这个属性表是一个Code类型的属性表
然后下图看00 09 后面的四个字节,他表示整个这个属性表还有多少字节 00 00 00 1D,29个长度
在这里插入图片描述

向后延申29个字节(记住这个29是Code类型的属性表里边的内容,不算前6个字节),如下图,属性数组结束,第一个方法也随之结束,上面的00 09 和 00 00 00 1d,是所有的属性表的共有属性,一个是该属性表的类型,一个是该属性表的 属性值长度(属性值长度=属性表长度-6),6就是我们前面的00 09 和 00 00 00 1d,6个字节
在这里插入图片描述
在这里插入图片描述
如上图属性表的
前两个个字节为一个指向常量池里的一个utf8类型的字符串,表示此属性的类别,
中间4个字节为整个属性表的接下来的长度
然后后面跟着的就是attribute_length个字节了,

我们这里类型是Code那么Code属性表里都是什么呢
在这里插入图片描述
可以看到我们Code表前两个字段是我们上面分析过的,所有属性表都有的,这里跳过这两个,下面看内容,也就是29个字节里分别是什么

在这里插入图片描述
29个字节中的前两个字节,也就是对应
在这里插入图片描述
max_stack 代表了操作数栈(Operand Stacks)深度的最大值。在方法执行的任意时刻,操作数栈都不会超过这个最大值。虚拟机运行的时候需要根据这个值来分配栈帧(Stack Frame)中的操作栈深度
根据我们总体学习框架,我们还没了解到栈,跳过

接下来所有的字节请对照Code属性表一个字节一个字节自行分析(不难),总之全部过后
在这里插入图片描述
第一个方法结束,我们就进行到这了,下一步我们分析第二个方法,再带大家走一遍
在这里插入图片描述
这6个字节是第二个方法的前面的一些属性
在这里插入图片描述,不做分析 接下来 的00 01是属性表长度,
再接下来的00 09是第一个属性表的名称类别,去常量池里找,发现还是Code属性表,
再接下来四个字节00 00 00 19 是属性表接下来的长度,25个字节。。。。。。不分析了总之第二个方法(含属性表)如下图
在这里插入图片描述
第三个方法
在这里插入图片描述
最后我们分析到了class文件的属性表
在这里插入图片描述
前两个字节的信息表示class有多少属性表 00 01 ,1个 然后跟着一个属性表
最后带大家分析一边属性表了
00 0E表示属性表种类,查找字符常量池的第14个
在这里插入图片描述
可以看我们右边的翻译,为sourceFile类型的,上次和上上次分析的是Code表,那么这个类型的属性表结构是什么?如下图
在这里插入图片描述
SourceFile属性用于记录生成这个Class文件的源码名称。
在接下来的四个字节为长度00 00 00 02,接下来还有两个字节
sourceFile_index,以_index结尾的不用说,指向常量池的索引,我们看下第00 0F个常量项
在这里插入图片描述
是Testt.java(这是我写的另一个java文件编译的,就换了名字)

可见只是记录了我们原java文件名称

到此我们的属性分析就。。。还没结束,我们全部类型的属性表很多,包括Code表里面的Code属性(方法内容),因为此篇太长了所以转到(常用的属性表种类集合)
为大家讲解

此篇分析class文件源码结束,这只是jvm的开始,希望大家自己再走一遍流程,熟悉下操作

还有此篇的转发无需请求我的同意,本人默认同意转发哈

猜你喜欢

转载自blog.csdn.net/lioncatch/article/details/105919391