本篇文章思维导图如下:
对象的创建可以分为五个步骤:检查类加载,分配内存,初始化零值,设置对象头,执行实例构造器<init>
对象所需的内存在类加载完成后就可以完全确定
虚拟机在堆上为新对象分配内存,有两种内存分配的方式:指针碰撞,空闲列表
特点:简单,高效,因为要堆内存规整整齐,所以垃圾收集器应该要有压缩整理的能力
分配内存流程(栈--老年代--TLAB--Eden)
因为在堆上为对象分配内存,内存不足会引起GC,引起GC可能会有STW(Stop The World)影响响应
为了优化减少GC,当对象不会发生逃逸(作用域只在方法中,不会被外界调用)且栈内存足够时,直接在栈上为对象分配内存,当线程结束后,栈空间被回收,(局部变量也被回收)就不用进行垃圾回收了
开启逃逸分析-XX:+DoEscapeAnalysis
满足条件的对象就在栈上分配内存
(当对象满足不会逃逸条件除了能够优化在栈上分配内存还会带来锁消除,标量替换等优化...)
TLAB 本地线程分配缓存
堆内存是线程共享的,并发情况下从堆中划分线程内存不安全,如果直接加锁会影响并发性能
为每个线程在Eden区分配小小一块属于线程的内存,类似缓冲区
哪个线程要分配内存就在那个线程的缓冲区上分配,只有缓冲区满了,不够了才使用乐观的同步策略(CAS+失败重试)保证分配内存的原子性
在并发情况下分配内存是不安全的(正在给A对象分配内存,指针还未修改,使用原来的指针为对象B分配内存),虚拟机采用TLAB(Thread Local Allocation Buffer本地线程分配缓冲)和CAS+失败重试来保证线程安全
分配内存完成后,虚拟机将分配的内存空间初始化为零值(不包括对象头) (零值: int对应0等)
保证了对象的成员字段(成员变量)在Java代码中不赋初始值就可以使用
把一些信息(这个对象属于哪个类? 对象哈希码,对象GC分代年龄)存放在对象头中 (后面详细说明对象头)
init方法 = 实例变量赋值 + 实例代码块 + 实例构造器
按照我们自己的意愿进行初始化
对象在堆中的内存布局可以分为三个部分:对象头,实例数据,对齐填充
类型指针默认是压缩指针,内存超过32G时为了寻址就不能采用压缩指针了
+XX:CompactFields
)HotSpot要求对象起始地址必须是8字节整倍数
所以任何对象的大小都必须是8字节的整倍,如果对象实例数据部分未到达8字节就会通过对齐填充进行补全
Object obj = new Object(); 占多少字节?
导入JOL依赖
<!-- https://mvnrepository.com/artifact/org.openjdk.jol/jol-core -->
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.12</version>
</dependency>
mark word : 8 byte
类型指针: 4 byte
对齐填充 12->16 byte
int[] ints = new int5; 占多少内存?
mark word:8 byte
类型指针: 4 byte
数组长度: 4 byte
数组内容初始化: 4*5=20byte
对齐填充: 36 -> 40 byte
父类私有字段到底能不能被子类继承?
子类对象的内存空间中保存有父类私有字段,只是无法使用
Java程序通过栈上的reference类型数据来操作堆上的对象
访问方式
对象实例数据: 对象的有效信息字段等(就是上面说的数据)
对象类型数据: 该对象所属类的类信息(存于方法区中)
句柄访问
直接指针访问
栈中的reference数据存储堆中该对象的地址(reference指向该对象),但是对象的内存布局需要保存对象类型数据
优点: 访问速度快
缺点: 不稳定,对象被移动时(压缩或复制算法),需要改动指针
访问方式是虚拟机来规定的,Hotspot主要使用直接指针访问
本篇文章主要从对象的创建流程(类加载、分配内存、初始化零值、设置对象头、执行实例方法)、对象的内存布局(对象头、实例数据、对齐填充)、访问对象的定位方式(直接指针访问、句柄访问)等层面详细介绍了对象,还在其中穿插了栈上分配、TLAB等内存分配优化以及分析对象占用具体空间
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。