深入浅出JVM(一)之Hotspot虚拟机中的对象

本篇文章思维导图

image-20210330233053370.png

对象的创建

对象的创建可以分为五个步骤:检查类加载,分配内存,初始化零值,设置对象头,执行实例构造器

类加载检查
  • HotSpot虚拟机遇到一条new指令,会先检查能否在常量池中定位到这个类的符号引用,检查这个类是否类加载过

    • 没有类加载过就去类加载
    • 类加载过就进行下一步分配内存
分配内存

对象所需的内存在类加载完成后就可以完全确定

分配内存方式

虚拟机在堆上为新对象分配内存,有两种内存分配的方式:指针碰撞,空闲列表

  • 指针碰撞

    • 使用场景: 堆内存规整整齐

    • 过程: 使用过的空间放在一边,空闲的空间放在另一边,中间有一个指针作为分界点指示器,把新生对象放在使用过空间的那一边,中间指针向空闲空间那边挪动一个新生对象的内存大小的距离即可

image-20201028174425589.png

特点:简单,高效,因为要堆内存规整整齐,所以垃圾收集器应该要有压缩整理的能力

  • 空闲列表

    • 使用场景: 已使用空间和空闲空间交错在一起
    • 过程: 虚拟机维护一个列表,列表中记录了哪些内存空间可用,分配时找一块足够大的内存空间划分给新生对象,然后更新列表
    • 特点: 比指针碰撞复杂, 但是对垃圾收集器可以不用压缩整理的能力
分配内存流程

分配内存流程(栈--老年代--TLAB--Eden)

因为在堆上为对象分配内存,内存不足会引起GC,引起GC可能会有STW(Stop The World)影响响应

为了优化减少GC,当对象不会发生逃逸(作用域只在方法中,不会被外界调用)且栈内存足够时,直接在栈上为对象分配内存,当线程结束后,栈空间被回收,(局部变量也被回收)就不用进行垃圾回收了

开启逃逸分析-XX:+DoEscapeAnalysis满足条件的对象就在栈上分配内存

(当对象满足不会逃逸条件除了能够优化在栈上分配内存还会带来锁消除,标量替换等优化...)

image-20201124164550072.png

  1. 尝试该对象能不能在栈上分配内存
  2. 如果不符合1,且该对象特别的大,比如内存超过了JVM设置的大对象的值就直接在老年代上为它分配内存
  3. 如果这个对象不大,为了解决并发分配内存,采用TLAB 本地线程分配缓冲

TLAB 本地线程分配缓存

堆内存是线程共享的,并发情况下从堆中划分线程内存不安全,如果直接加锁会影响并发性能

为每个线程在Eden区分配小小一块属于线程的内存,类似缓冲区

哪个线程要分配内存就在那个线程的缓冲区上分配,只有缓冲区满了,不够了才使用乐观的同步策略(CAS+失败重试)保证分配内存的原子性

image-123.png

在并发情况下分配内存是不安全的(正在给A对象分配内存,指针还未修改,使用原来的指针为对象B分配内存),虚拟机采用TLAB(Thread Local Allocation Buffer本地线程分配缓冲)和CAS+失败重试来保证线程安全

  • TLAB:为每一个线程预先在伊甸园区(Eden)分配一块内存,JVM给线程中的对象分配内存时先在TLAB分配,直到对象大于TLAB中剩余的内存或TLAB内存已用尽时才需要同步锁定(也就是CAS+失败重试)
  • CAS+失败重试:采用CAS配上失败重试的方式保证更新操作的原子性
初始化零值

分配内存完成后,虚拟机将分配的内存空间初始化为零值(不包括对象头) (零值: int对应0等)

保证了对象的成员字段(成员变量)在Java代码中不赋初始值就可以使用

设置对象头

把一些信息(这个对象属于哪个类? 对象哈希码,对象GC分代年龄)存放在对象头中 (后面详细说明对象头)

执行init方法

init方法 = 实例变量赋值 + 实例代码块 + 实例构造器

按照我们自己的意愿进行初始化

对象的内存布局

对象内存信息

对象在堆中的内存布局可以分为三个部分:对象头,实例数据,对齐填充

  • 对象头包括两类信息(8Byte + 4Byte)

    1. Mark Word:用于存储该对象自身运行时数据(该对象的哈希码信息,GC信息:分代年龄,锁信息:状态标志等)

    2. 类型指针(对象指向它类型元数据的指针):HotSpot通过类型指针确定该对象是哪个类的实例 (如果该对象是数组,对象头中还必须记录数组的长度)

    类型指针默认是压缩指针,内存超过32G时为了寻址就不能采用压缩指针了

  • 实例数据是对象真正存储的有效信息

    1. 记录从父类中继承的字段和该类中定义的字段
    2. 父类的字段会出现在子类字段之前,默认子类较小的字段可以插入父类字段间的空隙以此来节约空间(+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>

image-20201124171137539.png mark word : 8 byte

类型指针: 4 byte

对齐填充 12->16 byte

int[] ints = new int[5]; 占多少内存?

image-20201124171539813.png mark word:8 byte

类型指针: 4 byte

数组长度: 4 byte

数组内容初始化: 4*5=20byte

对齐填充: 36 -> 40 byte

父类私有字段到底能不能被子类继承?

image-20201124173533826.png

子类对象的内存空间中保存有父类私有字段,只是无法使用

栈-堆-方法区结构图

image-20210429190053375.png

对象的访问定位

Java程序通过栈上的reference类型数据来操作堆上的对象

访问方式

对象实例数据: 对象的有效信息字段等(就是上面说的数据)

对象类型数据: 该对象所属类的类信息(存于方法区中)

  • 句柄访问

image-20201109182633606.png - 在堆中开辟一块内存作为句柄池,栈中的reference数据存储的是该对象句柄池的地址,句柄中包含了对象实例数据和对象类型数据 - 优点: 稳定,对象被移动时(压缩或复制算法),只需要改动该句柄的对象实例数据指针 - 缺点: 多一次间接访问的开销

  • 直接指针访问

image-20201109182806454.png

栈中的reference数据存储堆中该对象的地址(reference指向该对象),但是对象的内存布局需要保存对象类型数据

优点: 访问速度快

缺点: 不稳定,对象被移动时(压缩或复制算法),需要改动指针

访问方式是虚拟机来规定的,Hotspot主要使用直接指针访问

总结

本篇文章主要从对象的创建流程(类加载、分配内存、初始化零值、设置对象头、执行实例方法)、对象的内存布局(对象头、实例数据、对齐填充)、访问对象的定位方式(直接指针访问、句柄访问)等层面详细介绍了对象,还在其中穿插了栈上分配、TLAB等内存分配优化以及分析对象占用具体空间

  • 参考资料
    • 《深入理解Java虚拟机》
    • 部分图片来源于网络

本文由博客一文多发平台 OpenWrite 发布!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/479567.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于Java+SpringBoot的旅游路线规划系统(源码+论文)

文章目录 目录 文章目录 前言 一、功能设计 二、功能实现 1.1 前端首页模块的实现 1.2 景点新闻 1.3 景点在线预订 1.4 酒店在线预订 1.5 管理员景点管理 1.6 管理员旅游线路管理 1.7 酒店信息管理 三、库表设计 前言 随着我国的经济的不断发展&#xff0c;现在的一些热门的景…

【C++】类与对象(构造函数、析构函数、拷贝构造函数、常引用)

&#x1f308;个人主页&#xff1a;秦jh__https://blog.csdn.net/qinjh_?spm1010.2135.3001.5343&#x1f525; 系列专栏&#xff1a;http://t.csdnimg.cn/eCa5z 目录 类的6个默认成员函数 构造函数 特性 析构函数 特性 析构的顺序 拷贝构造函数 特性 常引用 前言 &…

HBase 进阶

参考来源: B站尚硅谷HBase2.x 目录 Master 架构RegionServer 架构写流程MemStore Flush读流程HFile 结构读流程合并读取数据优化 StoreFile CompactionRegion Split预分区&#xff08;自定义分区&#xff09;系统拆分 Master 架构 Master详细架构 1&#xff09;Meta 表格介…

光芒绽放:妙用“GLAD原则”打造标准的数据可视化图表

光芒绽放&#xff1a;妙用“GLAD原则”打造标准的数据可视化图表 文章目录 光芒绽放&#xff1a;妙用“GLAD原则”打造标准的数据可视化图表前言一、可视化工具有哪些&#xff1f;二、那如何做出正确可视化图表 &#xff1f;GLAD原则1.G原则2.L原则3.A原则4.D原则 三、总结最后…

模式匹配这么好,Java语法里有吗?

这篇文章我们借助新版Java来理解模式匹配&#xff0c;Rust版的模式匹配稍后就端上来&#xff0c;各位先尝尝Java这杯老咖啡还香不香&#x1f604;。 什么是模式匹配&#xff1f; 下图直观的表达了模式匹配的概念。 所谓模式类似上图中木盒的各种形状的洞洞&#xff0c;我们…

单片机学习笔记---红外遥控(外部中断)

目录 红外遥控简介 硬件电路 基本发送与接收 NEC编码​​​​​​​ 遥控器键码 复习外部中断和定时器 红外遥控简介 红外遥控是利用红外光进行通信的设备&#xff0c;由红外LED将调制后的信号发出&#xff0c;由专用的红外接收头进行解调输出 通信方式&#xff1a;单工…

余弦退火:通过动态调整学习率增强深度学习

导 读 在快速发展的机器学习领域&#xff0c;特别是深度学习&#xff0c;训练算法的效率和有效性至关重要。学习率作为这些算法的一个关键因素&#xff0c;决定了梯度下降期间的步长。 而余弦退火作为用于优化学习率的一种新颖且日益流行的技术。本文将深入探讨了余弦退火的概…

SwiftUI 更自然地向自定义视图传递参数的“另类”方式

概览 在 SwiftUI 中&#xff0c;正是自定义视图让我们的 App 变得与众不同&#xff01;然而&#xff0c;除了传统的视图接口定义方式以外&#xff0c;我们其实还可以有更“银杏化”的选择。 如上图所示&#xff1a;对于 SubView 子视图所需的参数我们一开始并没有操之过急&…

软件工具安装遇到bug、报错不知道怎么解决?看这里!

前言 本文举例了几个常见的软件工具使用问题&#xff0c;文末会提供一些我自己整理和使用的工具资料 。 "在追逐零 Bug 的路上&#xff0c;我们不断学习、改进&#xff0c;更加坚定自己的技术信念。让我们相信&#xff0c;每一个 Bug 都是我们成长的机会。" 一、VM…

【Java EE初阶十五】网络编程TCP/IP协议(二)

1. 关于TCP 1.1 TCP 的socket api tcp的socket api和U大片的socket api差异很大&#xff0c;但是和前面所讲的文件操作很密切的联系 下面主要讲解两个关键的类&#xff1a; 1、ServerSocket&#xff1a;给服务器使用的类&#xff0c;使用这个类来绑定端口号 2、Socket&#xf…

如何使用IP代理解决亚马逊账号IP关联问题?

亚马逊账号IP关联问题是指当同一个IP地址下有多个亚马逊账号进行活动时&#xff0c;亚马逊会将它们关联在一起&#xff0c;从而可能导致账号被封禁或限制。 为了避免这种情况&#xff0c;许多人选择使用IP代理。 IP代理为什么可以解决亚马逊IP关联问题&#xff1f; IP代理是…

15-55V输入自动升降压 光伏MPPT自动跟踪充电方案 大功率300瓦

1.MPPT原理--简介 MPPT&#xff0c;全称为Maximum Power Point Tracking&#xff0c;即最大功点跟踪&#xff0c;它是一种通过调节电气模块的工作状态&#xff0c;使光伏板能够输出更多电能的电气系统能够将太阳能电池板发出的直流电有效地贮存在蓄电池中&#xff0c;可有效地…