当前位置: 首页 > 图灵资讯 > 技术篇> jvm中类和对象定义存储基础知识 | 京东云技术团队

jvm中类和对象定义存储基础知识 | 京东云技术团队

来源:图灵教育
时间:2023-06-08 09:25:00

1 类别文件数据结构类型

Class文件结构主要有两种数据结构:无符号数和表

•无符号数:用于表达数字、索引、数量值、字符串等 图1中的类型为u1、u2、u4和u8,分别代表1个字节、2个字节、4个字节和8个字节。

•表:表是由多个无符号数和其它表组成的复合结构,例如图1中的类型以_info结尾的项为表类型。

2 类结构定义

Class类文件紧凑、顺序、无间隙,魔数(MagicNumber)、Class文件版本(Version)、常量池(Constant\_Pool)、访问标记(Access\_flag)、本类(This\_class)、父类(Super\_class)、接口(Interfaces)、字段集合(Fields)、方法集合(Methods )、属性集合(Attributes)。interfaces接口类型是数组,因为java多继承;attribute_info是方法表中定义的code索引,指向具体的方法体字节码。interfaces接口类型是数组,因为java多继承;attribute_info是方法表中定义的code索引,指向具体的方法体字节码。如图1所示。

jvm中类和对象定义存储基础知识 | 京东云技术团队_对象定义存储

以下是一个程序,包括接口、方法、类变量和实例变量。机器如何识别字节码,然后根据上述规则定义这个class类别?

package com.jd.crm.Logback;public class TestClass implements Super{    private static final int staticVar = 0;    private int instanceVar=0;    public int instanceMethod(int param) throws  Exception{        return param ++;    }}interface Super{ }

class文件格式通过javap帮助分析如下:

Classfile /D:/spm-workspace/test/target/classes/com/jd/crm/Logback/TestClass.class  Last modified 2023-4-14; size 597 bytes  MD5 checksum 9d5d9fc2145ac17393fee7a707d3b9c  Compiled from "TestClass.java"public class com.jd.crm.Logback.TestClass implements com.jd.crm.Logback.Super  minor version: 0  major version: 52  flags: ACC_PUBLIC, ACC_SUPERConstant pool:   #1 = Methodref          #4.#26         // java/lang/Object."<init>":()V   #2 = Fieldref           #3.#27         // com/jd/crm/Logback/TestClass.instanceVar:I   #3 = Class              #28            // com/jd/crm/Logback/TestClass   #4 = Class              #29            // java/lang/Object   #5 = Class              #30            // com/jd/crm/Logback/Super   #6 = Utf8               staticVar   #7 = Utf8               I   #8 = Utf8               ConstantValue   #9 = Integer            0  #10 = Utf8               instanceVar  #11 = Utf8               <init>  #12 = Utf8               ()V  #13 = Utf8               Code  #14 = Utf8               LineNumberTable  #15 = Utf8               LocalVariableTable  #16 = Utf8               this  #17 = Utf8               Lcom/jd/crm/Logback/TestClass;  #18 = Utf8               instanceMethod  #19 = Utf8               (I)I  #20 = Utf8               param  #21 = Utf8               Exceptions  #22 = Class              #31            // java/lang/Exception  #23 = Utf8               MethodParameters  #24 = Utf8               SourceFile  #25 = Utf8               TestClass.java  #26 = NameAndType        #11:#12        // "<init>":()V  #27 = NameAndType        #10:#7         // instanceVar:I  #28 = Utf8               com/jd/crm/Logback/TestClass  #29 = Utf8               java/lang/Object  #30 = Utf8               com/jd/crm/Logback/Super  #31 = Utf8               java/lang/Exception{  public com.jd.crm.Logback.TestClass();    descriptor: ()V    flags: ACC_PUBLIC    Code:      stack=2, locals=1, args_size=1         0: aload_0         1: invokespecial #1                  // Method java/lang/Object."<init>":()V         4: aload_0         5: iconst_0         6: putfield      #2                  // Field instanceVar:I         9: return      LineNumberTable:        line 3: 0        line 7: 4      LocalVariableTable:        Start  Length  Slot  Name   Signature            0      10     0  this   Lcom/jd/crm/Logback/TestClass;  public int instanceMethod(int) throws java.lang.Exception;    descriptor: (I)I    flags: ACC_PUBLIC    Code:      stack=1, locals=2, args_size=2         0: iload_1         1: iinc          1, 1         4: ireturn      LineNumberTable:        line 10: 0      LocalVariableTable:        Start  Length  Slot  Name   Signature            0       5     0  this   Lcom/jd/crm/Logback/TestClass;            0       5     1 param   I    Exceptions:      throws java.lang.Exception    MethodParameters:      Name                           Flags      param}SourceFile: "TestClass.java"

以上是javap帮助我们生成的class文件分析结果,只是给人看,而不是机器。

class文件的格式如下,因为class文件是以8位为一个字节的二进制流。为了便于计算,二进制用16进制表示(1字节=2个16进制,所以下面每2个代表一个字节)

jvm中类和对象定义存储基础知识 | 京东云技术团队_对象定义存储_02

2.1 魔法数

前四个字节cafebabe是固定值,任何语言编译成jvm知道的二进制流,前四个字节必须是固定cafebabe字节。

2.2 版本号

然后2个字节00表示次版本号为0 ;0034代表主版本为52(jdk版本号对应的jdk版本为1.8)参考jdk版本与class字节版本的对应关系

2.3 常量个数

const____pool_count字节码000 20对应的说明常数为32,实际上为31,因为第一个jvm用作保留位。

2.4 常量池

常量池存储两个常量:字面量和符号引用,如文本字符串、生命final常量值等,符号引用包括类别、界面的全限名称、字段、方法名称和描述符号等。参考javap生成的类别文件信息。

在这里,我们只分析其中一个常数。在上面的常数2字节后面,一个字节0a10进制为10,参考常量池类型10代表类中方法的符号。继续参考Methodref_info格式定义:前两个字节004代表方法所在类名称的索引,后两个字节0001a代表NameandType类型的索引。

jvm中类和对象定义存储基础知识 | 京东云技术团队_常量池_03

2.5 类访问标志

常量池定义后的U2标志访问标志,本例标志为0x0021和下图标志位按位或计算,如0x001为真,0x0020为真,其他是否为真 ACC最终确认访问标志位PUBLIC、ACC\_SUPER

jvm中类和对象定义存储基础知识 | 京东云技术团队_JVM_04

2.6 这类、父类、接口索引集合

根据图1的规则,引用u2字节0003识别当前类名,引用常量池数组下标为#3,根据图3所示子项的类名为com/jd/crm/Logback/TestClass;代表父类类名的引用常量池数组下标为#4,根据图4引用的父类名称为java//lang/Object;然后0001识别接口的数量,指出数量为10005识别第一个接口数组中接口的名称,指向常量池中下标记为5的名称为com/jd/crm/Logback/Super;

例如,找到当前的类索引如下图所示

jvm中类和对象定义存储基础知识 | 京东云技术团队_字段_05

2.7 字段表集合

字段表以数组的形式存储在常量表中

jvm中类和对象定义存储基础知识 | 京东云技术团队_JVM_06

上图显示,0002标识域的数量为2个域标识,其中有两个类别,一个类别的域字段staticVar 一是实例对象的域字段instanceVar,如字段结构定义(下图)定义,前两个字节001a为访问标志,与类访问标志一样,分别采用001a二进制和下图字段域访问标志类型进行定位或操作,访问类型为ACC\_PRIVATE类型。name\_index占用两个字节006,指向常量表下标为6的引用,descriptor\_index=007指向常量表下标记为7的引用,I标记为int的数据类型,attributes\_count=001为1,值为0008指向常量表下标有#8的引用常量Constantvalue,标识为静态变量,最后依次类推第二个域标识

jvm中类和对象定义存储基础知识 | 京东云技术团队_对象定义存储_07

定义字段结构

请参考字段域的访问标志,逻辑计算一致,但规则不同 如下图

jvm中类和对象定义存储基础知识 | 京东云技术团队_java_08

2.8 方法表集合

类似于域字段集合表的定义 常量池中也定义了数组模式 ,attributes\count代表方法的属性数量,attribute_info是属性集合参考属性表集合

jvm中类和对象定义存储基础知识 | 京东云技术团队_JVM_09

jvm中类和对象定义存储基础知识 | 京东云技术团队_JVM_10

访问标识类型的方法表

该方法的代码块存储在类型为Code的属性表中,通过上述方法的访问标志、名称索引和描述索引定义方法的基本信息。

2.9 属性表集合

类别、字段表、方法表本身可以包含属性表,属性表结构如下,属性表结构类型较多,如Code类型、Exception类型、MethodParameters类型等,具体参考属性表类型。所有属性都引用常量池中的属性类型名称。然后根据属性的长度指定属性的内容,根据属性的不同类型分析不同的属性值。格式定义如下

jvm中类和对象定义存储基础知识 | 京东云技术团队_对象定义存储_11

以Code属性为例,Code属性结构如下所示

attribute__jvm按属性获取atributename\_index指向常量池中的字符串常量Code,然后attribute_length标识Code类型Info信息长度,包括:max\_stack 最大栈深,max_locals局部变量槽数量,code\_length识别机器字节码的长度,以后查询字节码如下图所示,实际上是0/1/4/5/6/9的指令集。Code类型,嵌套异常属性表,Linenumbertable,行号表、LocaVariableTable 局部变量表等信息。Code类型,嵌套异常属性表,Linenumbertable,行号表、LocaVariableTable 局部变量表等信息。javap生成的类定义信息如下图所示

jvm中类和对象定义存储基础知识 | 京东云技术团队_字段_13

1.Code1方法的执行过程:

构造方法:descriptor ()V标识无参无返回值为Void方法索引,flags可见性修饰符;

程序运行时,先将常量池、方法字节码、字符串常量池、静态变量加载到元数据区(1.8后字符串常量池、静态变量堆放);main线程开始运行,分配栈帧内存,操作数栈Stack=2表示操作方法所需的最大操作数栈深度为2;locals=1表示操作方法所需的最大局部方法表的最大slot数据是1;args_size是该方法的形参数,如果是实例方法 this引用了第一个形参。这个例子是this引用的。这个例子是this引用的。所以args\_size=1+实际参数

aload_0: 加载 slot0的局部变量,即this,作为下面的invokespeciall 调用结构方法的参数

invokespecial: 调用结构方法,常量池第#1项,即【Method java/lang/Object."":()V】

aload_0 :再次加载 slot0的局部变量,即thiss

iconst0: 将int类型为0的数值压入栈顶(为什么要再次放入栈顶,我个人可能是以下初始化实例需要指定到当前的实例对象)

putfileld: 常量池#2 也就是com//jd/crm/Logback/TestClass.instanceVar 实例变量赋值为0,并弹出栈。

通过上述指令操作,对象已初始化。可以发现,在实例变量初始化之前,先调用构造器方法,再初始化实例变量。

1.instancemethod执行Code2方法:

descriptor标识为int类型参与,int类型参与

flags标识方法问public类型

statck=2代表栈深度为2,locals=2.标志预留两个局部变量槽;args_size=标识两个参数,分别是隐藏的this和方法的形式参数,下标\[0\]=this、 \[1\]=param 如下所示

LocalVariableTable:

Start Length Slot Name Signature

0 4 0 this Lcom/jd/crm/Logback/TestClass;

0 4 1 param I

0:iload_1 将上述局部变量槽LocalVariabletable下标为1的param参数压入栈中

1:iconst_1 将int类型为1的常量数字压入栈中

2: iadd 将是当前栈顶的两个元素 param和1相加

3: ireturn 返回

LineNumberTable:

line 10: 0

实际java源代码的行数

2.10 字节码指令简介

•加载和存储指令:

•运算指令

•类型转换指令

•对象创建和访问指令

•操作数栈管理指令

•控制转移指令

•异常处理指令

•同步指令

•调用和返回执行方法

invokervirtual:调用对象的实例方法 invokerinterface 调用接口法,在自动运行期间搜索实现接口的对象进行调用;invokerspeical:调用init、调用私有和父类调用的特殊方法;invokedynamic:运行时动态分析

3 类文件加载

jvm中类和对象定义存储基础知识 | 京东云技术团队_对象定义存储_14

3.1 加载

jvm通过classloader(双亲委派)将class文件二进制流加载到元数据区内存,

将字节流标志的静态存储结构转换为元数据区的动态存储

在堆内存中创建一个Class对象。堆中的Class不存储静态变量、常量、方法等实际信息(实际存储元空间)。它可以看作是一个句柄,通过对象头的类指针指向元空间信息。这样,在强制转换或InstanceOf判断时,将根据对象中的类指针指向元空间的类常量池判断是否为同一类。

3.2 验证

1、验证文件格式

2、元数据验证

3、字节码验证

4、符号引用验证

3.3 准备

准备阶段是为类变量(静态变量)分配内存并设置类变量初始值的阶段。这些内存的分配是在元数据区进行的,但类变量(没有Final修改的静态变量)、1.8及以后将字符串常量放入堆间。本阶段需要重点介绍以下两点:

1、只有类变量(static修改的变量赋值的初始值,static final修改的赋值为程序指定值)将分配内存,不包括实例变量,当对象实例化时,实例变量将在堆中分配内存。

2、设置类变量的初始值是数量类型对应的默认值,而不是代码中设置的默认值。例如,public static int number=111.这种变量number在准备阶段后的初始值为0,而不是111。给number赋值111是在类的初始化阶段。

3.4 解析

分析阶段是虚拟机将常量池中的符号引用替换为直接引用的过程。分析动作主要用于类别或接口、字段、类别方法、接口方法、方法类型、方法句柄和调用点限定符7类。

符号引用:常量池中类和字段的常量字符串表示

类和界面分析例:如果类A引用类B,则加载阶段为静态分析。此时,B尚未放入JVM内存中。此时,A只引用代表B的符号,即符号引用。

直接引用: 指向目标或相对偏移的指针

类和接口分析例:类A在分析阶段发现其符号引用B,如果此时B尚未加载。它直接触发B的类加载,并将B的有效类信息地址存储在运行常量池中,并直接引用。

•分析类别和接口

•字段分析是根据常量池字段filedrf_info中的符号进行分析的。首先,根据简单的名称和字段描述符在符号引用类别中搜索。如果发现,则返回该字段的直接引用并结束。否则,每个家庭都会从下到上搜索地柜。如果未发现,则抛出nosuckfielderor异常

•方法解析

•接口方法分析

4 类实例初始化

对于类的静态变量,JVM负责对类的初始化,主要是对类变量的初始化clinit方法。Java中设置类变量初始值有两种方法:定义静态变量并指定值和使用静态代码块

对象初始化

jvm中类和对象定义存储基础知识 | 京东云技术团队_JVM_15

4.1 前检查初始化对象

当jvm遇到new指令时,首先判断改变指令指向的常量池的全名是否被加载和分析初始化。如果没有,则进行类加载,并加载参考文件

4.2 内存分配

通过jvm内存分配机制,这种分配机制取决于回收机制,通过指针碰撞或空闲列表进行堆内存分配;

1.指针碰撞法 假设Java堆中的内存是完整的,已分配的内存和空闲内存分别在不同的一侧。当需要分配内存时,只需将指针移动到空闲端,并与物体大小相等。使用的GC收集器:Serial、ParNew,适用于堆内存规则(即无内存碎片)的情况。两者都是新一代垃圾收集器,所以都采用复制算法,可以获得相对完整的内存区域。

2.空闲列表法 事实上,Java堆的内存并不完整,已分配的内存和空闲内存相互交错。JVM通过维护列表来记录可用的内存块信息。当分配操作发生时,从列表中找到足够大的内存块分配给对象,并更新列表上的记录。使用的GC收集器:CMS,适用于堆内存不规则的情况。从名字中的Mark 可以看到Sweep这两个词,CMS 收集器是通过“标记-清除”算法实现的,因此会得到大量的碎片,因此可以与空闲列表一起使用。

内存分配并发问题

在创建对象时,有一个非常重要的问题,即线程安全,因为在实际开发过程中,创建对象非常频繁,作为虚拟机,必须确保线程安全,一般来说,虚拟机使用两种方法来确保线程安全:

•CAS: CAS 这是实现乐观锁的一种方式。所谓乐观锁,就是每次不加锁,假设没有冲突就完成某个操作,如果因为冲突失败而重试,直到成功。虚拟机使用 CAS 以失败重试的方式保证更新操作的原子性。

•TLAB(本地现成缓冲区): 提前为每个线程分配一堆内存。当JVM将内存分配给线程中的对象时,首先在TLAB中分配。当对象大于TLAB或TLAB的剩余内存耗尽时,上述CAS用于内存分配。

4.3 初始化0值

内存分配完成后,虚拟机需要将分配的内存空间初始化为零(不包括对象头),这确保了对象的实例字段 Java 代码可以直接使用,而无需赋予初始值,程序可以访问这些字段的数据类型对应的零值。

4.4 对象头设置

初始化零值完成后,虚拟机需要设置对象,如对象是什么样的实例,如何找到元数据信息、对象的哈希码、对象的实例 GC 分代年龄和其他信息。这些信息存储在对象头中。此外,根据虚拟机的不同运行状态,如是否使用偏向锁,对象头将有不同的设置方法。

4.5 实例构造器的初始化

4.6 对象的内存布局

对象在对中的存储布局主要分为对象头、实例数据和对齐填充三个部分

jvm中类和对象定义存储基础知识 | 京东云技术团队_对象定义存储_16

对象头:

主要有两类:数据主要包括两部分:Mark Word、Class对象指针。特别是对于数组对象,它还包括数组长度数据。在64位Hotspot虚拟机下,Mark Word占8个字节,它记录了Hash Code、GC信息、锁定信息等相关信息;而Class对象指针指向Clas对象。

jvm中类和对象定义存储基础知识 | 京东云技术团队_对象定义存储_17

Hotspot对象头头

实例数据:由虚拟机分配策略参数存储的对象定义的实例变量(-XX:FieldsAllocationStype)以及字段定义的顺序。HotSpot的默认分配策略是将相同宽度的字段存储在一起,在子类变量之前会出现父类变量。

对齐填充:jvm存储的任何尺寸都必须是8个字节的整数倍,这是不够的。这与二级字节流一致。以下是无锁对象实例化后的数据结构。用jol工具打印的实例布局如下

jvm中类和对象定义存储基础知识 | 京东云技术团队_JVM_18

5 对象的访问

jvm中类和对象定义存储基础知识 | 京东云技术团队_JVM_19

5.1 句柄访问

Java堆将一块内存划分为句柄池,在reference中 存储是对象

句柄地址包含对象实例数据和类型数据的具体地址 息

5.2 直接访问

直接访问是直接存储在reference中的实例对象的地址。实例对象包含类对象的访问指针,即如果访问对象需要更多的参考层

优缺点

这两种对象的访问方式各有优势。使用句柄访问的最大优点是,稳定的句柄地址存储在reference中。当对象被移动(垃圾收集时移动对象是一种非常常见的行为)时,只会改变句柄中的实例数据指针,而reference本身不需要修改。 使用直接指针访问的最大好处是速度更快,节省了一次指针定位的时间开支, 由于对象访问在Java中非常频繁,这种费用也是一个非常可观的执行成本。本书讨论的主要虚拟机Sunn 就HotSpot而言,它使用第二种方式访问对象,但从软件开发的整个范围来看,使用句柄访问各种语言和框架也非常常见

6 虚拟机字节码执行引擎6.1 栈帧结构在运行过程中

1.局部变量表:当class文件被编译时,已知有几个局部变量槽,主要存储方法参数和方法内部定义的局部变量

2.操作数栈:类似于局部变量表,编译时操作数栈的深度很清楚

3.动态链接:在类加载分析过程中,大多数类别会将符号引用转换为直接引用,即在类加载阶段清楚地调用哪些类别和方法(这些方法在参考字节码指令介绍中调用invoke*指令),但有些方法必须在运行期间直接引用,以确定目标。

4.方法返回地址

6.2 方法调用

1.分析:在内部分析阶段,符号引用将转换为直接引用。这种可以在分析阶段确定的调用方法版本称为分析,如invokesatic invokespecial 调用invokevirtual等指令指示的方法

2.静态分布:方法的重载,虚拟机需要根据方法的参数和类型定位到特定的方法,发生在编译阶段,因此也属于一种分析方法

3.重载方法匹配优先级:在方法重载过程中,涉及方法的参与和数量,参与自动类型转换,如重载方法参与char类型,如果没有参与char类型的方法匹配,char自动类型转换为int类型,最终匹配int参与类型的方法。方法重载的本质

4.动态分配:如下图所示,man、women和重新man引用指向women,然后方法调用sayhello。此时,字节码中显示的符号被引用为human#sayHello,但实际执行结果与指令码不一致,这是因为invokevirtual指令在指令调用前将aload_x加载实际数据类型,这就是方法重写的本质

jvm中类和对象定义存储基础知识 | 京东云技术团队_对象定义存储_20

5.invokedynamic指令:为了解决虚拟机中其他invok*指令方法分配规则完全固化的问题,jvm支持设计师更高的灵活性,并以api的方式直接使用动态呼叫。参考java.lang.使用invoke包。

6.3 基于栈的字节码解释执行引擎

jvm是基于堆栈的指令集,该指令本身没有参数,使用操作堆栈的输入和输出作为指令本身的参数。物理机器通常是基于寄存器的指令集,指令本身携带参数并存储在寄存器中。

以下是如何在虚拟机中执行基于栈的字节码。

jvm中类和对象定义存储基础知识 | 京东云技术团队_常量池_21

以上字节码执行流程如下如下

7 7.1容易混淆点 文件常量池

类加载后,类域字段、方法和类描述信息将加载到元数据区,属于类静态常量池

7.2 常量池运行

我们上面提到的class文件中的常量池将在类加载后进入方法区的运行常量池。不仅Class定义的文件常量合并后放入运行常量池,新常量也可以放入运行过程中的池中,如stringintern方法

jvm中类和对象定义存储基础知识 | 京东云技术团队_字段_23

7.3 字符串常量池

字符串常量池存储在堆中(>=1.8)在堆内的字符串常量池中,存储字符串引用或字符串(两者都有),如下图所示,字符串创建的堆分布

jvm中类和对象定义存储基础知识 | 京东云技术团队_JVM_24

上图说明:

引用初始化初始化s、s2是先看常量池,然后引用返回对象,否则创建abc对象,然后创建s1/s2ref常量引用返回

字符串加:先创建Stringbuilder对象,再创建apend字符串a、apend字符串b 然后tostring(new方法)生成字符串ab对象,并在字符串常量池中生成引用返回。为什么不添加字符串,因为它会生成大量的stringbuilder对象

String s = "a"+"b";///返回常量池ab字符串引用String s1 ="ab";System.out.println(s == s1);///因为两者最终都指向字符串常量池,所以是true

new 字符串相当于堆创建两个对象,一个String对象,然后创建字符串堆存储,然后String对象引用字符串堆存储,

String s1 ="a";String s = new String ("a").intern();///强制生成字符串常量池引用Systemm.out.println(s == s1);//返回true
String s1 ="a";String s = new String ("a");System.out.println(s == s1);//返回false
8 附件

jvm常量池类型和结构体定义

jvm中类和对象定义存储基础知识 | 京东云技术团队_对象定义存储_25

常量池类型

jvm中类和对象定义存储基础知识 | 京东云技术团队_对象定义存储_26

定义常量池类型结构

jvm中类和对象定义存储基础知识 | 京东云技术团队_字段_27

常见的属性类型

jvm中类和对象定义存储基础知识 | 京东云技术团队_字段_28

jdk版本好clas字节版本号对应关系

jvm中类和对象定义存储基础知识 | 京东云技术团队_java_29

属性表类型