概览


  • 对象头

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

    (后面做详细介绍)

  • 实例数据

    存放:类的数据信息,父类的信息,对象字段属性信息。

    如果对象有属性字段,则这里会有数据信息。如果对象无属性字段,则这里就不会有数据。

    根据字段类型的不同占不同的字节,例如boolean类型占1个字节,int类型占4个字节等等;

  • 对齐填充

    存放:为了字节对齐,填充的数据,不是必须的。

    默认情况下,Java虚拟机堆中对象的起始地址需要对齐至8的倍数。

    假如对象头大小为12,实例数据大小为5,最近且大于12+5的8的倍数值是24,则对齐补充大小为:24-12-5=7。


为什么需要对象填充?

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

对象头

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
};

如图:


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 进行回答即可。

Java 对象头那点事的更多相关文章

  1. 015-线程同步-synchronized几种加锁方式、Java对象头和Monitor、Mutex Lock、JDK1.6对synchronized锁的优化实现

    一.synchronized概述基本使用 为确保共享变量不会出现并发问题,通常会对修改共享变量的代码块用synchronized加锁,确保同一时刻只有一个线程在修改共享变量,从而避免并发问题. syn ...

  2. 盘一盘 synchronized (一)—— 从打印Java对象头说起

    Java对象头的组成 Java对象的对象头由 mark word 和  klass pointer 两部分组成, mark word存储了同步状态.标识.hashcode.GC状态等等. klass  ...

  3. 探究java对象头

    探究java对象头 研究java对象头,我这里先截取Hotspot中关于对象头的描述,本文研究基于64-bit HotSpot VM 文件路径 openjdk-jdk8u-jdk8u\hotspot\ ...

  4. java对象头信息和三种锁的性能对比

    java头的信息分析 首先为什么我要去研究java的对象头呢? 这里截取一张hotspot的源码当中的注释 这张图换成可读的表格如下 |-------------------------------- ...

  5. JVM源码分析之Java对象头实现

    原创申明:本文由公众号[猿灯塔]原创,转载请说明出处标注 “365篇原创计划”第十一篇. 今天呢!灯塔君跟大家讲: JVM源码分析之Java对象头实现 HotSpot虚拟机中,对象在内存中的布局分为三 ...

  6. JAVA对象头详解(含32位虚拟机与64位虚拟机)

    为什么要学习Java对象头 学习Java对象头主要是为了解synchronized底层原理,synchronized锁升级过程,Java并发编程等. JAVA对象头 由于Java面向对象的思想,在JV ...

  7. 并发王者课-青铜5:一探究竟-如何从synchronized理解Java对象头中的锁

    在前面的文章<青铜4:synchronized用法初体验>中,我们已经提到锁的概念,并指出synchronized是锁机制的一种实现.可是,这么说未免太过抽象,你可能无法直观地理解锁究竟是 ...

  8. JAVA对象头

    #为了防止自己忘记,先记着,之前我一直以为<深入理解JAVA虚拟机>写错了来着. 一. JAVA对象 在HotSpot虚拟机中,对象在内存中存储的布局可以分为3块区域:对象头(Header ...

  9. jvm源码解析java对象头

    认真学习过java的同学应该都知道,java对象由三个部分组成:对象头,实例数据,对齐填充,这三大部分扛起了java的大旗对象,实例数据其实就是我们对象中的数据,对齐填充是由于为了规则分配内存空间,j ...

随机推荐

  1. Spring根据路径前缀获取不同Resource

    相关文章:https://www.jianshu.com/p/5bab9e03ab92 官方文档:https://docs.spring.io/spring/docs/current/spring-f ...

  2. 谷歌浏览器postman插件安装,亲测可用

    将谷歌浏览器进入扩展程序,将crx文件拖入即可. https://pan.baidu.com/s/1rIEe9RSby5EgTkygSx_dDA 百度云链接: https://pan.baidu.co ...

  3. Python学习--21天Python基础学习之旅(Day03、Day04)

    关于缩进问题,缩进几个空格都不影响程序解释(不会报错什么的),但一般缩进四个空格是为了可读性和规范. Day03: Chapter 5 1.if语句 1.1条件测试:值为True或False的表达式成 ...

  4. 攻防世界NewsCenter

    NewsCenter 打开题目是一个搜索框我们首先尝试一下sql注入 判断了一下是使用''进行包裹的字符型sql注入 然后我们需要判断数据库列数 1' order by 3# 回显正常但by4的时候回 ...

  5. poj_2386_dfs

    描述 由于最近的一场雨,农夫john的田地里很多地方流入了水,由一个N*M的矩形表示.每个方格要么有水(W)要么是干的(.).农夫想要知道他的田地里形成了多少池塘. 一个池塘由有水的方块相连,每个方块 ...

  6. 谈谈我认识的js原型

    众所周知,JavaScript中是没有传统类的概念的,js通过原型链的方式实现继承.原型是js学习中的一大重点知识,在ES6出来之前,因为js不像php.java一样拥有类的写法,所以继承方式也就不像 ...

  7. Ubuntu系统报错The system is running in low-graphics mode

    Ubuntu系统报错:The system is running in low-graphics mode 我遇到过两次这种请况,这次解决了.很nice! 在csdn上搜到的大部分操作是: 鼠标进入系 ...

  8. kafka生产者调优手册

    目录 第一章 kafka硬件配置选择 1.1 场景说明 1.2 服务器台数选择 1.3 磁盘选择 1.4 内存选择(堆内存,页缓存) 1.4.1 堆内存配置 1.4.2 页缓存选择 1.5 cpu选择 ...

  9. 模拟FCFS调度算法(先来先服务)

    文章目录 一.FCFS的介绍 二.代码演示 三.代码分析 1.使用节点模拟进程 2.SimulateFCFS(核心模拟FCFS类) 3.创建一个节点为n的队列(模拟就绪队列) 4.核心计算分析 5.输 ...

  10. 痞子衡嵌入式:大话双核i.MXRT1170之单独在线调试从核工程的方法(IAR篇)

    大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家分享的是i.MXRT1170下单独在线调试从核工程的方法(基于IAR). 两年前痞子衡写过一篇<双核i.MXRT1170之Cortex-M ...