JVM常用概念之对象对齐

devtools/2025/3/22 2:48:00/

问题

对象对齐有什么规范吗?对象对齐是8个字节吗?

基础知识

许多硬件实现要求对数据的访问是对齐的,即确保所有 N 字节宽度的访问都在 N 的整数倍的地址上完成。即使对于普通的数据访问没有特别要求,特殊操作(特别是原子操作)通常也有对齐约束。

例如,x86 通常可以接受未对齐的读取和写入,同时跨越两个缓存行的未对齐 CAS 仍然有效,但它会降低吞吐量性能。其他架构会直接拒绝执行此类原子操作,从而产生SIGBUS或其他硬件异常。x86 也不保证跨越多个缓存行的值的访问原子性,当访问未对齐时,这种情况是可能发生的。另一方面,Java 规范要求大多数类型都具有访问原子性,并且绝对要求所有volatile访问都具有访问原子性。

因此,如果 Java 对象中有long字段,并且它在内存中占用 8 个字节,那么出于性能原因,我们必须确保它按 8 个字节对齐。如果该字段是volatile ,那么出于正确性原因,也必须这样做。简单来说,要使这一点成立,需要发生两件事:对象内部的字段偏移量应按 8 个字节对齐,并且对象本身应按 8 个字节对齐。如果我们查看java.lang.Long实例,就会看到这一点:

$ java -jar jol-cli.jar internals java.lang.Long
# Running 64-bit HotSpot VM.
# Using compressed oop with 3-bit shift.
# Using compressed klass with 3-bit shift.
# Objects are 8 bytes aligned.
# Field sizes by type: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]
# Array element sizes: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]java.lang.Long object internals:OFFSET  SIZE   TYPE DESCRIPTION              VALUE0     4        (object header)          01 00 00 004     4        (object header)          00 00 00 008     4        (object header)          ce 21 00 f812     4        (alignment/padding gap)16     8   long Long.value               0
Instance size: 24 bytes
Space losses: 4 bytes internal + 0 bytes external = 4 bytes total

这里, value字段本身的偏移量为 16(它是 8 的倍数),并且对象按 8 对齐。

即使没有需要特殊处理的字段,仍然有对象头需要原子访问。从技术上讲,可以将大多数 Java 对象按 4 个字节对齐,而不是按 8 个字节对齐,但实现这一点所需的运行时工作量非常巨大。

因此,在 Hotspot 中,最小对象对齐是 8 个字节。但是它可以更大吗?当然可以,VM 选项可以实现这一点: -XX:ObjectAlignmentInBytes 。它会带来两个后果,一个是负面的,一个是正面的。

实例大小变大

一旦对齐变大,就意味着每个对象浪费的平均空间也会增加。例如,对象对齐增加到 16 和 128 字节:

$ java -XX:ObjectAlignmentInBytes=16 -jar jol-cli.jar internals java.lang.Longjava.lang.Long object internals:OFFSET  SIZE   TYPE DESCRIPTION                               VALUE0     4        (object header)                           01 00 00 004     4        (object header)                           00 00 00 008     4        (object header)                           c8 10 01 0012     4        (alignment/padding gap)16     8   long Long.value                                024     8        (loss due to the next object alignment)
Instance size: 32 bytes
Space losses: 4 bytes internal + 8 bytes external = 12 bytes total
$ java -XX:ObjectAlignmentInBytes=128 -jar jol-cli.jar internals java.lang.Longjava.lang.Long object internals:OFFSET  SIZE   TYPE DESCRIPTION                               VALUE0     4        (object header)                           01 00 00 004     4        (object header)                           00 00 00 008     4        (object header)                           a8 24 01 0012     4        (alignment/padding gap)16     8   long Long.value                                024   104        (loss due to the next object alignment)
Instance size: 128 bytes
Space losses: 4 bytes internal + 104 bytes external = 108 bytes total

每个实例 128 个字节,但只有 8 个字节的有用数据,这里又会有明显的内存空间的浪费。

压缩引用阈值发生偏移

通过将引用移几位来在大于 4 GB 的堆上启用压缩引用。移位的长度取决于引用中有多少低位为零。也就是说,对象是如何对齐的!默认情况下,使用 8 字节对齐,3 个低位为零,我们移位 3 位,得到 2 ( 32 + 3 ) 字节 = 32 G B 2^ {(32+3)}字节 = 32 GB 2(32+3)字节=32GB的压缩引用可寻址空间。使用 16 字节对齐,我们有 2 ( 32 + 4 ) 字节 = 64 G B 2^{(32+4)}字节 = 64 GB 2(32+4)字节=64GB的压缩引用堆!

实验

测试用例

对象对齐会使实例大小膨胀,从而增加堆占用率,但允许在更大的堆上使用压缩引用,从而减少堆占用率!这些因素会相互抵消吗?取决于堆的结构。我们可以使用之前使用的相同测试,尝试找出容纳给定数量对象的最小堆。

源码

import java.io.*;
import java.util.*;public class CompressedOopsAllocate {static final int MIN_HEAP = 0 * 1024;static final int MAX_HEAP = 100 * 1024;static final int HEAP_INCREMENT = 128;static Object[] arr;public static void main(String... args) throws Exception {if (args.length >= 1) {int size = Integer.parseInt(args[0]);arr = new Object[size];IntStream.range(0, size).parallel().forEach(x -> arr[x] = new byte[(x % 20) + 1]);return;}String[] opts = new String[]{"","-XX:-UseCompressedOops","-XX:ObjectAlignmentInBytes=16","-XX:ObjectAlignmentInBytes=32","-XX:ObjectAlignmentInBytes=64",};int[] lastPasses = new int[opts.length];int[] passes = new int[opts.length];Arrays.fill(lastPasses, MIN_HEAP);for (int size = 0; size < 3000; size += 30) {for (int o = 0; o < opts.length; o++) {passes[o] = 0;for (int heap = lastPasses[o]; heap < MAX_HEAP; heap += HEAP_INCREMENT) {if (tryWith(size * 1000 * 1000, heap, opts[o])) {passes[o] = heap;lastPasses[o] = heap;break;}}}System.out.println(size + ", " + Arrays.toString(passes).replaceAll("[\\[\\]]",""));}}private static boolean tryWith(int size, int heap, String... opts) throws Exception {List<String> command = new ArrayList<>();command.add("java");command.add("-XX:+UnlockExperimentalVMOptions");command.add("-XX:+UseEpsilonGC");command.add("-XX:+UseTransparentHugePages"); // faster this waycommand.add("-XX:+AlwaysPreTouch");          // even faster this waycommand.add("-Xmx" + heap + "m");Arrays.stream(opts).filter(x -> !x.isEmpty()).forEach(command::add);command.add(CompressedOopsAllocate.class.getName());command.add(Integer.toString(size));Process p = new ProcessBuilder().command(command).start();return p.waitFor() == 0;}
}

运行结果

在堆容量可达 100+ GB 的大型机器上运行此测试将产生可预测的结果。让我们从平均对象大小开始来设置叙述。请注意,这些是该特定测试中的平均对象大小,该测试分配了大量小的byte[]数组。如下图所示:

在这里插入图片描述
增加对齐确实会使平均对象大小膨胀:16 字节和 32 字节对齐已成功“稍微”增加了对象大小,而 64 字节对齐则使平均值大幅增加。请注意,对象对齐基本上说明了最小对象大小,一旦最小值增加,平均值也会增加。

压缩引用通常会在 32 GB 左右失败。但请注意,更高的对齐会延长这一时间,对齐越高,失败所需的时间就越长。例如,16 字节对齐会在压缩引用中发生 4 位移位,并在 64 GB 左右失败。32 字节对齐将发生 5 位移位,并在 128 GB 左右失败。 [ 3 ]在此特定测试中,在某些对象计数中,由于更高对齐导致的对象大小膨胀与由于压缩引用处于活动状态而导致的较低占用空间相平衡。当然,当压缩引用最终被禁用时,对齐成本就会赶上来。

在“最小堆大小”图中可以更清楚地看到它:
在这里插入图片描述
在这里,我们清楚地看到了 32 GB 和 64 GB 的失败阈值。请注意,在某些配置中,16 字节和 32 字节对齐占用的堆更少,这得益于更高效的引用编码。这种改进并不普遍:当 8 字节对齐足够或压缩引用失败时,更高的对齐会浪费内存。

结论

对象对齐是一件有趣的事情。虽然它大大增加了对象大小,但一旦压缩引用出现,它也可以降低整体占用空间。有时,稍微增加对齐是有意义的,以获得占用空间的好处。然而,在许多情况下,这会降低整体占用空间。需要仔细研究给定的应用程序和给定的数据集,以确定增加对齐是否有用。


http://www.ppmy.cn/devtools/169048.html

相关文章

强大的AI网站推荐(第一集)—— Devv AI

网站&#xff1a;Devv AI 号称&#xff1a;最懂程序员的新一代 AI 搜索引擎 博主评价&#xff1a;我的大学所有的代码都是使用它&#xff0c;极大地提升了我的学习和开发效率。 推荐指数&#xff1a;&#x1f31f;&#x1f31f;&#x1f31f;&#x1f31f;&#x1f31f;&#x…

图论——Prim算法

53. 寻宝(第七期模拟笔试) 题目描述 在世界的某个区域,有一些分散的神秘岛屿,每个岛屿上都有一种珍稀的资源或者宝藏。国王打算在这些岛屿上建公路,方便运输。 不同岛屿之间,路途距离不同,国王希望你可以规划建公路的方案,如何可以以最短的总公路距离将 所有岛屿联通…

基于生成对抗网络(GAN)的图像超分辨率重建:技术与应用

图像超分辨率重建(Super-Resolution, SR)是计算机视觉领域的重要任务,旨在从低分辨率图像中恢复出高分辨率图像。这一技术在医学影像、卫星图像、视频增强等领域具有广泛的应用价值。传统的超分辨率方法依赖于插值或基于模型的重建,效果有限。近年来,生成对抗网络(GAN)通…

Java 单例模式与线程安全

Java 单例模式与线程安全 单例模式&#xff08;Singleton Pattern&#xff09;是设计模式中最简单且常用的模式之一&#xff0c;它确保一个类只有一个实例&#xff0c;并提供一个全局访问点。单例模式在许多场景中非常有用&#xff0c;例如配置管理、线程池、缓存等。然而&…

蓝桥杯嵌入式组第十二届省赛题目解析+STM32G431RBT6实现源码

文章目录 1.题目解析1.1 分而治之&#xff0c;藕断丝连1.2 模块化思维导图1.3 模块解析1.3.1 KEY模块1.3.2 LED模块1.3.3 LCD模块1.3.4 TIM模块1.3.5 UART模块1.3.5.1 uart数据解析 2.源码3.第十二届题目 前言&#xff1a;STM32G431RBT6实现嵌入式组第十二届题目解析源码&#…

网络安全设备配置与管理-实验4-防火墙AAA服务配置

实验4-p118防火墙AAA服务配置 从这个实验开始&#xff0c;每一个实验都是长篇大论&#x1f613; 不过有好兄弟会替我出手 注意&#xff1a;1. gns3.exe必须以管理员身份打开&#xff0c;否则ping不通虚拟机。 win10虚拟机无法做本次实验&#xff0c;必须用学校给的虚拟机。首…

《数字图像处理》第三章 灰度变换与空间滤波学习笔记(3.1-3.2)反转、对数、幂律、分段线性等变换

请注意:笔记内容片面粗浅&#xff0c;请读者批判着阅读&#xff01; 3.1 背景知识&#xff1a;空间域处理基础 核心概念解析 空间域定义 空间域指图像平面本身&#xff0c;其处理直接作用于像素矩阵&#xff08;区别于频率域的变换处理&#xff09;。 数学表达式&#xff1a;…

Spring MVC 接口数据

访问路径设置 RequestMapping("springmvc/hello") 就是用来向handlerMapping中注册的方法注解! 秘书中设置路径和方法的对应关系&#xff0c;即RequestMapping("/springmvc/hello")&#xff0c;设置的是对外的访问地址&#xff0c; 路径设置 精准路径匹…