此问题是 多个 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
呵呵 对了一下 显然不对
大致的计算方式是 oop 的 nonStatic属性 占用的字节数 + 对象头[16字节]
首先这个计算 是不够精确的, 因为 会存在将 [Word, Short, Byte, Oop] 放在一些场景下产生的 gap 里面[比如 64bit 的 vm, 开启了 UseCompressedOops 之后, 对象头 对齐 16 byte 的 gap]
oop 的 nonStatic属性 占用的字节数 是读取自 dump 文件, 当前 oop 对应的记录, 根据 offset 来直接获取当前 oop 的 nonStatic属性 占用的字节数
那么 这个数据就是来自于 heapdump 生成的地方了?
以下截图基于 jdk9
如下部分是计算 oop 的 instanceSize 的地方
可以看到的是 oop 相关均是算作 8byte, 但是实际我们 64bit vm 上面 开启了 UseCompressedOops 之后仅占用 4byte
另外就是 上面提到的 可能有一部分 [Word, Short, Byte, Oop] 是被分配到 gap 里面的
综上 dump 文件中的 oop 占用空间的计算 是不够精确的, 不可信的
对象的默认内存布局 中提到了一些普通的类型的内存布局, 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
完