1、java对象的内存布局
一个java普通对象在内存中分为4部分:
-
Markword
主要记录对象的锁状态
-
class pointer
指向对象字节码的指针
-
instance data
实例数据
-
padding
jvm设计时对象所占大小要被8整除,要是位数不够需要加padding
2、对象布局的查看
使用JOL类可以查看对象的内存布局,maven引入如下依赖即可:
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.9</version>
</dependency>
以new Object()对象为例,看一下32位和64位上的差异。
32位
64位
可以看出在32位机上一个Object对象占8个字节,而在64位机上占16个字节。那为什么两个Markword不一样呢?
在64位jvm上执行java -XX:+PrintCommandLineFlags -version
可以看到:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YMelErMs-1649900403442)(C:\Users\10262863\AppData\Roaming\Typora\typora-user-images\image-20211215125727343.png)]
UseCompressedOops:普通对象指针压缩,oops: ordinary object pointer
UseCompressedClassPointers:类指针压缩
这两个参数会影响jvm的内存布局,具体如下:
本身classpointer是跟操作系统位数一样的,因为默认开启了UseCompressedClassPointers,所以而UseCompressedClassPointers则使用32-bit的offset(也就是4个字节)来代表64-bit进程中的class pointer;
也就是64位操作系统由于开启类指针压缩,所以4个字节来表示classpointer。为什么32位机器上也是4位呢?我们使用java -XX:+PrintCommandLineFlags -version
在32位机器上看一下:
可以看出32位机默认上并没有开启指针压缩,因此,classpointer就是32位,4个字节。而不是2个字节。
而**markword大小跟操作系统是对应的,32位操作系统就是4个字节,64位操作系统就是8个字节**。
3、无锁态的markword
看一下64位和32的区别
32位
两张图对应可以看出,new Object是无锁态,锁的标志是001,至于为什么说001不在低位,原因是大小端存储区别。内存里面是小端存储。
- 大端字节序:高位字节在前,低位字节在后,这是人类读写数值的方法。
- 小端字节序:低位字节在前,高位字节在后,即以
0x1122
形式储存。计算机电路先处理低位字节,效率比较高,因为计算都是从低位开始的。所以,计算机的内部处理都是小端字节序。
人类还是习惯读写大端字节序。所以,除了计算机的内部处理,其他的场合几乎都是大端字节序,比如网络传输和文件储存。
由于无锁状态,所以剩下的30位都为0。无锁的时候会记录hashcode,我们使用一下hashcode方法,看一下内存布局有什么变化。
可以看出最高的30位已经变成了对象的hashcode,01000001 10100010 01010000
,我们把它倒过来变成大端写法:01010000 10100010 01000001 00001001
然后使用System.out.println(Integer.toBinaryString(i));
打印一下对象的hashcode,如下10100001 01000100 10000010
,正好内存布局的hashcode对上了
64位
(省略,类似上面的32位)
4、锁升级的过程详解
以64位为例
无锁态
new 对象之后markword的锁标志就是001,如果没有调用过hashcode方法,对应的hashcode位就全为0。
偏向锁
markword里面记录了指向当前线程的指针,54位,标志位变成101.
轻量级锁
线程栈生成LR,markword指向线程栈中的LR指针。 当有其他线程竞争的时候,锁标志由偏向锁变成轻量级锁(00),通过自旋的方式完成,也就是CAS操作。
如果太占用cpu资源,就会升级为重量级锁。
重量级锁
如果竞争加剧,或者自旋超过10次,或者占用cpu资源的1/2.申请锁必须要经过操作系统老大kernel进行系统调用,入队进行排序操作,操作完之后再返回给用户态。