大堆对象是如何影响程序的性能的

news2024/11/18 1:34:48

img

在本文中,我们将详细了解 JVM 如何存储对象及其在内存中的表示形式。此外,我们将深入探讨性能影响以及如何利用它们来获得优势。

*此外,我们将了解如何使用-XX:+UseCompressedOops以及它如何影响应用程序的性能。此外,我们将了解UseCompressedOops*和堆大小之间的联系。

1. 对象的大小

JVM使用以下结构在内存中表示Java对象:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 1:Java 对象布局

让我们更详细地回顾这些部分,以了解它们的用途和存储的数据。

1.1. 标记词

此部分存储对象的运行时数据。JVM 可能会在此处放置有关锁、幸存者计数、垃圾收集标记甚至哈希码的信息。 此部分的大小完全取决于 JVM 的体系结构。32 位体系结构将使用四个字节表示一个标记字,64 位体系结构将使用八个字节表示一个标记字。虽然我们无法直接更改其大小,但体系结构可能会影响应用程序的性能。

1.2. 类指针

klass [sic] 指针是指向对象类的指针。 它引用类元数据,这对很多事情都有帮助:方法调用、计算字段偏移量、内存分配、垃圾收集等。 指针本身可以接收 4 个字节或 8 个字节的信息。本节最令人兴奋的部分是我们可以使用 JVM 参数来更改它,我们将在本文后面讨论。

1.3. 数组长度

数组长度仅针对数组设置。 在任何系统上它占用四个字节。 但是,这并不意味着我们可以使用无符号整数进行索引,因为我们可以将其放入数组长度部分。每个 JVM 都可能施加其限制,尝试分配一个巨大的数组可能会导致OutOfMemoryError并显示以下消息: 请求的数组大小超出 VM 限制

1.4. 内部填充

内部填充是可选的,用于对齐对象头的大小。其大小取决于 JVM 架构。32 位架构使用四字节对齐;64 位架构默认使用八字节对齐。 我们可以改变此行为,但此讨论超出了本文的范围。

1.5. 实例字段

实例字段包含对象字段的值。此部分的大小完全取决于字段类型及其组成。JVM 可能会执行优化并重新排列字段的顺序。这称为字段打包。此过程旨在减少因填充而浪费的内存。

1.6. 外部填充

最后的填充确保下一个对象的正确对齐。有时,对象会对齐到正确的字节数;有时,我们需要额外的填充。我们可以利用这一知识,在不增加内存占用的情况下向类添加更多字段。

2. 物体的大小

让我们计算不同 JVM 架构上空对象和空数组的大小。我们将考虑对象的默认 8 字节对齐,但我们可以更改它。一般公式如下所示:

空对象的大小 = Mark Word + Klass Pointer + Padding

因此,一般来说,我们会得到以下结果:

32 位 JVM64 位 JVM
32 位指针816
64 位指针-*16

对于数组,我们有一个类似的公式,其中仅包含数组的长度:

空对象的大小 = 标记字 + 类指针 + 数组的长度 + 填充

我们可以对空数组的大小进行类似的计算:

32 位 JVM64 位 JVM
32 位指针1616
64 位指针-*24
  • 我们不能在 32 位 JVM 上使用 64 位指针。

3. 对象指针

JVM 将对象存储在堆中;要使用这些对象,我们应该能够通过它们的地址引用它们。如果我们有 4 GB 的内存,我们需要 8 字节地址,并希望单独寻址每个字节。

这是否意味着我们不能对较大的堆使用八字节地址? JVM 有一个优化,它限制对象的起始并默认将其对齐到八个字节。

通过对齐,地址的最后三位将始终为零。 因此,我们可以在存储地址时省略它们,这在技术上使我们能够将地址从 32 位压缩到 29 位。 较小的地址大小意味着我们可以将可寻址内存增加八倍(!):从 4 GB 增加到 32 GB:

指针压缩

对象起始字节数32 位地址(二进制)29 位压缩地址
000000000000000000000000000000 00000000000000000000000000000000
800000000000000000000000000001 0000000000000000000000000000000001
1600000000000000000000000000010 000000000000000000000000000000010
2400000000000000000000000000011 0000000000000000000000000000000011
三十二00000000000000000000000000100 000000000000000000000000000000100
4000000000000000000000000000101 0000000000000000000000000000000101
四十八00000000000000000000000000110 0000000000000000000000000000000110
5600000000000000000000000000111 0000000000000000000000000000000111
6400000000000000000000000001000 000000000000000000000000000001000
7200000000000000000000000001001 000000000000000000000000000001001

当我们需要压缩后的完整地址时,我们可以向左移动一位并解压缩。但是,没有什么东西是免费的,我们将在这个操作上花费 CPU 周期:

**00000000000000000000000000101 << 3 = 00000000000000000000000000101 **000

如果我们使用的堆大于 32 GB,则 JVM 默认为地址分配 64 位。因此,我们的对象的大小将会增加。同时,由于对齐规则,64 位 JMV 上的空对象的大小不会改变。

这些知识有时可能很有用,因为我们可以在不显著增加内存消耗的情况下存储更多信息。但是,不建议依赖利用填充空间的性能优势。

4. 性能影响

我们将使用具有一百万个元素的整数 链表

@NotNull
private static LinkedList<Integer> createLinkedList() {
    return new LinkedList<>(ThreadLocalRandom.current()
      .ints(ONE_MILLION)
      .boxed()
      .collect(Collectors.toList()));
}

此外,我们将有一个简单的逻辑来从这个列表中过滤和计算奇数和偶数:

private static void filterList(List<Integer> integers, Blackhole blackhole) {
    int even = 0;
    int odd = 0;
    for (final Integer integer : integers) {
        if (integer % 2 == 0) {
            even++;
        } else {
            odd++;
        }
    }
    blackhole.consume(even);
    blackhole.consume(odd);
}

4.1. 解压缩和 CPU 周期

让我们运行以下基准测试:

@Benchmark
public void filteringList(NumberFilteringState state, Blackhole blackhole) {
    filterList(state.integers, blackhole);
}

此代码几乎不产生任何垃圾,因此让我们使用不同的堆大小来运行它。我们将针对 2 GB、4 GB 和 8 GB 堆运行几个测试。从技术上讲,它不会影响性能。此外,我们将使用-XX:+AlwaysPreTouch*来避免堆大小调整问题:*

** filterList() 基准的性能分析**

堆大小压缩指针 (ops/s)未压缩指针 (ops/s)
2 GB193.990241.983
4GB194.502242.473
8 GB194.555242.344

压缩指针有利于减小对象大小,但同时需要额外的 CPU 周期来解压缩。由于我们的基准测试所做的唯一一件事就是取消引用LinkedList 中的节点 ,因此使用压缩指针时开销较大。

因此,此类应用程序的性能可能会受到严重影响。在我们的基准测试中,差异非常显著:≈194 ops/s 对 ≈242 ops/s。

问题是,当堆大小超过 4 GB 时,JVM 会自动打开它们,这是 Java 6 的默认行为。**有时,我们可以通过添加更多内存来使应用程序变慢。 **

这就是为什么我们需要更加关注堆大小管理。我们应该平衡应用程序以分配“恰到好处”的内存量。不会产生太多垃圾的应用程序在较小的堆大小上可以表现得更好。

对于较大的堆,我们不使用压缩,这样就跳过了压缩步骤。同时,由于标头占用了 8 个字节,因此内存占用也更高。

4.2. 内存占用

让我们检查另一个具有更高对象创建率的基准:

** creatingList() 基准的性能分析**

堆大小压缩指针 (ops/s)未压缩指针 (ops/s)
2 GB26.86026.379
4GB28.19926.784
8 GB28.23627.002

这里,我们得到了相反的结果。使用压缩指针的基准测试性能更高。原因是对象占用的空间更少。

4.3. 总体表现

让我们结合这两个基准来获得更合理的结果,因为应用程序通常会创建并迭代对象:

@Benchmark
public void creatingAndFilteringList(NumberFilteringState state, Blackhole blackhole) {
    List<Integer> linkedList = createLinkedList();
    filterList(linkedList, blackhole);
    blackhole.consume(linkedList);
}

该基准测试将结合我们从较小的标头中获得的内存消耗优势和解压缩所浪费的周期:

** creatingAndFilteringList() 基准的性能分析**

堆大小压缩指针 (ops/s)未压缩指针 (ops/s)
2 GB25.19124.949
4GB25.08223.610
8 GB25.17422.874

在此设置中,压缩指针和非压缩指针的行为类似。但总体而言,压缩指针具有更好的性能。对于任何应用程序,结果可能都不相同。性能影响完全取决于访问模式、对象创建率和其他因素。

5.垃圾收集

让我们检查一下垃圾收集器在之前的基准测试中的行为。我们将考虑在 8 GB 堆上运行的示例的结果。为了分析行为,我们将使用来自GCeasy的报告。

5.1. 创建率低

首先,让我们检查一下过滤基准。这个基准没有产生任何垃圾,报告也相当无聊。该应用程序在所有测试的堆大小上的行为都类似。

唯一的区别是峰值堆使用量。它主要基于初始List的大小。**因此,使用未压缩指针的版本会消耗更多内存。 **

检查报告和视觉效果没有多大意义。基准测试不会产生任何垃圾,我们使用*-Xmx、-Xms-XX:+AlwaysPreTouch*预初始化了堆,因此垃圾收集日志中唯一的一行是以下内容:

[0.005秒][info][gc]使用G1

5.2. 高创建率

在creatingList基准测试中,我们发现创建率存在显著差异。使用压缩指针时,我们将得到以下结果:

未压缩指针与压缩指针的创建率
图 2:未压缩指针与压缩指针的创建率

我们拥有更大的对象,并且创建率更高。我们可以通过检查垃圾收集活动来解释这一点。在未压缩指针的情况下,我们将拥有更多的垃圾收集周期:

未压缩指针的垃圾收集活动
图 3:未压缩指针的垃圾收集活动

虽然压缩指针需要更少的循环:

在这里插入图片描述

图 4:压缩指针的垃圾收集活动

更少的循环是合理的,因为我们需要更小的对象来填充堆。因此,使用压缩指针,我们的性能会略有提高,垃圾收集周期也会更快:

未压缩指针与压缩指针的暂停时间
图 5:未压缩指针与压缩指针的暂停时间

结论

使用压缩对象指针可以帮助提高应用程序的性能,但同时,它可能会使其运行速度变慢,有时甚至不会影响它。一切都取决于应用程序和我们的目标。

要确定 -XX:UseCompressedOops 的实际效果 我们应该分析应用程序并查看它在不同情况下的行为。了解 JVM 的内部结构可以提供更多见解,并防止使用不会产生任何影响的 JVM 标志。同时,始终可以使用yCrash检查假设并确保理论优化为我们带来真正的好处。

原文链接

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

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

相关文章

[大语言模型-论文精读] 阿里巴巴-通过多阶段对比学习实现通用文本嵌入

[大语言模型-论文精读] 阿里巴巴达摩院-GTE-通过多阶段对比学习实现通用文本嵌入 1. 论文信息 这篇论文《Towards General Text Embeddings with Multi-stage Contrastive Learning》介绍了一种新的文本嵌入模型&#xff0c;名为GTE&#xff08;General-purpose Text Embeddin…

低空经济时代:无人机飞行安全要点详解

随着低空经济的蓬勃发展&#xff0c;无人机&#xff08;UAV&#xff09;在农业、航拍、物流、应急救援等多个领域的应用日益广泛。然而&#xff0c;无人机的安全飞行不仅关乎任务的成功与否&#xff0c;更直接关系到地面人员、财产及空中交通的安全。本文将从飞行前检查、环境评…

大数据-153 Apache Druid 案例 从 Kafka 中加载数据并分析

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; 目前已经更新到了&#xff1a; Hadoop&#xff08;已更完&#xff09;HDFS&#xff08;已更完&#xff09;MapReduce&#xff08;已更完&am…

【Linux学习】【Ubuntu入门】1-2 新建虚拟机ubuntu环境

1.双击打开VMware软件&#xff0c;点击“创建新的虚拟机”&#xff0c;在弹出的中选择“自定义&#xff08;高级&#xff09;” 2.点击下一步&#xff0c;自动识别ubuntu光盘映像文件&#xff0c;也可以点击“浏览”手动选择&#xff0c;点击下一步 3.设置名称及密码后&#xf…

1Panel安装部署证书(httpsok.com)

1Panel安装部署证书(httpsok.com) 购买服务器 推荐购买香港服务器&#xff0c;这样通过域名访问就不需要备案。 创建静态站点 申请SSL证书 进入 httpsok.com&#xff0c;点击申请证书 输入站点域名 根据提示&#xff0c;添加DNS解析记录 添加成功后&#xff0c;提示域名验证…

如何在AI绘画SD中调节光照?这2个超好用的方法别错过!轻松生成AI人像光感大片!

大家好&#xff0c;我是画画的小强 在AI绘画Stable Diffusion 摄影艺术中&#xff0c;灯光的运用对于照片的质量和情感表达至关重要。它不仅能够彰显主题&#xff0c;还能为画面增添深度与立体感&#xff0c;帮助传递感情&#xff0c;以及凸显细节之美。 下面&#xff0c;我将…

YD-D3无线遥控声光报警器,微波探测预警安全设备

YD-D3无线遥控声光报警器‌是一种广泛应用于工厂车间、水泥厂、起重机、叉车、仓库、门吊、港口、车站等场所的安全报警设备。它通过大分贝喇叭播报语音提示以及高亮灯光示警&#xff0c;为现场人员安全保驾护航。该报警器采用集成电路设计&#xff0c;音质优美&#xff0c;抗干…

航顺芯片HK32MCU受邀出席汽车芯片国产化与技术创新闭门研讨会

[中国&#xff0c;北京&#xff0c;2024年9月21日]近日&#xff0c;深圳市航顺芯片技术研发有限公司&#xff08;以下简称“航顺芯片”&#xff09;产品总监郑增忠受邀出席由中国设备管理协会新能源汽车产业发展促进中心主办的“汽车芯片国产化与技术创新闭门研讨会”。 会上航…

基于单片机电容测量仪仿真设计

文章目录 前言资料获取设计介绍设计程序具体实现截图设计获取 前言 &#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计划导师&#xff0c;一名热衷于单片机技术探索与分享的博主、专注于 精通51/STM32/MSP430/AVR等单片机设计 主要对象是咱们…

Elasticsearch 8.16 和 JDK 23 中的语言环境变化

作者&#xff1a;来自 Elastic Simon Cooper 随着 JDK 23 即将发布&#xff0c;语言环境信息中有一些重大变化&#xff0c;这将影响 Elasticsearch 以及你提取和格式化日期时间数据的方式。首先&#xff0c;介绍一些背景知识。 什么是语言环境&#xff1f; 每次 Java 程序需要…

【Java】static-静态变量、静态方法、工具类、注意事项、args数组的使用

文章目录 一、静态变量特点调用方式 二、静态方法特点调用方式 三、类的类型1.Javabean类2.测试类3.工具类 四、注意事项从代码方面解释1. 上下文清晰2. 静态变量的访问例子注意 3. 静态方法中没有this关键字原因 4. 静态方法只能访问静态变量和静态方法错误原因解决方法 4.非静…

如何获取钉钉webhook

第一步打开钉钉并登录 第二步创建团队 并且 添加自定义 机器人 即可获取webhook

【流计算】流计算概论

前言 作者在之前写过一个大数据的专栏&#xff0c;包含GFS、BigTable、MapReduce、HDFS、Hadoop、LSM树、HBase、Spark&#xff0c;专栏地址&#xff1a; https://blog.csdn.net/joker_zjn/category_12631789.html?fromshareblogcolumn&sharetypeblogcolumn&sharerI…

待办事项应用SideQuests

赶在国庆长假前&#xff0c;自驾&#x1f697;出去玩了几天。 国庆前的错峰出游简直是太香了&#xff01;一路上&#x1f6e3;️畅通无阻&#xff0c;停车&#x1f17f;️不用抢&#xff0c;吃饭&#x1f354;不用等&#xff0c;景点&#x1f3de;️不用排队&#xff0c;拍照&…

Flume实战--Flume中的拦截器详解与操作

在处理大规模数据流时&#xff0c;Apache Flume 是一款功能强大的数据聚合工具&#xff0c;它可以通过拦截器在运行时对Event进行修改或丢弃。本文将详细讲解Flume中的拦截器&#xff0c;包括时间戳拦截器、Host添加拦截器、静态拦截器以及如何自定义拦截器。 拦截器 拦截器的…

《HelloGitHub》第 102 期

兴趣是最好的老师&#xff0c;HelloGitHub 让你对编程感兴趣&#xff01; 简介 HelloGitHub 分享 GitHub 上有趣、入门级的开源项目。 github.com/521xueweihan/HelloGitHub 这里有实战项目、入门教程、黑科技、开源书籍、大厂开源项目等&#xff0c;涵盖多种编程语言 Python、…

LeetCode - #124 二叉树中的最大路径和(Top 100)

文章目录 前言1. 描述2. 示例3. 答案关于我们前言 本题为 LeetCode 前 100 高频题 我们社区陆续会将顾毅(Netflix 增长黑客,《iOS 面试之道》作者,ACE 职业健身教练。)的 Swift 算法题题解整理为文字版以方便大家学习与阅读。 LeetCode 算法到目前我们已经更新到 123 期…

Electron 隐藏顶部菜单

隐藏前&#xff1a; 隐藏后&#xff1a; 具体设置代码&#xff1a; 在 main.js 中加入这行即可&#xff1a; // 导入模块 const { app, BrowserWindow ,Menu } require(electron) const path require(path)// 创建主窗口 const createWindow () > {const mainWindow ne…

Qemu开发ARM篇-6、emmc/SD卡AB分区镜像制作并通过uboot进行挂载启动

文章目录 1、AB分区镜像制作2、uboot修改3、镜像启动 在上一篇 Qemu开发ARM篇-5、buildroot制作根文件系统并挂载启动中&#xff0c;我们通过buildroot制作了根文件系统&#xff0c;并通过 SD卡的形式将其挂载到设备并成功进行了启动&#xff0c;但上一章中&#xff0c;我们的…

启动 Ntopng 服务前需先启动 redis 服务及 Ntopng 常用参数介绍

启动Ntopng服务之前需要先启动redis服务&#xff0c;因为Ntopng服务依赖于redis服务的键值存储。 服务重启 服务启动 Ntopng常用参数&#xff1a; -d 将 Ntopng 进程放入后台执行。默认情况下&#xff0c;Ntop 在前台运行。 -u 指定启动Ntopng执行的用户&#xff0c;默认为…