当前位置: 首页 > news >正文

JVM原理(二):JVM之HotSpot虚拟机中对象的创建寻位与定位整体流程

1. 对象的创建

遇到new指令时

当Java虚拟机遇到一个字节码new指令时。

首先会去检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否被加载、解析和初始化过。

如果没有,那么必须执行类的加载过程(加载、检查、准备、解析、初始化)

类加载后?

类加载检查通过后,接下来虚拟机会为新生对象分配内存。对象所需内存的大小在类加载完成后便可完全确定

划分内存:

  1. 如果Java堆中内存是绝对规整的,那么只需一个指针作为分界点的指示器,对象需要多少内存,就移出多少内存。我们称这种方式为指针碰撞

  2. 如果Java堆中的内存不是规整的,那么虚拟机就必须维护一个表,记录那些内存块是可用的,在分配的时候从列表中找到一块足够大的空间划分给对象实例,并更新列表的记录,这种分配方式称为空闲列表

分配安全

在线程并发情况下,可能会出现正在给对象A分配一块内存,但指针还没来得及修改,对象B又使用了原来的指针进行分配,这就会造成内存不安全。

  1. 解决方案1:对分配内存空间的操作进行同步处理——实际上虚拟机是采用CAS配上失败重试的方式保证更新操作的原子性。

  2. 解决方案2:把内存分配的动作按照线程划分在不同空间之中进行,即每个线程在Java堆中预先分配一小块内存,称为本地线程分配缓冲(TLAB)

分配内存之后该怎么做?

Java虚拟机会将必要的信息存放到对象的对象头中,其中包括这个对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的GC分代年龄信息等。

分配好对象头之后,对象创建才刚刚开始(Java程序的角度)

目前对象的构造函数,即Class文件中的<init>()方法还没执行,所有的字段都为默认的零值,对象需要的其他资源和状态信息也还没有按照预定的意图构造好。new指令之后会接着执行<init>()方法,按照程序员的意愿对对象进行初始化,这样一个真正可用的对象才算完全被构造出来。

2. 对象的内存布局

在HotSpot虚拟机中,对象在堆内存中的存储布局可以划分为三个部分:对象头、实例数据、对齐填充。


对象头结构

HotSpot虚拟机对象的对象头部分包括两类信息。

第一类是用于存储对象自身的运行时数据,如哈希码(HashCode) 、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等,官方称它为“MarkWord?。MarkWord被设计成一个有着动态定义的数据结构。

        对象头的另外一部分是类型指针,即对象指向它的类型元数据的指针,Java虛拟机通过这个指针来确定该对象是哪个类的实例。并不是所有的虚拟机实现都必须在对象数据上保留类型指针,换句话说,查找对象的元数据信息并不一定要经过对象本身,这点我们会在下一节具体讨论。此外,如果对象是一个Java数组,那在对象头中还必须有一块用于记录数组长度的数据,因为虚拟机可以通过普通Java对象的元数据信息确定Java对象的大小,但是如果数组的长度是不确定的,将无法通过元数据中的信息推断出数组的大小。


对象第二部分数据(内存)

接下来实例数据部分是对象真正存储的有效信息,即我们在程序代码里面所定义的各种类型的字段内容,无论是从父类继承下来的,还是在子类中定义的字段都必须记录起来。


对象第三部分

对象的第三部分是对齐填充,这并不是必然存在的,也没有特别的含义,它仅仅起着占位符的作用。由于HotSpot虚拟机的自动内存管理系统要求对象起始地址必须是8字节的整数倍,换句话说就是任何对象的大小都必须是8字节的整数倍。对象头部分已经被精心设计成正好是8字节的倍数(1倍或者2倍),因此,如果对象实例数据部分没有对齐的话,就需要通过对齐填充来补全。

3. 对象的访问定位

创建对象自然是为了后续使用该对象,我们的Java程序会通过栈上的reference数据来操作堆上的具体对象。由于reference类型在《Java虚拟机规范》里面只规定了它是-一个指向对象的引用,并没有定义这个引用应该通过什么方式去定位、访问到堆中对象的具体位置,所以对象访问方式也是由虚拟机实现而定的,主流的访问方式主要有使用句柄和直接指针两种:

  • 句柄::如果使用句柄访问的话,Java堆中将可能会划分出一块内存来作为句柄池,reference中存储的就是对象的句柄地址,而句柄中包含了对象实例数据类型数据各自具体的地址信息,其结构如图2-2所。

  • .如果使用直接指针访问的话,Java堆中对象的内存布局就必须考虑如何放置访问类型数据的相关信息,reference中存储的直接就是对象地址,如果只是访问对象本身的话,就不需要多一次间接访问的开销,如图2-3所示。


两者的区别

使用直接指针来访问最大的好处就是速度更快,它节省了一次指针定位的时间开销,由于对象访问在Java中非常频繁,因此这类开销积少成多也是一项极为可观的执行成本,就本书讨论的主要虚拟机HotSpot而言,它主要使用第二种方式进行对象访问。

相关文章:

  • 如何取消闪迪Micro SD卡的写保护?这个技巧很有效!
  • 【C语言内存函数】
  • PHP景区旅游多商户版微信小程序系统源码
  • Kafka 管理TCP连接
  • 《妃梦千年》第二十四章:皇后的考验
  • Java+前后端分离架构+ MySQL8.0.36产科信息管理系统 产科电子病历系统源码
  • AIGI赋能未来:人工智能如何重塑电子电路学习体验
  • 探索金融数据API:现代投资的关键工具
  • 南方航空阿里v2滑块验证码逆向分析思路学习
  • JVM 内存中方法出入栈原理
  • 油猴Safari浏览器插件:Tampermonkey for Mac 下载
  • 修复harbor的/account/sign-in\?globalSearch=b不登录可以查询镜像的问题
  • vue3 滚动条滑动到元素位置时,元素加载
  • Linux——命令执行原理,命令别名
  • LVS FILTER UNUSED OPTION
  • [NodeJS] 关于Buffer
  • 2019.2.20 c++ 知识梳理
  • conda常用的命令
  • export和import的用法总结
  • HTTP传输编码增加了传输量,只为解决这一个问题 | 实用 HTTP
  • java8-模拟hadoop
  • Laravel核心解读--Facades
  • OpenStack安装流程(juno版)- 添加网络服务(neutron)- controller节点
  • PHP CLI应用的调试原理
  • Spring技术内幕笔记(2):Spring MVC 与 Web
  • 从setTimeout-setInterval看JS线程
  • 世界上最简单的无等待算法(getAndIncrement)
  • 协程
  • 用 vue 组件自定义 v-model, 实现一个 Tab 组件。
  • 支付宝花15年解决的这个问题,顶得上做出十个支付宝 ...
  • ​Linux·i2c驱动架构​
  • ​VRRP 虚拟路由冗余协议(华为)
  • (1)SpringCloud 整合Python
  • (13)Latex:基于ΤΕΧ的自动排版系统——写论文必备
  • (4) openssl rsa/pkey(查看私钥、从私钥中提取公钥、查看公钥)
  • (搬运以学习)flask 上下文的实现
  • (草履虫都可以看懂的)PyQt子窗口向主窗口传递参数,主窗口接收子窗口信号、参数。
  • (非本人原创)我们工作到底是为了什么?​——HP大中华区总裁孙振耀退休感言(r4笔记第60天)...
  • (紀錄)[ASP.NET MVC][jQuery]-2 純手工打造屬於自己的 jQuery GridView (含完整程式碼下載)...
  • (力扣)1314.矩阵区域和
  • (十一)c52学习之旅-动态数码管
  • (转) RFS+AutoItLibrary测试web对话框
  • (转)Android中使用ormlite实现持久化(一)--HelloOrmLite
  • (转)德国人的记事本
  • .Net Core和.Net Standard直观理解
  • .NET Framework 和 .NET Core 在默认情况下垃圾回收(GC)机制的不同(局部变量部分)
  • .NET/C# 将一个命令行参数字符串转换为命令行参数数组 args
  • .NET/C# 推荐一个我设计的缓存类型(适合缓存反射等耗性能的操作,附用法)
  • .net连接oracle数据库
  • @AliasFor注解
  • @PostConstruct 注解的方法用于资源的初始化
  • [ vulhub漏洞复现篇 ] JBOSS AS 4.x以下反序列化远程代码执行漏洞CVE-2017-7504
  • [ 隧道技术 ] 反弹shell的集中常见方式(四)python反弹shell
  • [Angular] 笔记 16:模板驱动表单 - 选择框与选项
  • [C#]winform利用seetaface6实现C#人脸检测活体检测口罩检测年龄预测性别判断眼睛状态检测