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


目录
相关文章
|
2天前
|
存储 设计模式 监控
如何快速定位并优化CPU 与 JVM 内存性能瓶颈?
本文介绍了 Java 应用常见的 CPU & JVM 内存热点原因及优化思路。
|
1月前
|
存储 Java 程序员
【JVM】——JVM运行机制、类加载机制、内存划分
JVM运行机制,堆栈,程序计数器,元数据区,JVM加载机制,双亲委派模型
|
1月前
|
存储 编译器 程序员
【C语言】内存布局大揭秘 ! -《堆、栈和你从未听说过的内存角落》
在C语言中,内存布局是程序运行时非常重要的概念。内存布局直接影响程序的性能、稳定性和安全性。理解C程序的内存布局,有助于编写更高效和可靠的代码。本文将详细介绍C程序的内存布局,包括代码段、数据段、堆、栈等部分,并提供相关的示例和应用。
60 5
【C语言】内存布局大揭秘 ! -《堆、栈和你从未听说过的内存角落》
|
1月前
|
机器学习/深度学习 人工智能 缓存
【AI系统】推理内存布局
本文介绍了CPU和GPU的基础内存知识,NCHWX内存排布格式,以及MNN推理引擎如何通过数据内存重新排布进行内核优化,特别是针对WinoGrad卷积计算的优化方法,通过NC4HW4数据格式重排,有效利用了SIMD指令集特性,减少了cache miss,提高了计算效率。
57 3
|
1月前
|
存储 监控 算法
深入探索Java虚拟机(JVM)的内存管理机制
本文旨在为读者提供对Java虚拟机(JVM)内存管理机制的深入理解。通过详细解析JVM的内存结构、垃圾回收算法以及性能优化策略,本文不仅揭示了Java程序高效运行背后的原理,还为开发者提供了优化应用程序性能的实用技巧。不同于常规摘要仅概述文章大意,本文摘要将简要介绍JVM内存管理的关键点,为读者提供一个清晰的学习路线图。
|
1月前
|
缓存 监控 算法
Python内存管理:掌握对象的生命周期与垃圾回收机制####
本文深入探讨了Python中的内存管理机制,特别是对象的生命周期和垃圾回收过程。通过理解引用计数、标记-清除及分代收集等核心概念,帮助开发者优化程序性能,避免内存泄漏。 ####
57 3
|
2月前
|
Java
JVM内存参数
-Xmx[]:堆空间最大内存 -Xms[]:堆空间最小内存,一般设置成跟堆空间最大内存一样的 -Xmn[]:新生代的最大内存 -xx[use 垃圾回收器名称]:指定垃圾回收器 -xss:设置单个线程栈大小 一般设堆空间为最大可用物理地址的百分之80
|
2月前
|
Java
JVM运行时数据区(内存结构)
1)虚拟机栈:每次调用方法都会在虚拟机栈中产生一个栈帧,每个栈帧中都有方法的参数、局部变量、方法出口等信息,方法执行完毕后释放栈帧 (2)本地方法栈:为native修饰的本地方法提供的空间,在HotSpot中与虚拟机合二为一 (3)程序计数器:保存指令执行的地址,方便线程切回后能继续执行代码
31 3
|
2月前
|
缓存 Prometheus 监控
Elasticsearch集群JVM调优设置合适的堆内存大小
Elasticsearch集群JVM调优设置合适的堆内存大小
534 1
|
3月前
|
存储 安全 Java
jvm 锁的 膨胀过程?锁内存怎么变化的
【10月更文挑战第3天】在Java虚拟机(JVM)中,`synchronized`关键字用于实现同步,确保多个线程在访问共享资源时的一致性和线程安全。JVM对`synchronized`进行了优化,以适应不同的竞争场景,这种优化主要体现在锁的膨胀过程,即从偏向锁到轻量级锁,再到重量级锁的转变。下面我们将详细介绍这一过程以及锁在内存中的变化。
51 4