JVM对象到底长啥样?synchronize长啥样?

解释

在计算机世界里,所有的数据(data)都是在二进制形式存储的,这些二进制数可以表示我们人类社会中的各种信息,比如文本,十进制数据,浮点数,等等。
那这里边有一个问题,计算机自己是如何识别这些信息的呢?


explain.jpg

简单回答是计算机本身不能识别,这需要程序设计人员的解释
如上图所示,当我们写入一个二进制时,我们问计算机这里的二进制数据是什么?它是无法回答的,如何想得到正确的答案,还需要另一部分数据,就是元数据(数据的描述信息),一般我们的计算机操作提令里会隐含着包含这些信息,比如iadd(int 类型相加)。
类似的元数据在计算机底层有很多。举个例子:
一个二进制位只有两个模式,这两个模式只能是0或是1,而这两个模式都有可能是数据本身,比如一个文件只有一位,文件内容是0或是1都是正常的,那么这个位有没有被占用,用当前位就无法表示了,需要另外一部分数据来描述这个位,这也可以理解为元数据。
这也是磁盘区块管理及内存页框的基本原理。
当然磁盘和内存不会一次只操作一位,磁盘的基本存储单位是扇区。
所有盘面上的同一磁道构成一个圆柱,通常称做柱面(Cylinder),每个圆柱上的磁头由上而下从“0”开始编号。数据的读/写按柱面进行,即磁 头读/写数据时首先在同一柱面内从“0”磁头开始进行操作,依次向下在同一柱面的不同盘面即磁头上进行操作,只在同一柱面所有的磁头全部读/写完毕后磁头才转移到下一柱面,因为选取磁头只需通过电子切换即可,而选取柱面则必须通过机械切换。

disk.jpeg

关于磁盘和文件系统管理详见
https://www.jianshu.com/p/5f77b221165e
推荐冬瓜哥《大话存储》

Java Object

JAVA 对象一般都是复杂对象,那么和上述基本原理一致,数据本身会存在一个地方(堆),那么对于复杂对象的访问,要知道对象的某个field是在什么位置,具体数据是什么?这些元数据也存在一个地方(堆 方法区),对对象的引用也应该保存起来。因为JVM GC的时侯需要遍历所有引用以便回收,这个对象用OOPMAP(详见周志明《深入理解JAVA虚拟机第二版》根枚举遍历)结构存储。

句柄访问
指针访问

GC Root OopMap 分析

mark word

JAVA对象除以上内容之外还有一个非常重要的部分就是mark word
The object header consists of a mark word and a klass pointer.
对象头包括markworkklass pointer

Object Header

The mark word has word size (4 byte on 32 bit architectures, 8 byte on 64 bit architectures) and
mark work 与机器的字长一致,32位是4个字节,64位是8个字节

the klass pointer has word size on 32 bit architectures. On 64 bit architectures the klass pointer either has word size, but can also have 4 byte if the heap addresses can be encoded in these 4 bytes.
64位下klass也可以通过压缩指针的方式变成4个字节,JVM参数为UseCompressedOops
This optimization is called "compressed oops" and you can also control it with the option UseCompressedOops.

原文参考
https://stackoverflow.com/questions/26357186/what-is-in-java-object-header
以下截至open jdk markOop.hpp

/ Bit-format of an object header (most significant first, big endian layout below):
//
//  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)
//
//  unused:25 hash:31 -->| cms_free:1 age:4    biased_lock:1 lock:2 (COOPs && normal object)
//  JavaThread*:54 epoch:2 cms_free:1 age:4    biased_lock:1 lock:2 (COOPs && biased object)
//  narrowOop:32 unused:24 cms_free:1 unused:4 promo_bits:3 ----->| (COOPs && CMS promoted object)
//  unused:21 size:35 -->| cms_free:1 unused:7 ------------------>| (COOPs && CMS free block)

实际占位情况

通过JOL 可以查看对象的实际占用情况
源码如下

 public static class A {
        private boolean a;
    }
# Running 64-bit HotSpot VM.
# Using compressed oop with 3-bit shift.
# Using compressed klass with 3-bit shift.
# WARNING | Compressed references base/shifts are guessed by the experiment!
# WARNING | Therefore, computed addresses are just guesses, and ARE NOT RELIABLE.
# WARNING | Make sure to attach Serviceability Agent to get the reliable addresses.
# Objects are 8 bytes aligned.
# Field sizes by type: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]
# Array element sizes: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]

**** Fresh object
org.openjdk.jol.samples.JOLSample_14_FatLocking$A object internals:
 OFFSET  SIZE      TYPE DESCRIPTION                               VALUE
      0     4           (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)
      4     4           (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4           (object header)                           16 f0 00 f8 (00010110 11110000 00000000 11111000) (-134156266)
     12     1   boolean A.a                                       false
     13     3           (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 3 bytes external = 3 bytes total

Using compressed oop with 3-bit shift?这句话是什么意思?
为什么压缩指针要移3位 先补补脑
https://wiki.openjdk.java.net/display/HotSpot/CompressedOops
https://stackoverflow.com/questions/25120546/trick-behind-jvms-compressed-oops

  • on modern computer architectures, memory addresses are byte addresses,
    在现在计算机的体系结构中,内存地址一般指向字节的,意思就是说一个字节一个地址。
  • Java object references are addresses that point to the start of a word。
    那么在jvm中,对象的引用指向的是字的起始位置。(32位4个字节,64位8个字节,这也是为什么java对象要8个字节对齐 下文要引用)
  • on a 64-bit machine, word alignment means that that the bottom 3 bits of an object reference / address are zero
    在64位机器上,字对齐意识站后3位变成0 (2的三次幂=8)
  • so, by shifting an address 3 bits to the right, we can "compress" up to a 35 bits of a 64 bit address into a 32-bit word,
    所以右移三位,我们可以将64位的35位压缩到32位的字中(相对于64位是半字(half-word))。
  • and, decompression can be done by shifting 3 bits to the left, which puts those 3 zero bits back,
    那么解压缩的时侯再左移三位,也就是在后三位添0.
  • 35 bits of addressing allows us to represent object pointers for up to 32 GB of heap memory using compressed oops that fit in 32-bit (half-)words on a 64-bit machine.
    35位(实际是32位)的地址可以引用32GB的堆内存空间。
    Using compressed oop with 3-bit shift
    就是上文提到的8个字节对齐

Field sizes by type: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]

 out.printf("# %-19s: %d, %d, %d, %d, %d, %d, %d, %d, %d [bytes]%n",
                "Field sizes by type",
                oopSize,
                sizes.booleanSize,
                sizes.byteSize,
                sizes.charSize,
                sizes.shortSize,
                sizes.intSize,
                sizes.floatSize,
                sizes.longSize,
                sizes.doubleSize
        );

Array element sizes: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]

        out.printf("# %-19s: %d, %d, %d, %d, %d, %d, %d, %d, %d [bytes]%n",
                "Array element sizes",
                U.arrayIndexScale(Object[].class),
                U.arrayIndexScale(boolean[].class),
                U.arrayIndexScale(byte[].class),
                U.arrayIndexScale(char[].class),
                U.arrayIndexScale(short[].class),
                U.arrayIndexScale(int[].class),
                U.arrayIndexScale(float[].class),
                U.arrayIndexScale(long[].class),
                U.arrayIndexScale(double[].class)
        );

对象头分析

OFFSET  SIZE      TYPE DESCRIPTION                               VALUE
      0     4           (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)
      4     4           (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4           (object header)                           16 f0 00 f8 (00010110 11110000 00000000 11111000) (-134156266)

当前环境为64位并开启压缩指针markwork占8byte即

OFFSET  SIZE      TYPE DESCRIPTION                               VALUE
      0     4           (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)
      4     4           (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)

第三行为压缩指针(Class pointer)4byte

8     4           (object header)                           16 f0 00 f8 (00010110 11110000 00000000 11111000) (-134156266)

JDK synchronize

JDK 1.6之后对synchronize进行过优化,会涉及锁升级,理论上通过jol和open-jdk markOop 对markwork的定义,是可以查询锁的变化的,但由于当前笔者能力有限,参考JAVA并发编译艺术 方腾飞 的这部分内容与markOop 结构定义不完全一致。
实际锁的变化如下

joi 源码
https://github.com/sparrowzoo/jol
测试源码
JOLSample_14_FatLocking

**** Fresh object
org.openjdk.jol.samples.JOLSample_14_FatLocking$A object internals:
 OFFSET  SIZE      TYPE DESCRIPTION                               VALUE
      0     4           (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)
      4     4           (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4           (object header)                           16 f0 00 f8 (00010110 11110000 00000000 11111000) (-134156266)
     12     1   boolean A.a                                       false
     13     3           (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 3 bytes external = 3 bytes total

**** Before the lock
org.openjdk.jol.samples.JOLSample_14_FatLocking$A object internals:
 OFFSET  SIZE      TYPE DESCRIPTION                               VALUE
      0     4           (object header)                           98 68 0b 11 (10011000 01101000 00001011 00010001) (285960344)
      4     4           (object header)                           00 70 00 00 (00000000 01110000 00000000 00000000) (28672)
      8     4           (object header)                           16 f0 00 f8 (00010110 11110000 00000000 11111000) (-134156266)
     12     1   boolean A.a                                       false
     13     3           (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 3 bytes external = 3 bytes total

**** With the lock
org.openjdk.jol.samples.JOLSample_14_FatLocking$A object internals:
 OFFSET  SIZE      TYPE DESCRIPTION                               VALUE
      0     4           (object header)                           9a 76 01 9d (10011010 01110110 00000001 10011101) (-1660848486)
      4     4           (object header)                           b5 7f 00 00 (10110101 01111111 00000000 00000000) (32693)
      8     4           (object header)                           16 f0 00 f8 (00010110 11110000 00000000 11111000) (-134156266)
     12     1   boolean A.a                                       false
     13     3           (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 3 bytes external = 3 bytes total

**** After the lock
org.openjdk.jol.samples.JOLSample_14_FatLocking$A object internals:
 OFFSET  SIZE      TYPE DESCRIPTION                               VALUE
      0     4           (object header)                           9a 76 01 9d (10011010 01110110 00000001 10011101) (-1660848486)
      4     4           (object header)                           b5 7f 00 00 (10110101 01111111 00000000 00000000) (32693)
      8     4           (object header)                           16 f0 00 f8 (00010110 11110000 00000000 11111000) (-134156266)
     12     1   boolean A.a                                       false
     13     3           (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 3 bytes external = 3 bytes total

**** After System.gc()
org.openjdk.jol.samples.JOLSample_14_FatLocking$A object internals:
 OFFSET  SIZE      TYPE DESCRIPTION                               VALUE
      0     4           (object header)                           09 00 00 00 (00001001 00000000 00000000 00000000) (9)
      4     4           (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4           (object header)                           16 f0 00 f8 (00010110 11110000 00000000 11111000) (-134156266)
     12     1   boolean A.a                                       false
     13     3           (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 3 bytes external = 3 bytes total

关于锁升级的理论参考

http://ifeve.com/java-synchronized/
https://blog.csdn.net/qq838642798/article/details/64439761

总结

综上所述

  • synchronize 并不慢
  • 基础数据类型更节省空间,应尽量使用基础数据类型,但有一种情况,基础数据类型容易产品空指针,而且很难定位排查。
    比如int value=2*Config.getValue("key") 或查询数据库等场景。null会直接报空指针异常。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,387评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,845评论 1 298
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 110,091评论 0 246
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,308评论 0 214
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,662评论 3 288
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,795评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,008评论 2 315
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,743评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,466评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,687评论 2 249
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,181评论 1 262
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,531评论 3 258
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,177评论 3 239
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,126评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,902评论 0 198
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,862评论 2 283
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,734评论 2 274

推荐阅读更多精彩内容

  • 在java语言层面的一个对象实例对应一个JVM中的instanceOopDesc ;参考/openjdk/hots...
    橡树人阅读 595评论 0 1
  • 背景 一年多以前我在知乎上答了有关LeetCode的问题, 分享了一些自己做题目的经验。 张土汪:刷leetcod...
    土汪阅读 12,663评论 0 33
  • 《秋雨初晴》 作者一一君 秋雨沥沥晓初晴 花木苍然露盈满 曙光绯红染碧空 秋色宜人空气鲜。
    四季如春_348b阅读 305评论 0 2
  • 这几天气温较低,出门也是缩手缩脚,如果不是需要吃药,去治疗,压根就不会出门,直接睡两天,但这两天却是和平常...
    玲_9e96阅读 123评论 0 0
  • 我的生命里除了母亲,除了血缘关系的姐妹,还有额外的三个女人。 大概到现在为止我们一共认识了11年。从懵懵懂懂的校园...
    轻婉飞扬阅读 1,135评论 18 26