在 HotSpot 虚拟机中,一个对象在内存中存储的布局可以分为三块区域:对象头(Object Header)、实例数据(Instance Data)和对齐填充(Padding)。

当我们在 Java 代码中,使用 new 关键字创建一个对象的时候,JVM 会为这个对象创建一个对应的 instanceOopDesc 对象,这个 instanceOopDesc 对象中包含了对象头(Header)以及实例数据。

instanceOopDesc 对象的结构1

2

3

4

5

6

7

8

9

10

11

12class{

friend class VMStructs;

friend class JVMCIVMStructs;

private:

volatile markOop _mark;

// 元数据

union _metadata {

// 对应的Klass对象

Klass* _klass;

narrowKlass _compressed_klass;

} _metadata;

对象头(Object Header)结构

而对象头(Object Header)包括两部分数据:Mark Word(标记字段)用于存储对象自身的运行时数据,如哈希码(HashCode)、GC 分代年龄、锁状态标志、线程持有的锁、偏向线程 ID、偏向时间戳等等。它是实现轻量级锁和偏向锁的关键。

对应于 instanceOopDesc 对象中的 _mark 字段。

Klass Pointer(类型指针)是对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。

对应于 instanceOopDesc 对象中的 _metadata 字段,而 _metadata 字段包含一个普通 _klass 和一个压缩后的 _compressed_klass。

如果这个对象是数组对象的话,还会有一个额外的部分用于存储数组的长度。

Java 对象头长度

在 32 位虚拟机中,Java 对象头一般占有两个机器码( 1 个机器码等于 4 字节,也就是 32 bit),1

2

3

4

5|--------------------------------------------------------------|

| Object Header (64 bits) |

|------------------------------------|-------------------------|

| Mark Word (32 bits) | Klass Word (32 bits) |

|------------------------------------|-------------------------|

但是如果对象是数组类型,则需要三个机器码(即 96 bit),因为 JVM 可以通过 Java 对象的元数据信息确定 Java 对象的大小,但是无法从数组的元数据来确认数组的大小,所以用一块来记录数组长度。1

2

3

4

5|---------------------------------------------------------------------------------|

| Object Header (96 bits) |

|--------------------------------|-----------------------|------------------------|

| Mark Word(32bits) | Klass Word(32bits) | array length(32bits) |

|--------------------------------|-----------------------|------------------------|

Mark Word(标记字段)

对 Mark Word(标记字段)的设计方式上,非常像网络协议报文头:将Mark Word(标记字段)划分为多个比特位区间,并在不同的对象状态下赋予比特位不同的含义。

Mark Word(标记字段)在 32 位 JVM 中的长度是32bit,在 64 位 JVM 中长度是64bit。

Mark Word(标记字段)用于存储对象自身的运行时数据,如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程 ID、偏向时间戳等等。

对象头信息是与对象自身定义的数据无关的额外存储成本,但是考虑到虚拟机的空间效率,Mark Word被设计成一个非固定的数据结构以便在极小的空间内存存储尽量多的数据,它会根据对象的状态复用自己的存储空间,也就是说,Mark Word会随着程序的运行发生变化,变化状态如下(32位虚拟机):

锁的状态

锁的状态总共有四种:无锁状态、偏向锁、轻量级锁和重量级锁。随着锁的竞争,锁可以从偏向锁升级到轻量级锁,再升级的重量级锁(但是锁的升级是单向的,也就是说只能从低到高升级,不会出现锁的降级)。JDK 1.6中默认是开启偏向锁和轻量级锁的,我们也可以通过-XX:-UseBiasedLocking来禁用偏向锁。

JVM一般是这样使用锁和 Mark Word 的:

1,当一个对象没有被当成锁时,这就是一个普通的对象,Mark Word记录对象的HashCode,锁标志位是01,是否偏向锁那一位是0。

2,当对象被当做同步锁,并有一个线程A抢到了锁时,锁标志位还是01,但是否偏向锁那一位改成1,前23bit记录抢到锁的线程id,表示进入偏向锁状态。

3,当线程A再次试图来获得锁时,JVM发现同步锁对象的标志位是01,是否偏向锁是1,也就是偏向状态,Mark Word中记录的线程id就是线程A自己的id,表示线程A已经获得了这个偏向锁,可以执行同步锁的代码。

4,当线程B试图获得这个锁时,JVM发现同步锁处于偏向状态,但是Mark Word中的线程id记录的不是B,那么线程B会先用CAS操作试图获得锁,这里的获得锁操作是有可能成功的,因为线程A一般不会自动释放偏向锁。如果抢锁成功,就把Mark Word里的线程id改为线程B的id,代表线程B获得了这个偏向锁,可以执行同步锁代码。如果抢锁失败,则继续执行步骤5。

5,偏向锁状态抢锁失败,代表当前锁有一定的竞争,偏向锁将升级为轻量级锁。JVM会在当前线程的线程栈中开辟一块单独的空间,里面保存指向对象锁Mark Word的指针,同时在对象锁Mark Word中保存指向这片空间的指针。上述两个保存操作都是CAS操作,如果保存成功,代表线程抢到了同步锁,就把Mark Word中的锁标志位改成00,可以执行同步锁代码。如果保存失败,表示抢锁失败,竞争太激烈,继续执行步骤6。

6,轻量级锁抢锁失败,JVM会使用自旋锁,自旋锁不是一个锁状态,只是代表不断的重试,尝试抢锁。从JDK1.7开始,自旋锁默认启用,自旋次数由JVM决定。如果抢锁成功则执行同步锁代码,如果失败则继续执行步骤7。

7,自旋锁重试之后如果抢锁依然失败,同步锁会升级至重量级锁,锁标志位改为10。在这个状态下,未抢到锁的线程都会被阻塞。

Klass Pointer(类型指针)

对象头(Header)的另外一部分是类型指针,即是对象指向它的类的元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。并不是所有的虚拟机实现都必须在对象数据上保留类型指针,换句话说查找对象的元数据信息并不一定要经过对象本身。另外,如果对象是一个Java数组,那在对象头中还必须有一块用于记录数组长度的数据,因为虚拟机可以通过普通Java对象的元数据信息确定Java对象的大小,但是从数组的元数据中无法确定数组的大小。

Reference

java jvm对象_【Java】JVM相关推荐

  1. java private 对象_[Java笔记]类的所有构造器都是private权限,就一定没有办法实例化它的对象了么?...

    笔者以前学过C++语言.众所周知,C++也是一门面向对象程序设计语言.还记得当时在大学的时候,老师讲过这样的话:类的构造函数不应该设置成private权限,这样的话还怎么去实例化类的对象?当时也信以为 ...

  2. java new 删除吗,java泛型对象初始化-java泛型对象会实例化吗T t=new T()

    java泛型对象初始化--java泛型对象能实例化吗T t=new T() java中没法得到泛型参数化类型,因为在编译期没法确定泛型参数化类型,也就找不到对应的类字节码文件,自然就不行了 泛型反射的 ...

  3. java线程堆栈_深入JVM剖析Java的线程堆栈

    在这篇文章里我将教会你如何分析JVM的线程堆栈以及如何从堆栈信息中找出问题的根因.在我看来线程堆栈分析技术是Java EE产品支持工程师所必须掌握的一门技术.在线程堆栈中存储的信息,通常远超出你的想象 ...

  4. jre包括jvm和java核心类库_包含JVM标准实现及Java核心类库

    包含JVM标准实现及Java核心类库 点击次数:1533  更新日期:2013-03-24 "青花瓷Java版"为北京师范大学教育学部蔡苏作词原创,覆盖教育技术学院专业选修课< ...

  5. java内存 海子_[转]JVM的内存区域划分

    学过C语言的朋友都知道C编译器在划分内存区域的时候经常将管理的区域划分为数据段和代码段,数据段包括堆.栈以及静态数据区.那么在Java语言当中,内存又是如何划分的呢? 由于Java程序是交由JVM执行 ...

  6. java是先有类还是先有对象_[Java]先有Class还是先有Object?

    先有鸡还是先有蛋 这个问题让我想到了一个古老的问题,先有鸡还是先有蛋. 回到本题 下面让我们详细看一下这个问题: 在Java的对象模型中: 所有的类都是Class类的实例,Object是类,那么Obj ...

  7. java robot 对象_用Java Robot对象实现服务器屏幕远程监视

    用Java Robot对象实现服务器屏幕远程监视 作者:李鲁群 摘要: 有时候,在Java应用程序开发中,如:远程监控或远程教学,常常需要对计算机的屏幕进行截取,由于屏幕截取是比较接近操作系统的操作, ...

  8. java通用对象_有效的Java –所有对象通用的方法

    java通用对象 所有对象共有的方法(第3章) 这是Joshua Blochs的有效Java第3章的简短摘要.我仅包含与自己相关的项目. 一般 等值合约将等价关系描述为: x.equals(null) ...

  9. java 注销变量_[ Java学习基础 ] Java对象的创建和销毁

    类实例化可生成对象,实例方法就是对象方法,实例变量就是对象属性.一个对象的生命周期包括三个阶段:创建.使用和销毁. 创建对象 创建对象包括两个步骤:声明和实例化. 1.声明 声明对象与声明普通变量没有 ...

最新文章

  1. 交换机运维需要注意哪些问题,让我们一起来闲聊下
  2. 试试这个文字冒险游戏,故事是AI写的:情节丰满逻辑不乱,进去就出不来了,在线可玩...
  3. 【Cracking the Code Interview(5th edition)】一、数组与字符串(C++)
  4. 用delphi 做服务器,basic4android 实现三层数据交互
  5. java启动监听器报错_JAVA通过JDBC连接Oracle数据库详解【转载】
  6. flex常见问题归纳
  7. Java和U3D比较,Unity热更方案 ILRuntime 和 toLua的比较
  8. MPLS virtual private network PE-CE之间的路由协议(BGP)
  9. 惊爆!一行正则表达式引发的 CPU 惨案
  10. 微信小程序图片上传uploadfile失败
  11. python matting后如何设置透明背景
  12. 还在花冤枉钱找人做电子签名?看这儿,教你制作纯手写电子签名
  13. RabbitMQ与Erlang的版本对应关系
  14. 【转载】Java 14都快出来了,为什么还有那么多人执着于Java 8?
  15. 综述 | 最新双曲深度神经网络综述论文
  16. python调用百度地图api定位_python 调用百度地图地点检索webAPI
  17. 【目录】王爽《汇编语言》
  18. 简单爬虫爬取头像,妈妈再也不用担心我头像不够用了
  19. java+mysql简单实现点赞评论转发帖子
  20. 软件测试人员一定要会的微信小程序测试点

热门文章

  1. pyinstaller 打包
  2. A Full Hardware Guide to Deep Learning
  3. hdu 2795(单点改动)
  4. htmlbuilder php phantomjs
  5. vim编辑器的设置文件
  6. 在.net中读写XML方法的总结[转]
  7. 关于switch-case问题
  8. CentOS7 DNS的添加
  9. CentOS 8 正式发布
  10. Java泛型总结---基本用法,类型限定,通配符,类型擦除