创造与布局:剖析 Java 对象创建过程以及内存布局

news2025/2/24 12:05:54

在这里插入图片描述
目录

  • 前言
  • 创建对象过程
  • 对象布局
    • 普通对象
    • 数组对象
    • 如何观察 Object 大小
    • 对象头组成部分
  • 对象如何定位
  • 对象如何分配
  • 总结

前言

上下文提及到了类的加载过程,详细介绍了加载类的每个阶段:Loading、Linking、Initialize,在其中也说明了静态变量赋值顺序 > 先赋予默认值、在 Initialize 初始化阶段赋予初始值

从类加载到双亲委派:深入解析类加载机制与 ClassLoader

该篇文章会详细实例对象的创建过程、对象如何布局、对象头包括哪些内容以及对象如何定位、分配等

创建对象过程

创建对象的过程如下:

  1. 创建一个对象:new Obj(),第一步把 class 文件 loading 进内存中
  2. 第二步:Linking 链接阶段,分为三部分进行:Verification 校验文件是否符合 class 格式(CAFE BABY)、Preparation 将类的静态变量赋默认值、Resolution 将符号引用解析为直接引用
  3. 第三步:Initialize 初始化阶段将类的静态变量设置初始值,同时执行静态代码块语句
  4. 申请对象内存,成员变量赋默认值
  5. 调用构造方法:成员变量按顺序赋初始值、执行构造方法的代码块

创建对象需要预先申请好内存,成员变量再赋默认值,然后再调用构造方法时,先会将成员变量按顺序进行赋予初始值,再后来才调用构造方法 > 第一句先通过 super() 方法调用父类

对象布局

观察虚拟机默认配置:java -XX:+PrintCommandLineFlags -version

在这里插入图片描述

-XX:InitialHeapSize=23891840 -XX:MaxHeapSize=382269440 -XX:+PrintCommandLineFlags -XX:+UseCompressedClassPointers -XX:+UseCompressedOops

作为内存布局的对象分为两种:
1、普通对象
2、数组对象

在这里插入图片描述

普通对象

普通对象内部有几个重要的概念组成部分,如下:

  1. 对象头:包含了 Mark Word 标记字及 Klass Pointer 类型指针,长度为 8 字节

Mark Word 标记字,长度为 8 字节
ClassPointer 指针,开启:-XX:+UseCompressedClassPointers 参数后会压缩为 4 字节,不开启为 8 字节

  1. 实例数据,开启:-XX:+UseCompressedOops 参数后,开启时引用类型为 4 字节,不开启时为 8 字节
  2. 填充对齐,Padding 填充必须是 8 的整数倍

数组对象

数组对象内部有几个重要的概念组成部分,如下:

  1. 对象头:包含了 Mark Word 标记字及 Klass Pointer 类型指针,同普通对象一样
  2. 数组长度占用为 4 字节
  3. 数组数据
  4. 填充对齐,Padding 填充必须是 8 的整数倍

数组对象对比普通对象多了数组长度

如何观察 Object 大小

1、创建一个 Agent 代理类,获取对象大小

import java.lang.instrument.Instrumentation;

/**
 * @author vnjohn
 * @since 2023/06/26
 */
public class ObjectSizeAgent {
    /**
     * Java 内部字节码处理调试叫为 Instrumentation(调弦),所以我们在代理装到我们 JVM 时候可以截获这个 Instrumentation
     */
    private static Instrumentation inst;

    /**
     * 必须要有 premain 函数参数也是固定的,第二个就是 Instrumentation
     * 这个是虚拟机调用的它会帮我们初始化 instrumentation,所以调用 getObjectSize 方法才不会空指针
     */
    public static void premain(String agentArgs,Instrumentation _inst){
        inst = _inst;
    }

    public static long sizeOf(Object o){
        return inst.getObjectSize(o);
    }
}

2、在 META-INF 目录下,创建 MANIFEST.MF 文件,设置版本、主函数调用前要执行的方法,如下:

Manifest-Version: 1.0
Created-By: vnjohn
Premain-Class: com.vnjohn.jvm.agent.ObjectSizeAgent

3、在 maven build 节点下引入自定义的 MANIFEST 配置,如下:

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-jar-plugin</artifactId>
            <configuration>
                <archive>
                    <manifestFile>src/META-INF/MANIFEST.MF</manifestFile>
                </archive>
            </configuration>
        </plugin>
    </plugins>
</build>

4、将其打包成 jar 包,再其他存在主函数的项目中引入该依赖,如下:

<dependency>
    <groupId>org.vnjohn</groupId>
    <artifactId>object-size-agent</artifactId>
    <version>1.0.0-SNAPSHOT</version>
</dependency>

5、编写测试类代码(可自行调整你想知道某个类的大小)先调整启动参数,如下:

-javaagent:/Users/vnjohn/repository/org/vnjohn/object-size-agent/1.0.0-SNAPSHOT/object-size-agent-1.0.0-SNAPSHOT.jar
/**
 * @author vnjohn
 * @since 2023/6/26
 */
public class SizeOfAnObject {
  public static void main(String[] args) {
    System.out.println(ObjectSizeAgent.sizeOf(new Object()));
    System.out.println(ObjectSizeAgent.sizeOf(new int[]{}));
    System.out.println(ObjectSizeAgent.sizeOf(new OrdinaryObject()));
  }

  // 一个 Object 占多少个字节
  // Oops = ordinary object pointers  普通对象指针
  private static class OrdinaryObject {
    // 8 _markword
    // 4 _class pointer
    int id;         // 4
    String name;    // 4
    int age;        // 4

    byte b1;        // 1
    byte b2;        // 1

    Object o;       // 4
    byte b3;        // 1
  }
}

具体更多的源码及介绍,可以阅览 GitHub 仓库:object-size-agent.git

当我们要评估临时扩容方案时,都需要计算好对应实体所具体要占用的大小,以此来作为测试基准,做规划来定下最终要扩容的服务器大小及数量!!!

对象头组成部分

对象头组成部分,可以看 HotSpot 内核源码中 Mark Word 结构,在 markOop.hpp 文件中,截取一部分源码的注释部分,如下:

//  32 bits:
//  --------
//             hash:25 ------------>| age:4    biased_lock:1 lock:2 (normal object)
//             JavaThread*:23 epoch:2 age:4    biased_lock:1 lock:2 (biased object)
//             size:32 ------------------------------------------>| (CMS free block)
//             PromotedObject*:29 ---------->| promo_bits:3 ----->| (CMS promoted object)
//
//  64 bits:
//  --------
//  unused:25 hash:31 -->| unused:1   age:4    biased_lock:1 lock:2 (normal object)
//  JavaThread*:54 epoch:2 unused:1   age:4    biased_lock:1 lock:2 (biased object)
//  PromotedObject*:61 --------------------->| promo_bits:3 ----->| (CMS promoted object)
//  size:64 ----------------------------------------------------->| (CMS free block)
//
//  unused:25 hash:31 -->| cms_free:1 age:4    biased_lock:1 lock:2 (COOPs && normal object)
//  JavaThread*:54 epoch:2 cms_free:1 age:4    biased_lock:1 lock:2 (COOPs && biased object)
//  narrowOop:32 unused:24 cms_free:1 unused:4 promo_bits:3 ----->| (COOPs && CMS promoted object)
//  unused:21 size:35 -->| cms_free:1 unused:7 ------------------>| (COOPs && CMS free block)

以 64 bits 位为例,如下表:

在这里插入图片描述

为什么 GC 年龄默认设置为 15?

当我们使用 PS 垃圾回收器时,它默认的从年轻代升到老年代的年龄为 15,因为只有 4 bit 来表示分代年龄,最大二进制为:1111=8+4+2+1=15,所以说最大年龄也就只有 15

对象如何定位

当 new 出来一个对象,比如:new OrdinaryObject(),那么这个对象是如何被定位到的呢?

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

句柄:若使用句柄访问的话,Java 堆中将会划分一块内存来作为句柄池,reference 存储的就是对象的句柄地址,而句柄中包含了对象实例数据、类型数据各自的地址信息

在这里插入图片描述

直接指针:若使用直接指针访问的话,Java 堆中对象的内存布局就必须考虑如何放置类型数据的相关信息,reference 中直接存储的就是对象地址,若只是访问对象本身而不是对象类型数据的话,那么就不需要多一次的间接访问开销

在这里插入图片描述

这两种对象定位方式各有优劣,如下:

  1. 使用句柄来访问的最大好处就是 reference 中存储的是稳定的句柄地址,在对象被移动(垃圾收集时移动对象是非常普遍的行为)时只会改变句柄中的实例数据指针,而 reference 本身不需要被修改

GC 回收时效率会比较高

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

对象如何分配

在这里插入图片描述

图解过程,如下:

  1. 当 new 一个对象时,先往栈上分配,若栈上能分配就分配在栈上,当栈上弹出后对象就没了

作用于方法作用域下的对象,非本地缓存

  1. 若栈上无法分配,判断对象是否足够大,对象特别大时直接分配到堆中的老年代中
  2. 若对象不足够大,优先进行线程本地分配(TLAB:Thread Local Allocation Buffer)线程本地缓冲区能分配下就进行分配
  3. 线程本地缓冲区分配不下就分配到伊甸区,然后进行 GC 回收过程
  4. GC 分代年龄到了(最大为 15)直接进入到老年代,若年龄不到将一直在年轻代中 YGC 来 YGC 去,直接对象被回收或者年龄足够满足到达老年代

以上的过程就是所谓的对象分配

总结

该篇博文讲解了创建对象过程的几个核心步骤,剖析了对象内部是如何布局 > 普通对象、数组对象,通过了一个简单的案例来如何统计一个 Object 所占的字节大小,对象头组成部分:Mark Word、Class Pointer,介绍了对象定位的两种方式:句柄池、直接指针,最后,简要说明了对象如何分配的过程;希望你能喜欢,帮助到你是莫过于最开心的事了!

博文放在 Java 专栏里,欢迎订阅,会持续更新!

如果觉得博文不错,关注我 vnjohn,后续会有更多实战、源码、架构干货分享!

推荐专栏:Spring、MySQL,订阅一波不再迷路

大家的「关注❤️ + 点赞👍 + 收藏⭐」就是我创作的最大动力!谢谢大家的支持,我们下文见!

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

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

相关文章

【无标题】asdasd

【4】Ubuntu网络图标消失 sudo service network-manager stop ----->停止网络服务 sudo rm /var/lib/NetworkManager/NetworkManager.state ----->删除配置文件 sudo service network-manager start ----->重启网络服务 sudo vim /etc/NetworkManager/NetworkMana…

Linux RPM包安装、卸载和升级(rpm命令)详解

下面讲解一下&#xff0c;如何使用 rpm 命令对 RPM 二进制包进行安装、卸载和升级操作。我们以安装 apache 程序为例。 RPM包默认安装路径 通常情况下&#xff0c;RPM 包采用系统默认的安装路径&#xff0c;所有安装文件会按照类别分散安装到下表所示的目录中。 RPM 包默认安…

Mybatis实现品牌数据的增删改查

项目目录如下图所示。Mapper包用于存储对数据库进行操作的Mapper接口文件&#xff0c;本文中通过注释的方式对sql语句进行编写。pojo包中存放实体类文件&#xff0c;文件中包含Brand对象相关字段的定义以及get、set、toString方法。service包中存放service层文件&#xff0c;调…

最优化方法(基于lingo)之 整数规划问题求解(3/6)

一、实验目的&#xff1a; 1. 掌握分支定界法原理。整数规划求解的分枝定界法&#xff0c;首先确定目标函数的一个初始上下界&#xff0c;然后通过逐步分支使上界减小&#xff0c;下界增大&#xff0c;直到两者相等时&#xff0c;就求出了最优值和最优解。 2. 掌握用数学软件求…

pdf可以转excel格式吗?分享两个快速转换方法给大家!

PDF文档常用于存储和共享信息&#xff0c;但在需要编辑或分析数据时&#xff0c;将PDF转换为Excel格式是一个常见需求。本文将向您介绍两种快速转换PDF为Excel格式的方法&#xff0c;让您轻松解决数据提取难题。无论您是处理大量数据还是需要转换复杂表格&#xff0c;这些方法都…

Ubuntu安装和配置ssh保姆教程

配置ssh常常遇到一些问题&#xff0c;接下来是避免踩坑&#xff0c;快速配置ssh 相信大家在设置ssh时&#xff0c;会碰到"Permission denied, please try again." ⚠️敲黑板&#xff1a; 有可能是密码输入错误 有可能是防火墙未关闭 有可能是连接机器的root用户没…

spring boot使用swagger简明笔记

1、什么是swagger swagger就是一个在你写接口的时候自动帮你生成接口文档的东西&#xff0c;只要你遵循它的规范并写一些接口的说明注解即可。 本文springboot版本&#xff1a;2.5.6 2、引入依赖 <!-- swagger --><dependency><groupId>io.springfox</gr…

【论文阅读】Level-S2fM:神经隐式表面水平集上的SfM

【论文阅读】Level-S2fM&#xff1a;神经隐式表面水平集上的SfM Abstract1. Introduction2. Related Works2.1. Structure from Motion2.2. Neural Implicit Representation for 3D Scene 3. Preliminaries3.1. Neural Implicit Surface Rendering3.2. Ray Sampling and Sphere…

JavaScript—DomApi

DomApi &#x1f50e;Dom&#x1f50e;Dom树&#x1f50e;Dom—querySelector(获取元素)&#x1f50e;事件事件的三要素 &#x1f50e;操作元素获取 / 修改元素内容获取 / 修改元素属性获取 / 修改表单元素属性获取 / 修改样式属性修改内联样式修改元素应用的 CSS 类名 &#x…

Scrapy框架--settings配置 (详解)

目录 settings配置 官网-参考配置 配置文档 Scrapy默认BASE设置 settings配置 Scrapy框架中的配置文件&#xff08;settings.py&#xff09;是用来管理爬虫行为和功能的关键部分。它是一个Python模块&#xff0c;提供了各种配置选项&#xff0c;可以自定义和控制爬虫的行为。…

02【存储引擎、索引】

文章目录 一、存储引擎1.1 查看存储引擎1.2 修改默认存储引擎1.3 常见存储引擎1.4 存储引擎的特点1.4.1 InnoDB 存储引擎1.4.2 MyISAM 存储引擎1.4.2.1 MyISAM与InnoDB对比1.4.2.2 批量插入性能测试1.4.2.3 MyISAM压缩表 1.4.3 Merge 存储引擎1.4.4 Memory 存储引擎 二、索引2.…

kafka入门,发送原理和生产者重要参数(三)

发送原理 在消息发送过程中&#xff0c;涉及两个线程&#xff0c;main线程和Sender线程。在main线程中创建了一个双端队列&#xff0c;RecordAccumulator,Sender过程不断从RecordAccumulator中拉取消息发送到Kafka Broker batch size:只有数据累计到batch.size之后&#xff0…

C++ 哈希思想应用 位图 布隆过滤器 海量数据处理

文章目录 问题引入位图&#xff08;附C模拟实现源码&#xff09;布隆过滤器&#xff08;附C模拟实现源码&#xff09; 问题引入 问题&#xff1a; 给40亿个不重复的无符号整数&#xff0c;没排过序。给一个无符号整数&#xff0c;如何快速判断一个数是否在这40亿个数中。 问题…

连续6年霸榜全球工作站市场,Dell Precision凭什么?

前段时间&#xff0c;IDC发布2022 Q4工作站市场报告&#xff0c;戴尔拿下2022年工作站市场出货量和行业占比的双料冠军&#xff0c;且成为全年唯一一家份额增长的供应商。 至此&#xff0c;Dell Precision已连续6年蝉联世界第一。 根据IDC发布的2022年第四季度全球工作站追踪…

第四十章 开发Productions - ObjectScript Productions - 定义企业消息库

文章目录 第四十章 开发Productions - ObjectScript Productions - 定义企业消息库概述定义 Message Bank服务器添加Message Bank Helper类关于Message Bank的注意事项 第四十章 开发Productions - ObjectScript Productions - 定义企业消息库 概述 Enterprise Message Bank …

初识运营,明晰运营的学习路径

关于运营的思考 问题1&#xff1a;运营是什么&#xff1f;运营到底是做什么工作的&#xff1f; 如题&#xff1a;到底什么是运营&#xff1f;为什么我们所接触到的很多运营都不太一样&#xff0c;有的运营就是每天追寻互联网热点&#xff0c;加班加点的写文案&#xff1b;有的…

考研算法32天:桶排 【桶排序】

算法介绍 桶排 举个例子&#xff0c;一个数组中的数是&#xff1a;4 1 2 3 5&#xff0c; 然后桶排的顺序是&#xff1a;将每个数应该在的下标算出来&#xff0c;咋算呢&#xff1f;这我们就得考虑两种情况&#xff1a;假设我们设现在这个需要找到自己在数组里位置的数是x。…

自学黑客(网络安全),一般人我劝你还是算了吧(自学网络安全学习路线--第七章 数据库安全)【建议收藏】

文章目录 一、自学网络安全学习的误区和陷阱二、学习网络安全的一些前期准备三、自学网络安全学习路线一、数据库安全概述1、数据库的安全特性 二、数据库中的数据保护2、数据库加密3、数据库完整性保护 三、数据备份与恢复1、数据库备份2、数据库恢复 四、SQL Servre数据库安全…

vue写法——使用js高阶函数实现多条件搜索功能

&#x1f642;博主&#xff1a;爱学习的Akali king &#x1f642;本文核心&#xff1a;vue写法——使用js高阶函数实现多条件搜索功能 目录 类比一下react写法用vue写法来实现&#xff0c;思路步骤&#xff1a;第一步&#xff1a;准备数据第二步&#xff1a;根据数据结构渲染Do…

ModaHub魔搭社区:向量数据库Milvus性能调优教程(一)

目录 性能调优 插入性能调优 查询性能调优 硬件环境 系统参数 性能调优 插入性能调优 “数据插入”到“数据写入磁盘”的基本流程请参考 存储操作。 如果数据量小于单次插入上限&#xff08;256 MB&#xff09;&#xff0c;批量插入比单条插入要高效得多。 系统配置中…