50 jhat 中 java.lang.String 的实例占用空间为什么是 28 bytes ?
创始人
2024-03-14 13:23:40
0

前言

 

此问题是 多个 classloader 加载的同类限定名的Class 在 jhat 中显示不全d 同一时期发现的问题  

大致的情况是 看到了 jhat 中统计的各个 oop 的占用空间 似乎是不太能够对的上  

比如 java.lang.String, 在 64bit vm 上面 开启了 UseCompressedOops 之后, 应该是占用 (12 + 4 + 4) + 4[对齐] = 24, 才对 

但是 实际上在 jhat 显示的是 28 bytes 

我们这里来看一下这个问题, 顺便也可以 扩展一些扩展一些其他问题 

可以先看一下 普通对象的内存布局 对象的默认布局

以下部分内容, 截图 若无特殊说明, 基于 jdk8 

测试用例 

测试用例如下, 然后 生成 dump 文件, 使用 jhat 进行分析 

/*** Test27MultiClassInClassloader** @author Jerry.X.He <970655147@qq.com>* @version 1.0* @date 2021-12-12 14:27*/
public class Test27MultiClassInClassloader {// Test27MultiClassInClassloaderpublic static void main(String[] args) throws Exception {List refs = new ArrayList<>();ClassLoader appClassloader = Test27MultiClassInClassloader.class.getClassLoader();URL[] classpath = new URL[]{new File("/Users/jerry/IdeaProjects/HelloWorld/target/classes").toURI().toURL()};String[] alwaysParentPattern = new String[]{};for (int i = 0; i < 10; i++) {ChildFirstClassLoader classLoader = new ChildFirstClassLoader(classpath, appClassloader, alwaysParentPattern);Class userClazz = classLoader.loadClass("com.hx.test12.Test27MultiClassInClassloaderUser");System.out.println(" the " + i + "th classloader " + Integer.toHexString(userClazz.hashCode()));Object userInstance = userClazz.newInstance();refs.add(userInstance);}System.in.read();}}
 

查看 java.lang.String 的 instances 

呵呵 对了一下 显然不对 

查看 Test27MultiClassInClassloaderUser

呵呵 对了一下 显然不对 

jhat 的 instanceSize 是怎么计算的 ?

大致的计算方式是 oop 的 nonStatic属性 占用的字节数 + 对象头[16字节] 

首先这个计算 是不够精确的, 因为 会存在将 [Word, Short, Byte, Oop] 放在一些场景下产生的 gap 里面[比如 64bit 的 vm, 开启了 UseCompressedOops 之后, 对象头 对齐 16 byte 的 gap]

oop 的 nonStatic属性 占用的字节数 是读取自 dump 文件, 当前 oop 对应的记录, 根据 offset 来直接获取当前 oop 的 nonStatic属性 占用的字节数 

那么 这个数据就是来自于 heapdump 生成的地方了? 

HotspotVM 生成 dump 文件中的 instanceRecord 

 以下截图基于 jdk9 

如下部分是计算 oop 的 instanceSize 的地方 

可以看到的是 oop 相关均是算作 8byte, 但是实际我们 64bit vm 上面 开启了 UseCompressedOops 之后仅占用 4byte 

另外就是 上面提到的 可能有一部分 [Word, Short, Byte, Oop] 是被分配到 gap 里面的 

综上 dump 文件中的 oop 占用空间的计算 是不够精确的, 不可信的 

java.lang.String 的内存布局

对象的默认内存布局 中提到了一些普通的类型的内存布局, allocate_style 为 1 

    // Fields order: longs/doubles, ints, shorts/chars, bytes, oops, padded fields

java.lang.String 等一部分特殊的 InstanceKlass 的 allocate_style 是不相同的 

  // The next classes have predefined hard-coded fields offsets// (see in JavaClasses::compute_hard_coded_offsets()).// Use default fields allocation order for them.if( (allocation_style != 0 || compact_fields ) && _loader_data->class_loader() == NULL &&(_class_name == vmSymbols::java_lang_AssertionStatusDirectives() ||_class_name == vmSymbols::java_lang_Class() ||_class_name == vmSymbols::java_lang_ClassLoader() ||_class_name == vmSymbols::java_lang_ref_Reference() ||_class_name == vmSymbols::java_lang_ref_SoftReference() ||_class_name == vmSymbols::java_lang_StackTraceElement() ||_class_name == vmSymbols::java_lang_String() ||_class_name == vmSymbols::java_lang_Throwable() ||_class_name == vmSymbols::java_lang_Boolean() ||_class_name == vmSymbols::java_lang_Character() ||_class_name == vmSymbols::java_lang_Float() ||_class_name == vmSymbols::java_lang_Double() ||_class_name == vmSymbols::java_lang_Byte() ||_class_name == vmSymbols::java_lang_Short() ||_class_name == vmSymbols::java_lang_Integer() ||_class_name == vmSymbols::java_lang_Long())) {allocation_style = 0;     // Allocate oops firstcompact_fields   = false; // Don't compact fields}

allocate_style 为 0 的场景下面, oops 是放置于 longs/doubles 之前 

对于我们 java.lang.String 来说一个内存编排应该是 

1. private final char value[]; 

2. private int hash;

  int next_nonstatic_oop_offset = 0;int next_nonstatic_double_offset = 0;// Rearrange fields for a given allocation styleif( allocation_style == 0 ) {// Fields order: oops, longs/doubles, ints, shorts/chars, bytes, padded fieldsnext_nonstatic_oop_offset    = next_nonstatic_field_offset;next_nonstatic_double_offset = next_nonstatic_oop_offset +(nonstatic_oop_count * heapOopSize);}

然后我们使用常用的工具 jol 来看一下, 一个 "xx" 的 java.lang.String, 可以看到 确实如此 

java.lang.String object internals:OFFSET  SIZE     TYPE DESCRIPTION                               VALUE0     4          (object header)                           05 00 00 00 (00000101 00000000 00000000 00000000) (5)4     4          (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)8     4          (object header)                           da 02 00 f8 (11011010 00000010 00000000 11111000) (-134216998)12     4   char[] String.value                              [x, x]16     4      int String.hash                               020     4          (loss due to the next object alignment)
Instance size: 24 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

完 

参考 

对象的默认布局

相关内容

热门资讯

不能访问光猫的的管理页面 光猫是现代家庭宽带网络的重要组成部分,它可以提供高速稳定的网络连接。但是,有时候我们会遇到不能访问光...
银河麒麟V10SP1高级服务器... 银河麒麟高级服务器操作系统简介: 银河麒麟高级服务器操作系统V10是针对企业级关键业务...
【NI Multisim 14...   目录 序言 一、工具栏 🍊1.“标准”工具栏 🍊 2.视图工具...
Android|无法访问或保存... 这个问题可能是由于权限设置不正确导致的。您需要在应用程序清单文件中添加以下代码来请求适当的权限:此外...
北信源内网安全管理卸载 北信源内网安全管理是一款网络安全管理软件,主要用于保护内网安全。在日常使用过程中,卸载该软件是一种常...
AWSECS:访问外部网络时出... 如果您在AWS ECS中部署了应用程序,并且该应用程序需要访问外部网络,但是无法正常访问,可能是因为...
​ToDesk 远程工具安装及... 目录 前言 ToDesk 优势 ToDesk 下载安装 ToDesk 功能展示 文件传输 设备链接 ...
AWSElasticBeans... 在Dockerfile中手动配置nginx反向代理。例如,在Dockerfile中添加以下代码:FR...
AsusVivobook无法开... 首先,我们可以尝试重置BIOS(Basic Input/Output System)来解决这个问题。...
APK正在安装,但应用程序列表... 这个问题可能是由于以下原因导致的:应用程序安装的APK文件可能存在问题。设备上已经存在同名的应用程序...