Java 对象头那点事
概览

对象头
存放:关于堆对象的布局、类型、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 对象头那点事的更多相关文章
- 015-线程同步-synchronized几种加锁方式、Java对象头和Monitor、Mutex Lock、JDK1.6对synchronized锁的优化实现
一.synchronized概述基本使用 为确保共享变量不会出现并发问题,通常会对修改共享变量的代码块用synchronized加锁,确保同一时刻只有一个线程在修改共享变量,从而避免并发问题. syn ...
- 盘一盘 synchronized (一)—— 从打印Java对象头说起
Java对象头的组成 Java对象的对象头由 mark word 和 klass pointer 两部分组成, mark word存储了同步状态.标识.hashcode.GC状态等等. klass ...
- 探究java对象头
探究java对象头 研究java对象头,我这里先截取Hotspot中关于对象头的描述,本文研究基于64-bit HotSpot VM 文件路径 openjdk-jdk8u-jdk8u\hotspot\ ...
- java对象头信息和三种锁的性能对比
java头的信息分析 首先为什么我要去研究java的对象头呢? 这里截取一张hotspot的源码当中的注释 这张图换成可读的表格如下 |-------------------------------- ...
- JVM源码分析之Java对象头实现
原创申明:本文由公众号[猿灯塔]原创,转载请说明出处标注 “365篇原创计划”第十一篇. 今天呢!灯塔君跟大家讲: JVM源码分析之Java对象头实现 HotSpot虚拟机中,对象在内存中的布局分为三 ...
- JAVA对象头详解(含32位虚拟机与64位虚拟机)
为什么要学习Java对象头 学习Java对象头主要是为了解synchronized底层原理,synchronized锁升级过程,Java并发编程等. JAVA对象头 由于Java面向对象的思想,在JV ...
- 并发王者课-青铜5:一探究竟-如何从synchronized理解Java对象头中的锁
在前面的文章<青铜4:synchronized用法初体验>中,我们已经提到锁的概念,并指出synchronized是锁机制的一种实现.可是,这么说未免太过抽象,你可能无法直观地理解锁究竟是 ...
- JAVA对象头
#为了防止自己忘记,先记着,之前我一直以为<深入理解JAVA虚拟机>写错了来着. 一. JAVA对象 在HotSpot虚拟机中,对象在内存中存储的布局可以分为3块区域:对象头(Header ...
- jvm源码解析java对象头
认真学习过java的同学应该都知道,java对象由三个部分组成:对象头,实例数据,对齐填充,这三大部分扛起了java的大旗对象,实例数据其实就是我们对象中的数据,对齐填充是由于为了规则分配内存空间,j ...
随机推荐
- 描述 Java 中的重载和重写?
重载和重写都允许你用相同的名称来实现不同的功能,但是重载是编译时活动, 而重写是运行时活动.你可以在同一个类中重载方法,但是只能在子类中重写方 法.重写必须要有继承.
- js技术之运用"typeof()"运算符校验变量类型
一.简介 typeof();个人的理解就是可以判断出对应的变量类型,而且是用统一的类型 如:数字,小数等..... 都用Number来表示 而:所有对象都用object表示 二.探索到 typeof的 ...
- C 语言中 static 的作用
在 C 语言中,static 的字面意思很容易把我们导入歧途,其实它的作用有三条. (1)先来介绍它的第一条也是最重要的一条:隐藏 当我们同时编译多个文件时,所有未加 static 前缀的全局变量和函 ...
- 原型模式故事链--new一个对象的过程
上一个总标题:https://segmentfault.com/a/11...提问:你有对象了吗?答:没有.笨!new一个不就好了吗! 问题点:为什么我要理解new一个对象的过程?答:不理解这个过程, ...
- 前端系列——快速理解babel6配置过程
繁琐的配置? 你能快速读懂babel的官方文档吗? 你能根据官方文档快速配置好babelrc吗? 你能明白自己需要哪些插件吗? 没有搞明白这3个问题,请往下看. 快速理解babel 6 来看一张让人颤 ...
- linux-RHEL7.0 —— 《Linux就该这么学》阅读笔记
目录 linux-RHEL7.0 安装部署 修改root密码 RPM(红帽软件包管理器) YUM(软件仓库) Systemd初始化进程 总结 linux命令 帮助命令 man 系统工作命令 echo ...
- 设置IDEA启动时不打开上次项目
步骤 1.启动IDEA,点击File 2.点击setting,在Appearance&Behavior中找到System Setting 3.取消勾选Reopen projects on st ...
- python---二叉树广度优先和深度优先遍历的实现
class Node(object): """结点""" def __init__(self, data): self.data = dat ...
- Struts2-获取值栈对象与结构
1.获取值栈对象有多种方式 (1)使用ActionContext类里面的方法获取值栈对象 @Override public String execute(){ //1.获取ActionContext类 ...
- Python-术语对照表
>>> 交互式终端中默认的 Python 提示符.往往会显示于能以交互方式在解释器里执行的样例代码之前. ... 具有以下含义: 交互式终端中输入特殊代码行时默认的 Python 提 ...