JVM运行时数据区——对象的实例化内存布局与访问定位

news2025/1/17 6:12:26

文章目录

  • 1、对象的实例化
    • 1.1、创建对象的方式
    • 1.2、创建对象的步骤
  • 2、对象的内存布局
  • 3、对象的访问定位
    • 3.1、对象访问的定位方式
    • 3.2、使用句柄访问
    • 3.3、使用指针访问
  • 4、小结

平时大家经常使用new关键字来创建对象,那么我们创建对象的时候,怎么去和运行时数据区关联起来呢?本贴将会带着这样的问题来重点讲解对象实例化的过程和方式,包括对象在内存中是怎样布局的,以及对象的访问定位方式,带领大家更加深入地学习对象的实例化布局。

1、对象的实例化

对象的实例化将分成两部分讲解,第一部分为创建对象的方式,第二部分为创建对象的步骤。如下图所示,是对象实例化的整体结构图:
在这里插入图片描述

1.1、创建对象的方式

创建对象的方式有多种,例如使用new关键字、Class的newInstance()方法、Constructor类的newInstance()方法、clone()方法、反序列化、第三方库Objenesis等,如下图所示:
在这里插入图片描述
每种创建对象方式的实际操作如下:

  • 使用new关键字——调用无参或有参构造器创建。
  • 使用Class的newInstance()方法——调用无参构造器创建,且需要是public的构造器。
  • 使用Constructor类的newInstance()方法——调用无参或有参、不同权限修饰构造器创建,实用性更广。
  • 使用clone()方法——不调用任何构造器,且对象需要实现Cloneable接口并实现其定义的clone()方法,且默认为浅复制。
  • 使用反序列化——从指定的文件或网络中,获取二进制流,反序列化为内存中的对象。
  • 第三方库Objenesis——利用了asm字节码技术,动态生成Constructor对象。

Java是面向对象的静态强类型语言,声明并创建对象的代码很常见,根据某个类声明一个引用变量指向被创建的对象,并使用此引用变量操作该对象。在实例化对象的过程中,JVM中发生了什么变化呢?

下面从最简单的Object ref = new Object();代码进行分析,利用javap -verbose -p命令查看对象创建的字节码,如下图所示:
在这里插入图片描述
各个指令的含义如下:

  • new:首先检查该类是否被加载。如果没有加载,则进行类的加载过程;如果已经加载,则在堆中分配内存。对象所需的内存的大小在类加载完成后便可以完全确定,为对象分配空间的任务等同于把一块确定大小的内存从Java堆中划分出来。这个指令完毕后,将指向实例对象的引用变量压入虚拟机栈栈顶。
  • dup:在栈顶复制该引用变量,这时的栈顶有两个指向堆内实例对象的引用变量。
  • invokespecial:调用对象实例方法,通过栈顶的引用变量调用方法。是对象初始化时执行的方法,而是类初始化时执行的方法。

从上面的四个步骤中可以看出,需要从栈顶弹出两个实例对象的引用。这就是为什么会在new指令下面有一个dup指令。其实对于每一个new指令来说,一般编译器都会在其下面生成一个dup指令,这是因为实例的初始化方法(方法)肯定需要用到一次,然后第二个留给业务程序使用,例如给变量赋值、抛出异常等。如果我们不用,那编译器也会生成dup指令,在初始化方法调用完成后再从栈顶pop出来。

1.2、创建对象的步骤

前面所述是从字节码角度看待对象的创建过程,现在从执行步骤的角度来分析,如下图所示:
在这里插入图片描述

创建对象的步骤如下:

  • 1、判断对象对应的类是否加载、链接、初始化:虚拟机遇到一条new指令,首先去检查这个指令的参数能否在Metaspace的常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已经被加载、解析和初始化(即判断类元信息是否存在)。如果没有,那么在双亲委派模式下,使用当前类加载器以“ClassLoader+包名+类名”为Key查找对应的“.class”文件。如果没有找到文件,则抛出ClassNotFoundException异常。如果找到,则进行类加载,并生成对应的Class类对象。
  • 2、为对象分配内存:首先计算对象占用空间大小,接着在堆中划分一块内存给新对象。如果实例成员变量是引用变量,仅分配引用变量空间即可,即4字节大小。如果内存规整,使用指针碰撞。如果内存是规整的,那么虚拟机将采用指针碰撞法(Bump The Pointer)来为对象分配内存。意思是所有用过的内存在一边,空闲的内存在另外一边,中间放着一个指针作为分界点的指示器,分配内存就仅仅是把指针向空闲那边挪动一段与对象大小相等的距离罢了。一般使用带有compact(整理)过程的收集器时,使用指针碰撞,例如Serial Old、Parallel Old等垃圾收集器。如果内存不规整,虚拟机需要维护一个列表,使用空闲列表(Free List)分配。如果内存不是规整的,已使用的内存和未使用的内存相互交错,那么虚拟机将采用空闲列表法来为对象分配内存。意思是虚拟机维护了一个列表,记录哪些内存块是可用的,在分配的时候从列表中找到一块足够大的空间划分给对象实例,并更新列表上的内容。这种分配方式称为空闲列表。选择哪种分配方式由Java堆是否规整决定,而Java堆是否规整又由所采用的垃圾收集器是否带有压缩整理功能决定。
  • 3、处理并发安全问题:创建对象是非常频繁的操作,在分配内存空间时,另外一个问题是保证new对象的线程安全性。虚拟机采用了两种方式解决并发问题。CAS(Compare And Swap):是一种用于在多线程环境下实现同步功能的机制。CAS操作包含三个操作数,内存位置、预期数值和新值。CAS的实现逻辑是将内存位置处的数值与预期数值相比较,若相等,则将内存位置处的值替换为新值;若不相等,则不做任何操作。TLAB:把内存分配的动作按照线程划分在不同的空间之中进行,即每个线程在Java堆中预先分配一小块内存。
  • 4、初始化分配到的空间:内存分配结束,虚拟机将分配到的内存空间都初始化为零值(不包括对象头)。这一步保证了对象的实例字段在Java代码中可以不用赋初始值就可以直接使用,程序能访问到这些字段的数据类型所对应的零值。
  • 5、设置对象的对象头:将对象的所属类(即类的元数据信息)、对象的HashCode、对象的GC信息、锁信息等数据存储在对象头中。这个过程的具体设置方式取决于JVM实现。
  • 6、执行init()方法进行初始化:从Java程序的视角看来,初始化才正式开始。初始化成员变量,执行实例化代码块,调用类的构造方法,并把堆内对象的首地址赋值给引用变量。因此一般来说(由字节码中是否跟随由invokespecial指令所决定),new指令之后接着就是执行方法,把对象按照程序员的意愿进行初始化,这样一个真正可用的对象才算完全创建出来。

2、对象的内存布局

对象的内存布局如下图所示:
在这里插入图片描述
在HotSpot虚拟机中,对象在内存中的布局可以分成对象头(Header)、实例数据(Instance Data)、对齐填充(Padding)三部分。

  • 对象头:主要包括对象自身的运行时元数据,比如哈希值、GC分代年龄、锁状态标志等,同时还包含一个类型指针,指向类元数据,表明该对象所属的类型。此外,如果对象是一个数组,对象头中还必须有一块用于记录数组的长度的数据。因为正常通过对象元数据就知道对象的确切大小。所以数组必须得知道长度。
  • 实例数据:它是对象真正存储的有效信息,包括程序代码中定义的各种类型的字段(包括从父类继承下来的和本身拥有的字段)。
  • 对齐填充:由于HotSpot虚拟机的自动内存管理系统要求对象起始地址必须是8字节的整数倍,换句话说就是任何对象的大小都必须是8字节的整数倍。对象头部分已经被精心设计成正好是8字节的倍数(1倍或者2倍),因此,如果对象实例数据部分没有对齐的话,就需要通过对齐填充来补全。它不是必要存在的,仅仅起着占位符的作用。

对象的内存布局示例如下图所示:
在这里插入图片描述
下面我们用代码来讲述实例在内存中的布局,如下代码清单所示:
在这里插入图片描述
把CustomerTest中main()方法看作是主线程,主线程虚拟机栈中放了main()方法的栈帧,其中栈帧里包含了局部变量表、操作数栈、动态链接、方法返回地址、附加信息等结构。局部变量表对于main()方法来讲第一个位置放的是args,第二个位置放的是cust,cust指向堆空间中new Customer()实体。Customer对象实体整体来看分为对象头、实例数据、对齐填充。对象头中主要有运行时元数据和元数据指针,元数据指针也可称为类型指针,运行时元数据包含哈希值、GC分代年龄、锁状态标志等信息;类型指针指向当前对象所属类的信息,也就是方法区的Customer类的Klass类元信息,Klass类元信息包括对象的类型信息;在实例数据中包含父类的实例数据,对于当前对象来讲它有id、name、acct三个变量,name的字符串常量放在堆空间的字符串常量池中,成员变量acct指向new Account()对象实例在堆中的内存地址,new Account()对象实例的对象头中也维护了一个类型指针指向方法区的Account的Klass类元信息。整体布局如下图所示:
在这里插入图片描述

3、对象的访问定位

3.1、对象访问的定位方式

前面讲解了创建对象的方式以及对象的内存结构。创建好对象之后,接下来就是去访问对象,那么JVM是如何通过栈帧中的对象引用访问到其内部对象实例的呢?如下图所示:
在这里插入图片描述
通常来讲,栈帧存储指向堆区中的对象地址,对象中含有该类对象的类型指针,也就是我们说的元数据指针,如果访问对象,只需要访问栈帧中的地址即可。

《Java虚拟机规范》没有对访问对象做具体的说明和要求,所以对象访问方式由虚拟机实现而定。主流有两种方式,分别是使用句柄访问和使用直接指针访问。

3.2、使用句柄访问

堆需要划分出一块内存来做句柄池,reference中存储对象的句柄池地址,句柄中包含对象实例与类型数据各自具体的地址信息,如下图所示:
在这里插入图片描述
这样做的好处是reference中存储稳定句柄地址,对象被移动(垃圾收集时移动对象很普遍)时只会改变句柄中实例数据指针,reference本身不需要被修改。但是这样做会造成多开辟一块空间来存储句柄地址,相当于是间接访问对象。

3.3、使用指针访问

reference中存储的就是对象的地址,如果只是访问对象本身的话,就不需要多一次间接访问的开销。

这样做的好处是访问速度更快,Java中对象访问频繁,每次访问都节省了一次指针定位的时间开销。HotSpot虚拟机主要使用直接指针访问的方式,如下图所示:
在这里插入图片描述
JVM可以通过对象引用准确定位到Java堆区中的对象,这样便可成功访问到对象的实例数据。JVM通过存储在对象中的元数据指针定位到存储在方法区中的对象的类型信息,即可访问目标对象的具体类型。

4、小结

讲解了多种创建对象的方式,如使用new关键字、Class的newInstance()方法、Constructor类的newInstance()方法等。紧接着讲解了创建对象的步骤,总共分为6步:第1步是判断对象对应的类是否加载、链接、初始化;第2步是为对象分配内存;第3步是处理并发安全问题;第4步是初始化分配到的空间;第5步是设置对象的对象头;第6步是执行init方法进行初始化。接下来讲解了对象的内存布局,并且使用案例讲解了对象在内存布局中的内容。最后讲解了访问对象的两种主流方式,分别是使用句柄访问和使用指针访问,其中经常使用的HotSpot虚拟机主要使用指针访问。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

前端语义化标签及实例

常用的语义化标签的以下几种&#xff1a; header、nav、article、section、aside、footer、abbr、dfn、address、del、ins、pre、meter、progress <header> 定义文章的页眉信息 <header><h1>我的网站标题</h1><nav><ul><li><a …

环保新征程:能源结构调整与臭氧污染治理|中联环保圈

新年的钟声刚刚敲响&#xff0c;全国各地的两会会议便如火如荼地展开。随着会议的密集召开&#xff0c;各地的2024年政府工作报告也相继出炉。截至2月19日&#xff0c;全国共有27个省&#xff08;自治区、直辖市&#xff09;发布了2024年政府工作报告。各地根据自身经济发展潜力…

mysql 数据库查询 查询字段用逗号隔开 关联另一个表并显示

文章目录 问题描述解决方案 问题描述 如下如所示&#xff1a; 表一&#xff1a;wechat_dynamically_config表&#xff0c;重点字段&#xff1a;wechat_object 表二&#xff1a;wechat_object表&#xff0c;重点字段&#xff1a;wxid 需求&#xff1a;根据wechat_dynamically_…

Flink StreamGraph生成过程

文章目录 概要SteramGraph 核心对象SteramGraph 生成过程 概要 在 Flink 中&#xff0c;StreamGraph 是数据流的逻辑表示&#xff0c;它描述了如何在 Flink 作业中执行数据流转换。StreamGraph 是 Flink 运行时生成执行计划的基础。 使用DataStream API开发的应用程序&#x…

【Selenium】selenium介绍及工作原理

一、Selenium介绍 用于Web应用程序测试的工具&#xff0c;Selenium是开源并且免费的&#xff0c;覆盖IE、Chrome、FireFox、Safari等主流浏览器&#xff0c;通过在不同浏览器中运行自动化测试。支持Java、Python、Net、Perl等编程语言进行自动化测试脚本编写。 官网地址&…

ROS从入门到精通4-2:Docker安装ROS、可视化仿真与终端复用

目录 0 专栏介绍1 Docker安装ROS2 Docker可视化仿真2.1 显示配置2.2 启动容器 3 终端复用工具3.1 session操作3.2 window操作3.3 pane操作3.4 其他操作 0 专栏介绍 本专栏旨在通过对ROS的系统学习&#xff0c;掌握ROS底层基本分布式原理&#xff0c;并具有机器人建模和应用ROS…

Outlook邮箱后缀如何修改?怎么添加后缀?

Outlook邮箱后缀是什么&#xff1f;Outlook邮箱后缀可以改吗&#xff1f; Outlook邮箱广泛应用于企业和个人用户之间。在使用过程中&#xff0c;有时我们可能会因为某些原因需要修改Outlook邮箱后缀。那么&#xff0c;Outlook邮箱后缀如何修改呢&#xff1f;下面&#xff0c;A…

应用程序并行配置不正确怎么办?

当出现应用程序的并行配置不正确的问题时&#xff0c;通常也无法打开目标应用程序了&#xff0c;应该如何解决此问题呢&#xff1f;下面我们一起来了解一下。 1、重装出现问题的应用 如果是某个应用程序出现问题&#xff0c;那么卸载它再进行重装是很好的方法。 具体步骤&…

手写分布式配置中心(三)增加实时刷新功能(短轮询)

要实现配置自动实时刷新&#xff0c;需要改造之前的代码。代码在https://gitee.com/summer-cat001/config-center​​​​​​​ 服务端改造 服务端增加一个版本号version&#xff0c;新增配置的时候为1&#xff0c;每次更新配置就加1。 Overridepublic long insertConfigDO(…

技术指标和振荡器大全(二)

原文&#xff1a;stockcharts.com/school/doku.php?idchart_school:technical_indicators 译者&#xff1a;飞龙 协议&#xff1a;CC BY-NC-SA 4.0 成交量加权平均价格&#xff08;VWAP&#xff09; 目录 成交量加权平均价格&#xff08;VWAP&#xff09; 介绍 Tick 与 Minu…

项目中spring security与jwt.腾讯面试分享

写这篇文章是为了记录我面试pcg时平时没有留意或者钻研的地方。 面试是根据项目问的问题&#xff1a; 为什么采用jwt存储token&#xff1f; 我的项目是微服务项目&#xff0c;里面部署了资源服务和认证服务&#xff0c;这里选择jwt作为token一方面是可以存储用户的信息&#…

【DPDK】基于dpdk实现用户态UDP网络协议栈

文章目录 一.背景及导言二.协议栈架构设计1. 数据包接收和发送引擎2. 协议解析3. 数据包处理逻辑 三.网络函数编写1.socket2.bind3.recvfrom4.sendto5.close 四.总结 一.背景及导言 在当今数字化的世界中&#xff0c;网络通信的高性能和低延迟对于许多应用至关重要。而用户态网…

C++指针(四)

个人主页&#xff1a;PingdiGuo_guo 收录专栏&#xff1a;C干货专栏 前言 相关文章&#xff1a;C指针&#xff08;一&#xff09;、C指针&#xff08;二&#xff09;、C指针&#xff08;三&#xff09; 本篇博客是介绍函数指针、函数指针数组、回调函数、指针函数的。 点赞破六…

文件批量重命名神器:按长度与区间智能管理,让文件整理更高效!

在数字化时代&#xff0c;电脑中堆积如山的文件常常让我们头疼不已。命名不规范、杂乱无章的文件不仅占用了大量的存储空间&#xff0c;更在关键时刻让我们难以迅速找到所需内容。现在&#xff0c;有了这款文件批量改名神器&#xff0c;一切烦恼将烟消云散&#xff01; 首先&a…

时隔n年再度会看Vue,Git

时隔n年再度会看Vue,Git 曾经沧海难为水&#xff0c;除却巫山不是云。不知道这句话用在这里合不合适&#xff0c;好多东西在记忆中都淡化了。但是互联网确是有记忆的。研究以前项目的时候&#xff0c;翻看到gitee码云上托管的项目&#xff0c;就像是自己的孩子重新又回来了一样…

观其大略之HybridCLR学习笔记

问题背景 1 现有热更方案的开发效率、性能没有到达极限&#xff0c;还有提升的空间 2 ios多平台政策导致热更新受限问题&#xff0c;ios禁止jit。根据我查找的资料&#xff0c;ios的代码段启动的时候就确定了&#xff0c;不能增加新的代码段。IOS封了内存&#xff08;或者堆&…

如何摆脱水印困扰?三款神器助您清爽无烦恼!

水印常常成为我们图片处理的一大难题&#xff0c;让我们苦恼不已。那么&#xff0c;如何能轻松摆脱这些烦人的水印呢&#xff1f;本文将向您推荐三款强大的去水印工具&#xff0c;让您清爽无烦恼&#xff0c;图片重焕光彩&#xff01; 1. 水印云 如何快速而准确地去除各类水印…

Stable Diffusion 解析:探寻 AI 绘画背后的科技神秘

AI 绘画发展史 在谈论 Stable Diffusion 之前&#xff0c;有必要先了解 AI 绘画的发展历程。 早在 2012 年&#xff0c;华人科学家吴恩达领导的团队训练出了当时世界上最大的深度学习网络。这个网络能够自主学习识别猫等物体&#xff0c;并在短短三天时间内绘制出了一张模糊但…

【RK3288 Android6, T8PRO 快捷按键 gpio 配置上拉输入】

文章目录 【RK3288 Android6&#xff0c; T8PRO 快捷按键 gpio 配置上拉输入】需求开发过程尝试找到没有用的上拉gpio尝试修改pwm1的gpio的默认上拉模式 改动 【RK3288 Android6&#xff0c; T8PRO 快捷按键 gpio 配置上拉输入】 需求 T8pro想要模仿T10 的 快捷按键&#xff…

嵌入式开发的常用软件、学习资源网站推荐

1、软件推荐 1.1、文本编辑软件 ——Notepad 1、适合编写和查看文本文件&#xff0c;也可以安装插件来查看二进制文件、对比文件 2、参考博客&#xff1a;《Notepad实用小技巧》&#xff1b; 1.2、PDF文件阅读软件——福昕PDF阅读器 福昕PDF阅读器&#xff0c;在官网就可以下载…