50 jhat 中 java.lang.String 的实例占用空间为什么是 28 bytes ?
Posted 蓝风9
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了50 jhat 中 java.lang.String 的实例占用空间为什么是 28 bytes ?相关的知识,希望对你有一定的参考价值。
前言
此问题是 多个 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
// Test27MultiClassInClassloader
public static void main(String[] args) throws Exception
List<Object> 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 first
compact_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 style
if( allocation_style == 0 )
// Fields order: oops, longs/doubles, ints, shorts/chars, bytes, padded fields
next_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 VALUE
0 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 0
20 4 (loss due to the next object alignment)
Instance size: 24 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
完
参考
以上是关于50 jhat 中 java.lang.String 的实例占用空间为什么是 28 bytes ?的主要内容,如果未能解决你的问题,请参考以下文章
org.json.JSONException: Value okhttp3.internal.http.RealResponseBody@da68fa1 of type java.lang.Strin