JVM之对象创建流程及对象内存布局

简介: JVM之对象创建流程及对象内存布局

JVM之内存区域及对象创建流程

对象创建流程

当JAVA虚拟机碰到new字节码指令时,首先会去常量池中查找是否有对应的类名(也就是去查找是否有对应的符号引用),然后去检查这个符号引用代表的类是否已经被加载,解析和初始化过。如果没有会先进行类加载过程。

当类加载后,虚拟机将会为其分配内存,为其分配的内存大小是可知的,下面的内存布局将会讲解为什么是可知的。

分配内存

分配内存这时候有两种情况:

1.假如堆里面的内存是整齐的,用过的在一边,没有用过的内存放在另外一边(后期配图),这个时候中间有个指针来作为两边内存的界限,当内存分配时,指针移动对象内存大小对应的距离即可,这种叫指针碰撞

2.堆里面的内存空间不是规整的,这就需要记录下来哪些内存是可用的,哪些内存是已经被占用了的。这种方式就叫做空闲列表:将内存中空闲内存块记录到列表里面。当分配对象时,直接从空闲列表里面进行取出对应大小的内存块即可。

这两种情况又是根据采用的垃圾收集器是否带有空间压缩整理的能力划分,如果垃圾收集器已经具备了空间压缩整理的能力那么他的内存空间就是被整理好的,直接使用指针碰撞就好;但是如果是基于清除算法决定的垃圾回收器时,就只能用复杂的空闲列表来分配内存。

并发解决方法

但是如果发生并发的话,可能在分配一个对象空间的时候又碰到另外一个线程也在分配空间,这个时候就会出现问题,解决方式有两种:

1.通过CAS进行同步处理,基于失败重试的原则;

2.将堆里面的空间进行按线程分配,每个线程在队中都会有块内存,当线程分配内存时,直接分配到自己线程的那块内存当中,当那小块内存用完时,在进行CAS同步申请新的内存,这种小块内存叫做本地线程分配缓存(TLAB)

设置初始值

分配完内存之后需要给这部分内存设置零值,不包括对象头。
当通过TLAB分配内存时,其实在分配内存的时候就可以设置零值,不需要等到分配完在设置,因为这部分内存区域是已知的不会出现分配时产生并发的问题

在程序中可能会出现的问题

解释:

这步操作也就是说当对象分配到内存后就可以直接使用里面的字段,但是这个是初始值,如果说当我分配完内存后直接使用这个字段的话程序肯定会出问题(因为CPU是乱序执行的,当两个操作互不关联时,一个操作耗时一个操作不耗时,这时候CPU会进行优化让不耗时的先运行。而且一个创建对象的过程需要多行字节码来完成,所以可能会出现重排序的问题,但是这个概率特别低)这时候就需要用volatile关键字来保证有序性。

其本身是通过在JVM平台上面的Load,Store两个读写屏障组合来保证的,对应于intel的X86来说是基于MESI协议来保证的。

其实JVM平台规定了一些不能乱序执行的原则:HappenBefore原则,里面就规定了volitaile关键字

设置对象头

当对象中的字段设置为对应的默认值(零值)时,需要设置对象头里面的数据,这部分数据包括两部分:

对象头数据结构

1.对象自身运行时的数据

比如:

哈希码(延迟到真正调用hashcode()方法时才生成)

锁状态标志

线程持有锁

偏向锁的线程ID

偏向时间戳

对象分代年龄

在未开启压缩指针的情况下,根据32位虚拟机和64位虚拟机不同,这部分数据的总大小分别是32个比特和64个比特。

这部分数据叫做“Mark Word”,由于对象运行时存储的数据很多,所以Mark Word是一个动态的数据结构,有些数据其实根本用不到所以某些数据其实是没有必要立马就存储的。

32位的虚拟机中,MarkWord是32个比特,其中哈希码占用25个比特,分代年龄占用4个,锁标志位占用两个,剩下的另外一个比特固定为0。

2.类型指针

指向类的元数据信息,通过这个指针来确定该对象属于哪个类的实例。

(不是所有的虚拟机都必须在对象数据上设置类型指针)

当对象是数组。。。。

如果对象是数组,在对象头中还会记录数组长度,普通JAVA对象可以通过找到类的元数据信息确定JAVA对象的大小,但是数组长度是不能通过类的元数据信息推导出来的,所以需要在对象头中设置数组长度

Class文件的<.init>

当设置完字段的默认值和对象头的数据后,这个时候该调用Class对象的<.init>方法了即构造函数。

对象的内存布局

当了解完前面的对象创建流程时,相信对于对象在堆中的内存布局也已经有两大概的轮廓了,接下来进行总结:

分为三部分:对象头,实例数据,对齐填充

1.对象头前面已经详细讲过了,就不在阐述了

2.实例数据:

记录父类和当前类中定义的字段,存储的顺序默认是:

long/doubles , ints , shorts/chars , bytes/booleans , oops。默认顺序遵从的原则是相同宽度的字段分配到一起,接着父类定义的变量在子类定义的变量的签名。

3.对齐填充:不是必然的

占位符。

由于HotSpot虚拟机自动内存管理系统要求对象的起始地址必须是8字节的整数倍,也就是对象的大小都必须是8的倍数。

对象头刚刚说了无非是32比特或者64比特默认就是八字节的,所以当实例数据满足八的倍数时,就不需要占位符,这部分数据也就没有;如果不满足八的倍数,将添加占位符使整个对象大小为八的倍数。


目录
相关文章
|
8天前
|
存储 安全 Java
jvm 锁的 膨胀过程?锁内存怎么变化的
【10月更文挑战第3天】在Java虚拟机(JVM)中,`synchronized`关键字用于实现同步,确保多个线程在访问共享资源时的一致性和线程安全。JVM对`synchronized`进行了优化,以适应不同的竞争场景,这种优化主要体现在锁的膨胀过程,即从偏向锁到轻量级锁,再到重量级锁的转变。下面我们将详细介绍这一过程以及锁在内存中的变化。
23 4
|
8天前
|
缓存 算法 Java
JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS
这篇文章详细介绍了Java虚拟机(JVM)中的垃圾回收机制,包括垃圾的定义、垃圾回收算法、堆内存的逻辑分区、对象的内存分配和回收过程,以及不同垃圾回收器的工作原理和参数设置。
29 4
JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS
|
7天前
|
存储 监控 算法
JVM调优深度剖析:内存模型、垃圾收集、工具与实战
【10月更文挑战第9天】在Java开发领域,Java虚拟机(JVM)的性能调优是构建高性能、高并发系统不可或缺的一部分。作为一名资深架构师,深入理解JVM的内存模型、垃圾收集机制、调优工具及其实现原理,对于提升系统的整体性能和稳定性至关重要。本文将深入探讨这些内容,并提供针对单机几十万并发系统的JVM调优策略和Java代码示例。
33 2
|
8天前
|
Java 测试技术 Android开发
让星星⭐月亮告诉你,强软弱虚引用类型对象在内存足够和内存不足的情况下,面对System.gc()时,被回收情况如何?
本文介绍了Java中四种引用类型(强引用、软引用、弱引用、虚引用)的特点及行为,并通过示例代码展示了在内存充足和不足情况下这些引用类型的不同表现。文中提供了详细的测试方法和步骤,帮助理解不同引用类型在垃圾回收机制中的作用。测试环境为Eclipse + JDK1.8,需配置JVM运行参数以限制内存使用。
19 2
|
8天前
|
存储 Java
JVM知识体系学习四:排序规范(happens-before原则)、对象创建过程、对象的内存中存储布局、对象的大小、对象头内容、对象如何定位、对象如何分配
这篇文章详细地介绍了Java对象的创建过程、内存布局、对象头的MarkWord、对象的定位方式以及对象的分配策略,并深入探讨了happens-before原则以确保多线程环境下的正确同步。
24 0
JVM知识体系学习四:排序规范(happens-before原则)、对象创建过程、对象的内存中存储布局、对象的大小、对象头内容、对象如何定位、对象如何分配
|
6天前
|
存储 Kubernetes 架构师
阿里面试:JVM 锁内存 是怎么变化的? JVM 锁的膨胀过程 ?
尼恩,一位经验丰富的40岁老架构师,通过其读者交流群分享了一系列关于JVM锁的深度解析,包括偏向锁、轻量级锁、自旋锁和重量级锁的概念、内存结构变化及锁膨胀流程。这些内容不仅帮助群内的小伙伴们顺利通过了多家一线互联网企业的面试,还整理成了《尼恩Java面试宝典》等技术资料,助力更多开发者提升技术水平,实现职业逆袭。尼恩强调,掌握这些核心知识点不仅能提高面试成功率,还能在实际工作中更好地应对高并发场景下的性能优化问题。
|
2月前
|
Java Docker 索引
记录一次索引未建立、继而引发一系列的问题、包含索引创建失败、虚拟机中JVM虚拟机内存满的情况
这篇文章记录了作者在分布式微服务项目中遇到的一系列问题,起因是商品服务检索接口测试失败,原因是Elasticsearch索引未找到。文章详细描述了解决过程中遇到的几个关键问题:分词器的安装、Elasticsearch内存溢出的处理,以及最终成功创建`gulimall_product`索引的步骤。作者还分享了使用Postman测试接口的经历,并强调了问题解决过程中遇到的挑战和所花费的时间。
|
11天前
|
存储 缓存 算法
JVM核心知识点整理(内存模型),收藏再看!
JVM核心知识点整理(内存模型),收藏再看!
JVM核心知识点整理(内存模型),收藏再看!
|
11天前
|
Java API 对象存储
JVM进阶调优系列(2)字节面试:JVM内存区域怎么划分,分别有什么用?
本文详细解析了JVM类加载过程的关键步骤,包括加载验证、准备、解析和初始化等阶段,并介绍了元数据区、程序计数器、虚拟机栈、堆内存及本地方法栈的作用。通过本文,读者可以深入了解JVM的工作原理,理解类加载器的类型及其机制,并掌握类加载过程中各阶段的具体操作。
|
16天前
|
存储 Java Linux
【JVM】JVM执行流程和内存区域划分
【JVM】JVM执行流程和内存区域划分
35 1