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比特默认就是八字节的,所以当实例数据满足八的倍数时,就不需要占位符,这部分数据也就没有;如果不满足八的倍数,将添加占位符使整个对象大小为八的倍数。


相关文章
|
4月前
|
Arthas 存储 算法
深入理解JVM,包含字节码文件,内存结构,垃圾回收,类的声明周期,类加载器
JVM全称是Java Virtual Machine-Java虚拟机JVM作用:本质上是一个运行在计算机上的程序,职责是运行Java字节码文件,编译为机器码交由计算机运行类的生命周期概述:类的生命周期描述了一个类加载,使用,卸载的整个过类的生命周期阶段:类的声明周期主要分为五个阶段:加载->连接->初始化->使用->卸载,其中连接中分为三个小阶段验证->准备->解析类加载器的定义:JVM提供类加载器给Java程序去获取类和接口字节码数据类加载器的作用:类加载器接受字节码文件。
412 55
|
5月前
|
Arthas 监控 Java
Arthas memory(查看 JVM 内存信息)
Arthas memory(查看 JVM 内存信息)
396 6
|
4月前
|
Arthas 监控 Java
Arthas vmtool(从 jvm 里查询对象,执行 forceGc)
Arthas vmtool(从 jvm 里查询对象,执行 forceGc)
307 16
|
10月前
|
缓存 Prometheus 监控
Elasticsearch集群JVM调优设置合适的堆内存大小
Elasticsearch集群JVM调优设置合适的堆内存大小
1698 1
|
6月前
|
存储 缓存 算法
JVM简介—1.Java内存区域
本文详细介绍了Java虚拟机运行时数据区的各个方面,包括其定义、类型(如程序计数器、Java虚拟机栈、本地方法栈、Java堆、方法区和直接内存)及其作用。文中还探讨了各版本内存区域的变化、直接内存的使用、从线程角度分析Java内存区域、堆与栈的区别、对象创建步骤、对象内存布局及访问定位,并通过实例说明了常见内存溢出问题的原因和表现形式。这些内容帮助开发者深入理解Java内存管理机制,优化应用程序性能并解决潜在的内存问题。
306 29
JVM简介—1.Java内存区域
|
6月前
|
缓存 监控 算法
JVM简介—2.垃圾回收器和内存分配策略
本文介绍了Java垃圾回收机制的多个方面,包括垃圾回收概述、对象存活判断、引用类型介绍、垃圾收集算法、垃圾收集器设计、具体垃圾回收器详情、Stop The World现象、内存分配与回收策略、新生代配置演示、内存泄漏和溢出问题以及JDK提供的相关工具。
JVM简介—2.垃圾回收器和内存分配策略
|
7月前
|
存储 算法 Java
JVM: 内存、类与垃圾
分代收集算法将内存分为新生代和老年代,分别使用不同的垃圾回收算法。新生代对象使用复制算法,老年代对象使用标记-清除或标记-整理算法。
91 6
|
9月前
|
存储 编译器 程序员
【C语言】内存布局大揭秘 ! -《堆、栈和你从未听说过的内存角落》
在C语言中,内存布局是程序运行时非常重要的概念。内存布局直接影响程序的性能、稳定性和安全性。理解C程序的内存布局,有助于编写更高效和可靠的代码。本文将详细介绍C程序的内存布局,包括代码段、数据段、堆、栈等部分,并提供相关的示例和应用。
290 5
【C语言】内存布局大揭秘 ! -《堆、栈和你从未听说过的内存角落》
|
9月前
|
机器学习/深度学习 人工智能 缓存
【AI系统】推理内存布局
本文介绍了CPU和GPU的基础内存知识,NCHWX内存排布格式,以及MNN推理引擎如何通过数据内存重新排布进行内核优化,特别是针对WinoGrad卷积计算的优化方法,通过NC4HW4数据格式重排,有效利用了SIMD指令集特性,减少了cache miss,提高了计算效率。
281 3
|
9月前
|
存储 监控 算法
深入探索Java虚拟机(JVM)的内存管理机制
本文旨在为读者提供对Java虚拟机(JVM)内存管理机制的深入理解。通过详细解析JVM的内存结构、垃圾回收算法以及性能优化策略,本文不仅揭示了Java程序高效运行背后的原理,还为开发者提供了优化应用程序性能的实用技巧。不同于常规摘要仅概述文章大意,本文摘要将简要介绍JVM内存管理的关键点,为读者提供一个清晰的学习路线图。