从一道关于Java字符串的面试题初探JVM之内存布局、对象占用内存大小

从一道面试题初探JVM之内存布局、对象占用内存大小

测试环境

机器

CentOS Linux release 7.6.1810 (Core) x64
openjdk version “1.8.0-internal-debug”

代码

代码以及断点

问题:
运行第三行代码之后,会在JVM堆中创建几个对象?
每个对象多少个字节?
对象的内存布局如何?
JVM其他内存区域,比如字符串常量池、线程栈有什么变化?

不开启指针压缩测试

即添加JVM启动参数:-XX:-UseCompressedOops

猜测

此时我们这段代码的JVM内存布局应该是这样的
"99"这个字符串常量,(假设)不存在于字符串常量池中,会去Java堆中创建String对象,创建String的过程中需要先创建value对象(char[]),创建完成String对象之后,会在字符串常量池(底层实现是StringTable,一个哈希表结构)中缓存一份,然后把String对象的引用地址赋值给main线程栈的栈帧中的局部变量1(局部变量0为args参数)
代码执行后猜测的JVM运行时数据区

验证

s1对象内存布局
0x00007f26fd11cc80即s1对象内存的起始地址,这里拿出从该地址开始的80个字节的内存(JVM对象都是8字节对齐的):

hsdb> mem 0x00007f26fd11cc80 10
0x00007f26fd11cc80: 0x0000000000000001
0x00007f26fd11cc88: 0x00007f273c67ac70
0x00007f26fd11cc90: 0x00007f26fd11cca0
0x00007f26fd11cc98: 0x0000000000000000
0x00007f26fd11cca0: 0x0000000000000001
0x00007f26fd11cca8: 0x00007f273c670210
0x00007f26fd11ccb0: 0x0000000000000002
0x00007f26fd11ccb8: 0x0000000000390039
0x00007f26fd11ccc0: 0xbaadbabebaadbabe
0x00007f26fd11ccc8: 0xbaadbabebaadbabe

分析如下:

s1对象 内存地址含义
0x00007f26fd11cc800x0000000000000001s1对象._mark,8字节,值为1代表无锁
0x00007f26fd11cc880x00007f273c67ac70s1对象._metadata._klass,指向String类元信息的指针,结合下面的当前VM的堆的内存布局,可以知道类元信息不是存储在JVM堆中
0x00007f26fd11cc900x00007f26fd11cca0这个重要了,这个值是个内存地址,指向s1对象.value属性

接下来分析value对象,从地址0x00007f26fd11cca0开始:

value对象 内存地址含义
0x00007f26fd11cca00x0000000000000001value对象._mark,8字节,值为1代表无锁
0x00007f26fd11cca80x00007f273c670210value对象._metadata._klass,此时未开启指针压缩,为8字节
0x00007f26fd11ccb00x00000002value对象._max_length,表示数组对象的数组长度,4个字节
0x00007f26fd11ccb40x00000000value对象头填充?,4个字节,这样对象头是8*3=24个字节
0x00007f26fd11ccb80x00390039value对象实例数据,Java中每个char占用两个字节,0x0039的ascii码是‘9’
0x00007f26fd11ccbc0x00000000value对象实例数据部分的填充,这样实例数据正好是8个字节

这里用hsdb看下value对象的内存情况:
32个字节没错,但是没有标识出数组长度、填充的字节,可以用jol-core这个jar打印出内存布局进行互相验证(我这边暂时不做了)
用hsdb查看VM对象内存布局

VM oop类(ordinary object pointer)
这里看下当前VM的堆的内存布局

Heap Parameters:
ParallelScavengeHeap [ PSYoungGen [ eden =  [0x00007f26fce00000,0x00007f26fd194310,0x00007f26feb00000] , from =  [0x00007f26fef80000,0x00007f26fef80000,0x00007f26ff400000] , to =  [0x00007f26feb00000,0x00007f26feb00000,0x00007f26fef80000]  ] PSOldGen [  [0x00007f26b1600000,0x00007f26b1600000,0x00007f26b6200000]  ]  ]

java.lang.String类有两个属性:分别是引用类型和原生int类型
Java数组在VM看来是一种特殊的对象类型,比普通对象多了个_max_length属性

尝试源码分析

单步调试环境也是禁用指针压缩的
在将要执行Java的main方法之前,下好断点
跳到查找字符串常量池中有没有“99”字符串的方法,怎么证明呢?
看len是2,怎么看name执行的内存地址存储的是不是"99"呢?
在这里插入图片描述
通过上图可以看到地址0x7ffff0009750存储的值是3735609
通过python把十进制3735609转换成二进制为:

>>> a=3735609
>>> print("%#x"%a);
0x390039

man ascii命令可以看到0x39对应字符9linux shell man ascii命令可以看到0x39对应字符9

再结合len=2,读取2*2个字节(Java中每个char占用2个字节),就是0x00390039,而0x0039对应的ascii码就是字符’9’
ps:通过以上JVM源码,我们也知道为什么Java中的char是2个字节,因为它对应的VM中的类型是jchar,而jchar又是

typedef unsigned short  jchar;

那为什么short又是2个字节?C++的知识了。。。

在这里插入图片描述

默认开启指针压缩测试

几点感悟

1.所谓的Java对象的内存占用,其实指的是每个Java对象所对应的VM中的C++对象的内存占用,这个C++类一般是

参考

1.https://www.bilibili.com/video/BV14i4y1K7Zv?p=31
2.https://club.perfma.com/article/2261053

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值