CPU|java面试——常见的锁和锁升级( 四 )


对象头用于存储对象的元数据信息
分为两块:1.存储对象自身的运行时数据 , 如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁等 。
2. *类型指针* , 指向它的类元数据的指针 , 用于判断对象属于哪个类的实例 , 比如person类 , 对象头就存储着这个对象的类型的指针 , 另外 , 如果对像是一个数组 , 那在对象头中还必须有一块用于记录<u>数组长度</u> 的数据

实例数据
实例数据部分是对象真正存储的有效信息 , 也是在程序代码中所定义各种类型的字段内容
对齐填充
对齐填充并不是必然存在的 , 由于hotspot VM的自动内存管理系统要求对象起始地址必须是8字节的整数倍 , 换句话 , 就是对象的大小必须是8字节的整数倍 。 而对象头正好是8字节的倍数 。 因此 , 当对象实例数据部分没有对齐时 , 就需要通过对齐填充来补全
拿一个空的object对象来说:
一个空的object对象 , 占多少字节?
  1. 开启classPoint的压缩情况下:
    因为java jvm本身开启了classPoint的压缩 , 总的占16个字节 ,
markWord 8个字节
jvm 在64位操作系统中占8个字节 , 默认开启了classpoint的压缩 , 压缩完占4个字节 。
8+4=12不能被整除 , 则补位对齐4个字节 , 总的是16字节。
  1. 如果没有开启压缩其实也是占16个字节
    markWord 8个字节
    classPoint没有压缩 8个字节
    没有成员变量 0字节
    右对齐吗 , 没有 , 两个因为16已经可以被整除了 , 所以总的也是占16个字节
拓展:USER对象 , 有两个属性 , 在内存中占多少字节?
User user = new User();
user.name=\"hdx\";
user.age=16;
//打印实例内部的占用
System.out.println(ClassLayout.parseInstance(user).toPrintable());
印证一下对象上的锁 , 锁的具体位置
在对象的markword上面
一个对象经过一个GC之后分代计数就会增加1 , 当大于当前垃圾回收器的阈值之后就会到老年代 , cms回收器默认是6次
所以markword里面也记录了GC的信息
上面要注意锁标志位的code
锁升级的过程锁的种类:无锁——>偏向锁——>轻量级锁(自旋锁)——>重量级锁
锁概念的解释:
偏向锁(01)经过HotSpot的作者大量的研究发现 , 大多数时候是不存在锁竞争的 , 常常是一个线程多次获得同一个锁 , 因此如果每次都要竞争锁会增大很多没有必要付出的代价 , 为了降低获取锁的代价 , 才引入的偏向锁 , 不加锁岂不是更好 , 为啥要多此一举?
比如A对象 , 他在获取锁的时候并没有去申请操作系统那个比较重的锁 , 而是把当前线程指针的Id放到markword中 , 作为一个标记 ,
偏向锁的适用场景
始终只有一个线程在执行同步块 , 在它没有执行完释放锁之前 , 没有其它线程去执行同步块 , 在锁无竞争的情况下使用 , 一旦有了竞争就升级为轻量级锁 , 升级为轻量级锁的时候需要撤销偏向锁 , 撤销偏向锁的时候会导致stop the word操作; 在有锁的竞争时 , 偏向锁会多做很多额外操作 , 尤其是撤销偏向所的时候会导致进入安全点 , 安全点会导致stw , 导致性能下降 , 这种情况下应当禁用
偏向锁的获取过程
  1. 访问Mark Word中偏向锁位的标识是否设置成1 , 锁标志位是否为01(未锁定时的标志位) , 确认为可偏向状态


    推荐阅读