• java对象结构 对象头 Markword


    概述

    对象实例由对象头、实例数据组成,其中对象头包括markword和类型指针,如果是数组,还包括数组长度;

    | 类型 | 32位JVM | 64位JVM|
    | ------ ---- | ------------| --------- |
    | markword | 32bit | 64bit |
    | 类型指针 | 32bit |64bit ,开启指针压缩时为32bit |
    | 数组长度 | 32bit |32bit |

    header.png

    compressed_header.png

    可以看到

    • 开启指针压缩时,markword占用8bytes,类型指针占用8bytes,共占用16bytes;
    • 未开启指针压缩时,markword占用8bytes,类型指针占用4bytes,但由于java内存地址按照8bytes对齐,长度必须是8的倍数,因此会从12bytes补全到16bytes;
    • 数组长度为4bytes,同样会进行对齐,补足到8bytes;

    另外从上面的截图可以看到,开启指针压缩之后,对象类型指针为0xf800c005,但实际的类型指针为0x7c0060028;那么指针是如何压缩的呢?实际上由于java地址一定是8的倍数,因此将0xf800c005*8即可得到实际的指针0x7c0060028,关于指针压缩的更多知识可参考官方文档

    markword结构

    markword的结构,定义在markOop.hpp文件:

    1. 32 bits:
    2. --------
    3. hash:25 ------------>| age:4 biased_lock:1 lock:2 (normal object)
    4. JavaThread*:23 epoch:2 age:4 biased_lock:1 lock:2 (biased object)
    5. size:32 ------------------------------------------>| (CMS free block)
    6. PromotedObject*:29 ---------->| promo_bits:3 ----->| (CMS promoted object)
    7.  
    8. 64 bits:
    9. --------
    10. unused:25 hash:31 -->| unused:1 age:4 biased_lock:1 lock:2 (normal object)
    11. JavaThread*:54 epoch:2 unused:1 age:4 biased_lock:1 lock:2 (biased object)
    12. PromotedObject*:61 --------------------->| promo_bits:3 ----->| (CMS promoted object)
    13. size:64 ----------------------------------------------------->| (CMS free block)
    14.  
    15. unused:25 hash:31 -->| cms_free:1 age:4 biased_lock:1 lock:2 (COOPs && normal object)
    16. JavaThread*:54 epoch:2 cms_free:1 age:4 biased_lock:1 lock:2 (COOPs && biased object)
    17. narrowOop:32 unused:24 cms_free:1 unused:4 promo_bits:3 ----->| (COOPs && CMS promoted object)
    18. unused:21 size:35 -->| cms_free:1 unused:7 ------------------>| (COOPs && CMS free block)
    19. [ptr | 00] locked ptr points to real header on stack
    20. [header | 0 | 01] unlocked regular object header
    21. [ptr | 10] monitor inflated lock (header is wapped out)
    22. [ptr | 11] marked used by markSweep to mark an object
    23.  
    24.  

    由于目前基本都在使用64位JVM,此处不再对32位的结构进行详细说明:

    偏向锁标识位锁标识位锁状态存储内容
    0 01 未锁定 hash code(31),年龄(4)
    1 01 偏向锁 线程ID(54),时间戳(2),年龄(4)
    00 轻量级锁 栈中锁记录的指针(64)
    10 重量级锁 monitor的指针(64)
    11 GC标记 空,不需要记录信息

    此处,有几点要注意:

    • 如果对象没有重写hashcode方法,那么默认是调用os::random产生hashcode,可以通过System.identityHashCode获取;os::random产生hashcode的规则为:next_rand = (16807seed) mod (2*31-1),因此可以使用31位存储;另外一旦生成了hashcode,JVM会将其记录在markword中;
    • GC年龄采用4位bit存储,最大为15,例如MaxTenuringThreshold参数默认值就是15;
    • 当处于轻量级锁、重量级锁时,记录的对象指针,根据JVM的说明,此时认为指针仍然是64位,最低两位假定为0;当处于偏向锁时,记录的为获得偏向锁的线程指针,该指针也是64位;
    1. We assume that stack/thread pointers have the lowest two bits cleared.
    2. ObjectMonitor* monitor() const {
    3. assert(has_monitor(), "check");
    4. // Use xor instead of &~ to provide one extra tag-bit check.
    5. return (ObjectMonitor*) (value() ^ monitor_value);//monitor_value=2,value最右两位为10,因此异或之后最右两位为0
    6. }
    7. JavaThread* biased_locker() const {
    8. assert(has_bias_pattern(), "should not call this otherwise");
    9. return (JavaThread*) ((intptr_t) (mask_bits(value(), ~(biased_lock_mask_in_place | age_mask_in_place | epoch_mask_in_place))));
    10. //~(biased_lock_mask_in_place | age_mask_in_place | epoch_mask_in_place)为11111111111111111111110010000000,计算后的结果中,低10位全部为0;
    11. }

    由于java中内存地址都是8的倍数,因此可以理解为最低3bit为0,因此假设轻量级和重量级锁的最低2位为0是成立的;但为什么偏向锁的最低10位都是0?查看markOop.hpp文件,发现有这么一句话:

    1. // Alignment of JavaThread pointers encoded in object header required by biased locking
    2. enum { biased_lock_alignment = 2 << (epoch_shift + epoch_bits)
    3. //epoch_shift+epoch_bits=10
    4. };

    thread.hpp中重载了operator new:

    1. void* operator new(size_t size) { return allocate(size, true); }
    2.  
    3. // ======= Thread ========
    4. // Support for forcing alignment of thread objects for biased locking
    5. void* Thread::allocate(size_t size, bool throw_excpt, MEMFLAGS flags) {
    6. if (UseBiasedLocking) {
    7. const int alignment = markOopDesc::biased_lock_alignment;//10
    8. size_t aligned_size = size + (alignment - sizeof(intptr_t));
    9. void* real_malloc_addr = throw_excpt? AllocateHeap(aligned_size, flags, CURRENT_PC)
    10. : os::malloc(aligned_size, flags, CURRENT_PC);
    11. void* aligned_addr = (void*) align_size_up((intptr_t) real_malloc_addr, alignment);
    12. assert(((uintptr_t) aligned_addr + (uintptr_t) size) <=
    13. ((uintptr_t) real_malloc_addr + (uintptr_t) aligned_size),
    14. "JavaThread alignment code overflowed allocated storage");
    15. if (TraceBiasedLocking) {
    16. if (aligned_addr != real_malloc_addr)
    17. tty->print_cr("Aligned thread " INTPTR_FORMAT " to " INTPTR_FORMAT,
    18. real_malloc_addr, aligned_addr);
    19. }
    20. ((Thread*) aligned_addr)->_real_malloc_address = real_malloc_addr;
    21. return aligned_addr;
    22. } else {
    23. return throw_excpt? AllocateHeap(size, flags, CURRENT_PC)
    24. : os::malloc(size, flags, CURRENT_PC);
    25. }
    26. }

    如果开启了偏移锁,在创建线程时,线程地址会进行对齐处理,保证低10位为0

    实例数据

    实例数据中主要包括对象的各种成员变量,包括基本类型和引用类型;static类型的变量会放到java/lang/Class中,而不会放到实例数据中;
    对于引用类型的成员(包括string),存储的指针;对于基本类型,直接存储内容;通常会将基本类型存储在一起,引用类型存储在一起;
    例如类Test的成员定义如下:

    1. private static Test t1=new Test();
    2. private Test t2;
    3. private int a=5;
    4. private Integer b=7;
    5. private String c="112";
    6. private BigDecimal d=new BigDecimal("5");
    7. private long e=9l;

    body.png

    可以看到long e、int a为基本类型,存储在一起;其它的引用类型存储在一起;int占用4bytes,不足8bytes,自动补足到8bytes;

    链接:https://www.jianshu.com/p/ec28e3a59e80

  • 相关阅读:
    2019自我剖析
    jzoj4640. 【GDOI2017模拟7.15】妖怪
    jzoj4649. 【NOIP2016提高A组模拟7.17】项链
    jzoj3171. 【GDOI2013模拟4】重心
    jzoj4673. 【NOIP2016提高A组模拟7.20】LCS again
    学习计算几何基础知识小结
    学习第一类斯特林数小记
    jzoj4213. 对你的爱深不见底
    jzoj4212. 【五校联考1day2】我想大声告诉你
    jzoj3085. 图的计数
  • 原文地址:https://www.cnblogs.com/tiancai/p/12630305.html
Copyright © 2020-2023  润新知