• Java 对象头那点事


    概览

    image


    • 对象头
      存放:关于堆对象的布局、类型、GC状态、同步状态和标识哈希码的基本信息。Java对象和vm内部对象都有一个共同的对象头格式。
      (后面做详细介绍)

    • 实例数据
      存放:类的数据信息,父类的信息,对象字段属性信息。
      如果对象有属性字段,则这里会有数据信息。如果对象无属性字段,则这里就不会有数据。
      根据字段类型的不同占不同的字节,例如boolean类型占1个字节,int类型占4个字节等等;

    • 对齐填充
      存放:为了字节对齐,填充的数据,不是必须的。
      默认情况下,Java虚拟机堆中对象的起始地址需要对齐至8的倍数。
      假如对象头大小为12,实例数据大小为5,最近且大于12+5的8的倍数值是24,则对齐补充大小为:24-12-5=7。


    为什么需要对象填充?

    ①:字段内存对齐的其中一个原因,是让字段只出现在同一CPU的缓存行中。如果字段不是对齐的,那么就有可能出现跨缓存行的字段。也就是说,该字段的读取可能需要替换两个缓存行,而该字段的存储也会同时污染两个缓存行。这两种情况对程序的执行效率而言都是不利的。其实对其填充的最终目的是为了计算机高效寻址。

    ②:在计算机系统中,内存是以【缓存行】为单位存储的,一个缓存行存储的字节是2的倍数。不同机器上,缓存行大小也不一样,通常来说为64字节。

    对象头

    mark word

    OpenJDK(JDK8)地址:https://github.com/openjdk/jdk
    根据OpenJDK 官方源码中MarkOop.hpp文件中给的注释介绍,可以大概看出mark word的组成。

    MarkOop.hpp中的注释1:

    32 bits:
    --------
               hash:25 ------------>| age:4    biased_lock:1 lock:2 (normal object)
               JavaThread*:23 epoch:2 age:4    biased_lock:1 lock:2 (biased object)
               size:32 ------------------------------------------>| (CMS free block)
               PromotedObject*:29 ---------->| promo_bits:3 ----->| (CMS promoted object)
    
    64 bits:
    --------
               unused:25 hash:31 -->| unused:1   age:4    biased_lock:1 lock:2 (normal object)
               JavaThread*:54 epoch:2 unused:1   age:4    biased_lock:1 lock:2 (biased object)
               PromotedObject*:61 --------------------->| promo_bits:3 ----->| (CMS promoted object)
               size:64 ----------------------------------------------------->| (CMS free block)
    

    MarkOop.hpp中的注释2:

        [JavaThread* | epoch | age | 1 | 01]       lock is biased toward given thread
        [0           | epoch | age | 1 | 01]       lock is anonymously biased
    
      - the two lock bits are used to describe three states: locked/unlocked and monitor.
    
        [ptr             | 00]  locked             ptr points to real header on stack
        [header      | 0 | 01]  unlocked           regular object header
        [ptr             | 10]  monitor            inflated lock (header is wapped out)
        [ptr             | 11]  marked             used by markSweep to mark an object
                                                   not valid at any other time
    

    MarkOop.hpp中的源码1:

      enum { age_bits                 = 4,
             lock_bits                = 2,
             biased_lock_bits         = 1,
             max_hash_bits            = BitsPerWord - age_bits - lock_bits - biased_lock_bits,
             hash_bits                = max_hash_bits > 31 ? 31 : max_hash_bits,
             cms_bits                 = LP64_ONLY(1) NOT_LP64(0),
             epoch_bits               = 2
      };
    

    如图:
    image


    MarkOop.hpp中的源码2:

      enum { locked_value             = 0,
             unlocked_value           = 1,
             monitor_value            = 2,
             marked_value             = 3,
             biased_lock_pattern      = 5
      };
    
    • locked_value
      轻量级锁状态值,mark word 最后2位为00,转为10进制为0。
    • unlocked_value
      无锁状态值,mark word 最后3位为001,转为10进制为1。
    • monitor_value
      重量级锁状态值,mark word 最后2位为10,转为10进制为2。
    • marked_value
      mark word 最后2位为11,转为10进制为3。
      作用比较复杂,
      1:当锁升级为重量级锁的过程中,会将markword设置为这个值。
      2:当对象GC时也要使用这个值。

    markOop.hpp部分源码如下:

      // 仅用于存储到Lock Record中,用来表示锁正在使用重量级监视器(轻量级锁膨胀为重量级锁之前会这么做)
      static markOop unused_mark() {
        return (markOop) marked_value;
      }
    
      // age operations
      markOop set_marked()   { return markOop((value() & ~lock_mask_in_place) | marked_value); }
      markOop set_unmarked() { return markOop((value() & ~lock_mask_in_place) | unlocked_value); }
    
    • biased_lock_pattern
      偏向锁状态值,mark word 最后3位为101,转为10进制为5。

    markOop.cpp中还有以下代码,用以判断当前markword处于哪种锁状态:

      // 轻量级锁
      bool is_locked()   const {
        return (mask_bits(value(), lock_mask_in_place) != unlocked_value);
      }
      // 偏向锁
      bool is_unlocked() const {
        return (mask_bits(value(), biased_lock_mask_in_place) == unlocked_value);
      }
      // marked
      bool is_marked()   const {
        return (mask_bits(value(), lock_mask_in_place) == marked_value);
      }
      // 无锁
      bool is_neutral()  const { return (mask_bits(value(), biased_lock_mask_in_place) == unlocked_value); }
      // 膨胀时 markOop 的特殊临时状态。 在锁外查看标记的代码需要考虑到这一点。
      bool is_being_inflated() const { return (value() == 0); }
      // 锁对象处于升级为重量级锁的过程中
      static markOop INFLATING() { return (markOop) 0; }
    

    为什么对象头分代占4bit
    因为对象经过15次GC就会被放入老年代,而15转化为二进制就是1111,干好占4bit.


    epoch的作用
    抄自于:http://www.itqiankun.com/article/bias-lock-epoch-effect

    其本质是一个时间戳,代表了偏向锁的有效性,epoch存储在可偏向对象的MarkWord中。

    ①:除了对象中的epoch,对象所属的类class信息中,也会保存一个epoch值。

    ②:每当遇到一个全局安全点时(这里的意思是说批量重偏向没有完全替代了全局安全点,全局安全点是一直存在的),比如要对class C 进行批量再偏向,则首先对 class C中保存的epoch进行增加操作,得到一个新的epoch_new。

    ③:然后扫描所有持有 class C 实例的线程栈,根据线程栈的信息判断出该线程是否锁定了该对象,仅将epoch_new的值赋给被锁定的对象中,也就是现在偏向锁还在被使用的对象才会被赋值epoch_new。

    ④:退出安全点后,当有线程需要尝试获取偏向锁时,直接检查 class C 中存储的 epoch 值是否与目标对象中存储的 epoch 值相等, 如果不相等,则说明该对象的偏向锁已经无效了(因为(3)步骤里面已经说了只有偏向锁还在被使用的对象才会有epoch_new,这里不相等的原因是class C里面的epoch值是epoch_new,而当前对象的epoch里面的值还是epoch),此时竞争线程可以尝试对此对象重新进行偏向操作。

    klass point

    元数据指针class pointer,即指向方法区的instanceKlass实例(虚拟机通过这个指针来群定这个对象是哪个类的实例)。

    oop.hpp中的源码:

    class oopDesc {
      friend class VMStructs;
     private:
      volatile markOop  _mark;
      union _metadata {
        Klass*      _klass;
        narrowKlass _compressed_klass;
      } _metadata;
    

    length field

    该属性只有数组对象才有,用以表示数组的长度。

    arrayOop.hpp中有这么一段注释:

    // The layout of array Oops is:
    //
    //  markOop
    //  Klass*    // 32 bits if compressed but declared 64 in LP64.
    //  length    // shares klass memory or allocated after declared fields.
    

    总结

    可能面试的时候会被问到这个问题:为什么一个对象可以当成一把锁?
    这方面可以与上文中提到的对象头、markword 进行回答即可。

  • 相关阅读:
    RHEL6中LVM逻辑卷管理
    Linux配置iSCSI存储
    Linux中FTP服务器配置
    360浏览器兼容模式下IE内核版本
    Ocelot + Consul + Registrator 基于Docker 实现服务发现、服务自动注册
    电视接入系统页面、监控页面
    Registrator中文文档
    关于Skyline沿对象画boundingbox的探讨
    EntityFramework实体默认值遇到Oracle自增主键
    C# 6.0 11个新特性
  • 原文地址:https://www.cnblogs.com/zgq7/p/16250149.html
Copyright © 2020-2023  润新知