解析字节对齐的数据_字段解析之OopMapBlock(4)

OopMapBlock是一个简单的内嵌在Klass里面的数据结构,用来描述oop中包含的引用类型属性,即该oop所引用的其他oop在oop中的内存分布,然后就可以根据当前oop的地址找到所有引用的其他oop了,其定义如下:

源代码位置:oops/instanceKlass.hpp// ValueObjs embedded in klass. Describes where oops are located in instances of// this klass.class OopMapBlock VALUE_OBJ_CLASS_SPEC { public:  // Byte offset of the first oop mapped by this block.  int offset() const          { return _offset; }  void set_offset(int offset) { _offset = offset; }   // Number of oops in this block.  uint count() const         { return _count; }  void set_count(uint count) { _count = count; }   // sizeof(OopMapBlock) in HeapWords.  static const int size_in_words() {    return align_size_up(int(sizeof(OopMapBlock)), HeapWordSize) >>      LogHeapWordSize;  }  private:  int  _offset;  uint _count;};

OopMapBlock结构可以描述某个对象中引用区域的起始偏移和引用个数。offset描述第一个所引用的oop相对于当前oop地址的偏移量,count表示包含的oop的个数,注意这里的包含并不是指这些oop位于OopMapBlock里面,而是有count个连续存放的oop。为啥会有多个OopMapBlock了?因为每个OopMapBlock只能描述当前子类中包含的引用类型属性,父类的引用类型属性由单独的OopMapBlock描述。 

之前介绍过,可以利用-XX:+PrintFieldLayout来查看布局情况。该选项只在调试版本中有效。至于布局模式,可以使用-XX:+FieldsAllocationStyle=mode来指定,默认是1。之前介绍过布局模式有3种,如下:

  • allocation_style=0,字段排列顺序为oops、longs/doubles、ints、shorts/chars、bytes,最后是填充字段,以满足对齐要求;

  • allocation_style=1,字段排列顺序为longs/doubles、ints、shorts/chars、bytes、oops,最后是填充字段,以满足对齐要求;

  • allocation_style=2,JVM在布局时会尽量使父类oops和子类oops挨在一起。

当allocation_style的值为2时,父子oop的布局会连续在一起,这样至少有2个好处:

  1. 减少OopMapBlock的数量。由于GC收集时要扫描存活的对象,所以必须知道对象中引用的内存位置。原始类型不需要扫描。

  2. 连续的对象区域使得缓存行的使用效率更高。试想如果父对象和子对象的对象引用区域不连续,而中间插入了原始类型字段的话,那么在做GC对象扫描时,很可能需要跨缓存行读取才能完成扫描。

下面我们用HSDB来实际探查下相关的内存布局:

package jvmTest;  import java.lang.management.ManagementFactory;import java.lang.management.RuntimeMXBean;  class Base{    private int a=1;      private String s="abc";      private Integer a2=12;      private int a3=22;}  class A extends Base  {    private int b=3;      private String s2="def";      private int b2=33;      private Base a=new Base();}  class B extends A{    private String s3="ghk";      private Integer c=4;      private int c2=44;}  public class jvmTest {      public static void main(String[] args) {        Base a=new Base();        A a2=new A();        B b=new B();        while (true){            try {                System.out.println(getProcessID());                Thread.sleep(600*1000);            } catch (Exception e) {              }        }    }      public static final int getProcessID() {        RuntimeMXBean runtimeMXBean = ManagementFactory.getRuntimeMXBean();        System.out.println(runtimeMXBean.getName());        return Integer.valueOf(runtimeMXBean.getName().split("@")[0]).intValue();    }}

运行main方法后,用HSDB查看main线程的线程栈,从中找出变量a,a2,b对应的oop的地址,如下:

ba29dbf3f9f60547f19810b70b56bf7e.png 

 分别查看0x00000000d69d98a0,0x00000000d69db5f8,0x00000000d69dd070对应的oop,如下:

 1c4690fb3a4d36e1aea28181a2c93197.png 

3625e2ba9aaf467aeebf02b5063e5734.png 

 dcc25527b1cb4c783b3a0bbd4fc89918.png 

 上从面的截图可知,子类会完整的保留父类的属性,从而方便调用父类方法时能够正确的使用父类的属性。上述对oop的属性打印是按照类声明属性的顺序来的,内存中是这样保存的么?可以通过查看属性的偏移量来判断。

在Class Browser中搜索jvmTest,可以查找到我们三个自定义类对应的Klass,如下图:

68fcb3c4452f35e7ab07f9761dd99fb6.png 

分别点击这三个类查看属性的偏移量,如下:

 072bb6f3dadc7a69cf3e14f8fe894215.png 

 be333cb4f2f8af6a199d9c9a37614371.png 

e1c5b42567d3b8864b9a8cee43fdfb9e.png 

根据上述偏移量,我们可以得出jvmTest.B对象的内存布局,如下:

42d3e4a7626efd688d4b7b0ce4fd90c7.png 

int本身占4个字节,引用类型属性本质上就是一个指针,这里因为默认开启了指针压缩,所以也是4字节。

我们再看下表示OopMapBlock在Klass中的字宽数的属性_nonstatic_oop_map_size在三个类中的取值,如下:

8f03f3ae3ccf5e2d88aa6a44d5f3b2a0.png 

3036a3fa3532ed4b20e86ae052b7c2a8.png 

 8d4ef34355c64766907565876cde32ba.png 

OopMapBlock本身就只有两个int属性,所以一个OopMapBlock实例只有8字节,即一个字宽,jvmTest.B的_nonstatic_oop_map_size属性值为3,即由3个OopMapBlock,下面通过CHSDB的mem命令来看看这3个OopMapBlock对应的内存数据。

首先执行inspect对象得到该Klass本身的大小,即sizeof的大小,如下:

 075ee4df5a9db9ded76c687a5e0d3ebc.png 

vtable,itable,OopMapBlock这三个都是内嵌在Klass里面的,所谓的内嵌实际是指这块内存是紧挨着Klass自身的属性对应的内存的下面,从上一节的分析可知,OopMapBlock在itable的后面,itable在vtable的后面,而vtable是紧挨着Klass的,从上述inspect命令的输出,也可知道itable和vtable的内存大小,单位是字宽,如下:

e39fc87ec6a77a68403853d00d06f7fe.png 

因此OopMapBlock的起始地址就是Klass的地址加上Klass本身的大小440字节即55字宽,再加上vtable的5字宽,itable的2字宽,总共加62字宽,OopMapBlock本身占用3个字宽,因此用mem查看这65字宽的数据,如下:

8d0df8c0df87bf4511267df3c037b557.png 

最后的3个字宽如下:

9ffef38e85b13920635713554af16c64.png 

每个字宽对应一个OopMapBlock,前面4字节就是count属性,这里都是2,后面4字节就是offset,分别是20,36,48,与jvmTest.B的内存结构是完全一致的。

作者持续维护的个人博客http://classloading.com/

相关文章的链接如下:

1、在Ubuntu 16.04上编译OpenJDK8的源代码 

2、调试HotSpot源代码

3、HotSpot项目结构 

4、HotSpot的启动过程 

5、HotSpot二分模型(1)

6、HotSpot的类模型(2)

7、HotSpot的类模型(3)

8、HotSpot的类模型(4)

9、HotSpot的类模型(5)

10、HotSpot的对象模型(6)

11、操作句柄Handle(7)

12、释放句柄Handle(8)

13、类加载器

14、类的双亲委派机制

15、核心类的預装载

16、Java主类的装载

17、触发类的装载

18、文件流

19、解析Class文件

20、常量池解析(1)

21、常量池解析(2)

22、字段解析(1)

23、字段解析(2)

24、字段解析之伪共享(3)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值