jvm中类和对象定义存储基础知识
Tech导读
一个java程序类文件由程序员编写并且编译二进制后,如何能更节约空间、更安全的、更高效寻址等等,可以从本文略知一二。
本文将从以下几个点说明:
1、类、方法、普通字段域、静态字段域等等字节码存储
2、类对象如何实例化,内存分配如何,分配的过程中存在什么问题等等
3、方法调用的当前线程栈的运行情况
导读
一个java程序类文件由程序员编写并且编译二进制后,如何能更节约空间、更安全的、更高效寻址等等,可以从本文略知一二。
本文将从以下几个点说明:
1、类、方法、普通字段域、静态字段域等等字节码存储
2、类对象如何实例化,内存分配如何,分配的过程中存在什么问题等等
3、方法调用的当前线程栈的运行情况
01 类文件数据结构类型
在今年的敏捷团队建设中,我通过Suite执行器实现了一键自动化单元测试。Juint除了Suite执行器还有哪些执行器呢?由此我的Runner探索之旅开始了!
Class文件结构主要有两种数据结构:无符号数和表
无符号数:用来表述数字,索引引用、数量值以及字符串等,比如图1中类型为u1,u2,u4,u8分别代表1个字节,2个字节,4个字节,8个字节的无符号数。
表:表是有由多个无符号数以及其它的表组成的复合结构,比如图1中类型以_info结尾的项为表类型。
02
类结构定义
理解,首先 MCube 会依据模板缓存状态判断是否需要网络获取最新模板,当获取到模板后进行模板加载,加载阶段会将产物转换为视图树的结构,转换完成后将通过表达式引擎解析表达式并取得正确的值,通过事件解析引擎解析用户自定义事件并完成事件的绑定,完成解析赋值以及事件绑定后进行视图的渲染,最终将目标页面展示到屏幕。
下面用一段程序做说明,此类有接口,有方法、类变量和实例变量,机器是如何识别字节码然后按照上面的规则来定义此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{ }
通过javap帮助解析class文件格式如下:
Classfile /D:/spm-workspace/test/target/classes/com/jd/crm/Logback/TestClass.class
Last modified 2023-4-14; size 597 bytes
MD5 checksum 9d5dd9fc2145ac17393fee7a707d3b9c
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_SUPER
Constant 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个十六进制的数,故下面每2个数就代表1个字节)
2.1 魔法数
2.2 版本号
2.3 常量个数
2.4 常量池
2.5 类访问标志
2.6 本类、父类、接口索引集合
比如查找当前类索引如下图
2.7 字段表集合
字段域的访问标志请参考类访问标志,逻辑计算一致,只是规则不一样而已 如下图
2.8 方法表集合
方法表访问标识类型
2.9 属性表集合
类、字段表、方法表本身可包含属性表,属性表格结构体如下,属性表结构类型较多,比如有Code类型、Exception类型、MethodParameters类型等等,具体参考属性表类型。所有的属性都是引用常量池中的属性类型名称。然后根据属性的长度指定该属性的内容,根据属性的不同类型解析不同的属性值。格式定义如下
2.10 字节码简介指令
加载和存储指令
运算指令
类型转换指令
对象创建和访问指令
操作数栈管理指令
控制转移指令
异常处理指令
同步指令
方法调用和返回执行
invokervirtual:调用对象的实例方法 invokerinterface 调用接口方法,自动运行期搜索一个实现接口的对象进行方法调用;invokerspeical:调用init、私有和父类调用的特殊方法调用;invokedynamic:运行时动态解析。
03
类文件加载
理解,首先 MCube 会依据模板缓存状态判断是否需要网络获取最新模板,当获取到模板后进行模板加载,加载阶段会将产物转换为视图树的结构,转换完成后将通过表达式引擎解析表达式并取得正确的值,通过事件解析引擎解析用户自定义事件并完成事件的绑定,完成解析赋值以及事件绑定后进行视图的渲染,最终将目标页面展示到屏幕。
3.1 加载
3.2 验证
1、文件格式验证
2、元数据验证
3、字节码验证
3.3 准备
3.4 解析
类和接口的解析
字段解析根据常量池字段filedrf_info中的符号进行解析,首先在符号引用的类中根据简单名称和字段描述符查找,如果查到则返回这个字段的直接引用并结束,否则从下往上地柜各个父类查找,如果还未查到则抛出NoSuckFieldError异常
方法解析
接口方法解析
04 类实例初始化
理解,首先 MCube 会依据模板缓存状态判断是否需要网络获取最新模板,当获取到模板后进行模板加载,加载阶段会将产物转换为视图树的结构,转换完成后将通过表达式引擎解析表达式并取得正确的值,通过事件解析引擎解析用户自定义事件并完成事件的绑定,完成解析赋值以及事件绑定后进行视图的渲染,最终将目
对象初始化
4.1 初始化对象前检查
jvm碰到一个new指令,首先判断改指令指向的常量池的类全名是否被加载、解析初始化过,如果没有则进行类加载,参考类文件加载。
4.2 内存分配
CAS:CAS 是乐观锁的一种实现方式。所谓乐观锁就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。虚拟机采用 CAS 配上失败重试的方式保证更新操作的原子性。 TLAB(本地现成缓冲区):为每一个线程预先分配一块堆内存,JVM在给线程中的对象分配内存时,首先在TLAB分配,当对象大于TLAB中的剩余内存或TLAB的内存已用尽时,再采用上述的CAS进行内存分配。
4.3 初始化0值
内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值(不包括对象头),这一步操作保证了对象的实例字段在 Java 代码中可以不赋初始值就直接使用,程序能访问到这些字段的数据类型所对应的零值。
4.4 对象头设置
初始化零值完成之后,虚拟机要对对象进行必要的设置,例如这个对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的 GC 分代年龄等信息。这些信息存放在对象头中。另外,根据虚拟机当前运行状态的不同,如是否启用偏向锁等,对象头会有不同的设置方式。
4.5 实例构造器初始化
略
4.6 对象的内存布局
对象在对中的存储布局主要分为三部分,对象头、实例数据、对齐填充
主要两类:其主要包括两部分数据:Mark Word、Class对象指针。特别地对于数组对象而言,其还包括了数组长度数据。在64位的HotSpot虚拟机下,Mark Word占8个字节,其记录了Hash Code、GC信息、锁信息等相关信息;而Class对象指针则指向该实例的Class对象。
HotSpot对象头
实例数据:对象定义的实例变量,这部分数据存储受到虚拟机分配策略参数(-XX:FieldsAllocationStype)和字段定义的顺序影响。HotSpot默认分配的策略是将相同宽度字段一起存放,父类的变量会出现在子类变量之前。
05 对象的访问
理解,首先 MCube 会依据模板缓存状态判断是否需要网络获取最新模板,当获取到模板后进行模板加载,加载阶段会将产物转换为视图树的结构,转换完成后将通过表达式引擎解析表达式并取得正确的值,通过事件解析引擎解析用户自定义事件并完成事件的绑定,完成解析赋值以及事件绑定后进行视图的渲染,最终将目标页面展示到屏幕。
图19
Java堆中将会划分出一块内存来作为句柄池,reference中存储的就是对象的句柄地址,而句柄中包含了对象实例数据与类型数据各自的具体地址信息。
5.2 直接访问
这两种对象访问方式各有优势,使用句柄来访问的最大好处就是reference中存储的是稳定的句柄地址,在对象被移动(垃圾收集时移动对象是非常普遍的行为)时只会改变句柄中的实例数据指针,而reference本身不需要修改。使用直接指针访问方式的最大好处就是速度更快,它节省了一次指针定位的时间开销,由于对象的访问在Java中非常频繁,因此这类开销积少成多后也是一项非常可观的执行成本。就本文讨论的主要虚拟机Sun HotSpot而言,它是使用第二种方式进行对象访问的,但从整个软件开发的范围来看,各种语言和框架使用句柄来访问的情况也十分常见。
06 虚拟机字节码执行引擎
理解,首先 MCube 会依据模板缓存状态判断是否需要网络获取最新模板,当获取到模板后进行模板加载,加载阶段会将产物转换为视图树的结构,转换完成后将通过表达式引擎解析表达式并取得正确的值,通过事件解析引擎解析用户自定义事件并完成事件的绑定,完成解析赋值以及事件绑定后进行视图的渲染,最终将目
6.1 运行时栈帧结构
6.2 方法调用
6.3 基于栈的字节码解释执行引擎
下面是一个基于栈来展示在虚拟机中字节码是如何执行的。
07 容易混淆点
理解,首先 MCube 会依据模板缓存状态判断是否需要网络获取最新模板,当获取到模板后进行模板加载,加载阶段会将产物转换为视图树的结构,转换完成后将通过表达式引擎解析表达式并取得正确的值,通过事件解析引擎解析用户自定义事件并完成事件的绑定,完成解析赋值以及事件绑定后进行视图的渲染,最终将目
7.1 文件常量池
7.2 运行时常量池
7.3 字符串常量池
字符串常量池存放在堆内存(>=1.8)中,堆里边的字符串常量池存放的是字符串的引用或者字符串(两者都有),如下图描述字符串创建的堆分布。
字符串相加:先创建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();//强制生成字符串常量池引用
System.out.println(s == s1);//返回true
String s1 ="a";
String s = new String ("a");
System.out.println(s == s1);//返回false
08
附件
理解,首先 MCube 会依据模板缓存状态判断是否需要网络获取最新模板,当获取到模板后进行模板加载,加载阶段会将产物转换为视图树的结构,转换完成后将通过表达式引擎解析表达式并取得正确的值,通过事件解析引擎解析用户自定义事件并完成事件的绑定,完成解析赋值以及事件绑定后进行视图的渲染,最终将目标页面展示到屏幕。
jvm常量池类型和结构体定义
图25
常量池类型图26
常量池类型结构定义
图27
常见的属性类型
图29
属性表类型09 结束语
理解,首先 MCube 会依据模板缓存状态判断是否需要网络获取最新模板,当获取到模板后进行模板加载,加载阶段会将产物转换为视图树的结构,转换完成后将通过表达式引擎解析表达式并取得正确的值,通过事件解析引擎解析用户自定义事件并完成事件的绑定,完成解析赋值以及事件绑定后进行视图的渲染,最终将目只有对技术底层细节有一定掌握,在平时工作中碰到一些堆溢出、泄露等异常才能快速的处理,能更高的理解java GC的原理。
Mybatis-SQL分析组件(2.0)
从0到1搭建自己的脚手架(java后端)
一次网络请求中的流量分发过程
求分享
求点赞
求在看