• 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 进行分析 

    1. /**
    2. * Test27MultiClassInClassloader
    3. *
    4. * @author Jerry.X.He <970655147@qq.com>
    5. * @version 1.0
    6. * @date 2021-12-12 14:27
    7. */
    8. public class Test27MultiClassInClassloader {
    9. // Test27MultiClassInClassloader
    10. public static void main(String[] args) throws Exception {
    11. List<Object> refs = new ArrayList<>();
    12. ClassLoader appClassloader = Test27MultiClassInClassloader.class.getClassLoader();
    13. URL[] classpath = new URL[]{
    14. new File("/Users/jerry/IdeaProjects/HelloWorld/target/classes").toURI().toURL()
    15. };
    16. String[] alwaysParentPattern = new String[]{};
    17. for (int i = 0; i < 10; i++) {
    18. ChildFirstClassLoader classLoader = new ChildFirstClassLoader(classpath, appClassloader, alwaysParentPattern);
    19. Class userClazz = classLoader.loadClass("com.hx.test12.Test27MultiClassInClassloaderUser");
    20. System.out.println(" the " + i + "th classloader " + Integer.toHexString(userClazz.hashCode()));
    21. Object userInstance = userClazz.newInstance();
    22. refs.add(userInstance);
    23. }
    24. System.in.read();
    25. }
    26. }

    查看 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 是不相同的 

    1. // The next classes have predefined hard-coded fields offsets
    2. // (see in JavaClasses::compute_hard_coded_offsets()).
    3. // Use default fields allocation order for them.
    4. if( (allocation_style != 0 || compact_fields ) && _loader_data->class_loader() == NULL &&
    5. (_class_name == vmSymbols::java_lang_AssertionStatusDirectives() ||
    6. _class_name == vmSymbols::java_lang_Class() ||
    7. _class_name == vmSymbols::java_lang_ClassLoader() ||
    8. _class_name == vmSymbols::java_lang_ref_Reference() ||
    9. _class_name == vmSymbols::java_lang_ref_SoftReference() ||
    10. _class_name == vmSymbols::java_lang_StackTraceElement() ||
    11. _class_name == vmSymbols::java_lang_String() ||
    12. _class_name == vmSymbols::java_lang_Throwable() ||
    13. _class_name == vmSymbols::java_lang_Boolean() ||
    14. _class_name == vmSymbols::java_lang_Character() ||
    15. _class_name == vmSymbols::java_lang_Float() ||
    16. _class_name == vmSymbols::java_lang_Double() ||
    17. _class_name == vmSymbols::java_lang_Byte() ||
    18. _class_name == vmSymbols::java_lang_Short() ||
    19. _class_name == vmSymbols::java_lang_Integer() ||
    20. _class_name == vmSymbols::java_lang_Long())) {
    21. allocation_style = 0; // Allocate oops first
    22. compact_fields = false; // Don't compact fields
    23. }

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

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

    1. private final char value[]; 

    2. private int hash;

    1. int next_nonstatic_oop_offset = 0;
    2. int next_nonstatic_double_offset = 0;
    3. // Rearrange fields for a given allocation style
    4. if( allocation_style == 0 ) {
    5. // Fields order: oops, longs/doubles, ints, shorts/chars, bytes, padded fields
    6. next_nonstatic_oop_offset = next_nonstatic_field_offset;
    7. next_nonstatic_double_offset = next_nonstatic_oop_offset +
    8. (nonstatic_oop_count * heapOopSize);
    9. }

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

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

    完 

    参考 

    对象的默认布局

  • 相关阅读:
    学习记录十六
    Toxel 与 PCPC II
    【Node.js】—基本知识点总结
    支付宝内部打开h5页面报错
    Python中Collections模块OrderedDict用法
    Curator实现zookeeper分布式锁
    【前端CSS】网站都变成灰色了,它是怎么实现的?(含源代码解析)
    AXI协议详解(7)-响应信号
    学习笔记-SSTI
    python制作自己的专属二维码
  • 原文地址:https://blog.csdn.net/u011039332/article/details/121888858