-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
2 changed files
with
199 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
--- | ||
isOriginal: true | ||
date: 2023-11-09 | ||
index: true | ||
category: | ||
|
||
- Java | ||
|
||
tag: | ||
|
||
- JVM | ||
|
||
--- | ||
|
||
# JVM对象内存布局 | ||
|
||
Hotspot虚拟机中,将对象在内存中存储的布局分为三块:**对象头(Header)**、**实例数据(Instance Data)**、**对齐填充(Padding)** | ||
|
||
<!-- more --> | ||
|
||
![对象内存布局](https://qiniu.yanggl.cn/image/202311091034325.png) | ||
|
||
## 对象头 | ||
|
||
HotSpot主要将对象头划分为:**MarkWord**、**KlassPointer**、**数组长度**,记录了对象Hash码、对象所属年代、对象锁、锁状态、偏向锁的线程ID、偏向时间、数组长度等等信息 | ||
|
||
### MarkWord | ||
|
||
MarkWord用于存储对象自身运行时的数据,例如哈希码(HashCode)、GC分代年龄、锁状态、持有锁的线程、偏向线程ID、偏向时间戳等。这部分数据的长度在32位和64位的虚拟机中分别占32bit和64bit。 | ||
|
||
- **32位MarkWord** | ||
|
||
![32位MarkWord](https://qiniu.yanggl.cn/image/202311091048332.png) | ||
|
||
- **64位MarkWord** | ||
|
||
![64位MarkWord](https://qiniu.yanggl.cn/image/202311091048664.png) | ||
|
||
> MarkWord结构搞得那么复杂,是因为需要节省内存,让同一内存区域在不同锁阶段有不同的用处 | ||
- **hash**:保存对象的哈希码,在运行期间调用System.identityHashCode()进行计算,这是一个延迟计算,并将结果赋值到hash内存中 | ||
- **age**:保存对象的分代年龄。记录对象被GC的次数,当该次数到达阈值后就会由年轻代转入老年代 | ||
- **biased_lock**:偏向锁标识位。由于无锁和偏向锁的锁标识都是记01,为了区分引入了一位来标识是否为偏向锁 | ||
- **lock**:锁状态标识。区分锁状态,比如00时表示轻量锁,只有最后2位锁标识(00)有效 | ||
- **JavaThread**:保存持有偏向锁的线程ID。当处于偏行模式时,有线程持有对象,则对象的这里就会保存持有线程的ID,后续再获取锁时就无需再进行尝试获取锁的动作 | ||
- **epoch**:保存偏向时间戳。偏向锁再CAS锁操作过程中,偏向性标识,标识对象更偏向哪个锁 | ||
|
||
### KlassPointer | ||
|
||
KlassPointer又称类型指针,是指向对象的类元数据的指针。虚拟机可以通过这个指针来确定这个对象是那个类的实例。 | ||
|
||
在32位的JVM内,类型指针占4byte | ||
在64位的JVM内,类型指针正常占8byte,若开启**指针压缩**或者**最大堆内存小于32G**时占4byte。JDK8后默认开启指针压缩 | ||
|
||
### 数组长度 | ||
|
||
如果这个对象是一个数组,则对象头中会有一块4byte长度数数据区用于记录数组的长度,如果不是数组则这部分长度为0 | ||
|
||
![Header内存占用](https://qiniu.yanggl.cn/image/202311091135325.png) | ||
|
||
## 内存布局查看实战 | ||
|
||
为了验证对象内存布局,可使用Java对象的内部布局工具**JOL(JAVA OBJECT LAYOUT)** | ||
,用此工具可以查看new出来的一个java对象的内部布局,以及一个普通的java对象占用多少字节。 | ||
|
||
Maven依赖引入 | ||
|
||
```xml | ||
<!-- JAVA对象布局、大小查看 --> | ||
<dependency> | ||
<groupId>org.openjdk.jol</groupId> | ||
<artifactId>jol-core</artifactId> | ||
<version>0.10</version> | ||
</dependency> | ||
``` | ||
|
||
案例 | ||
|
||
```java | ||
public class Main { | ||
public static void main(String[] args) { | ||
System.err.println(ClassLayout.parseInstance(new Object()).toPrintable()); | ||
} | ||
} | ||
``` | ||
|
||
- 开启指针压缩 | ||
|
||
```shell | ||
java.lang.Object object internals: | ||
OFF SZ TYPE DESCRIPTION VALUE | ||
0 8 (object header: mark) 0x0000000000000001 (non-biasable; age: 0) | ||
8 4 (object header: class) 0xf80001e5 | ||
12 4 (object alignment gap) | ||
Instance size: 16 bytes | ||
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total | ||
``` | ||
|
||
- 未开启指针压缩 | ||
|
||
```shell | ||
java.lang.Object object internals: | ||
OFF SZ TYPE DESCRIPTION VALUE | ||
0 8 (object header: mark) 0x0000000000000001 (non-biasable; age: 0) | ||
8 8 (object header: class) 0x000000001beb1c00 | ||
Instance size: 16 bytes | ||
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total | ||
``` | ||
|
||
> - OFF:偏移地址(Byte) | ||
> - SZ:内存占用大小(Byte) | ||
> - TYPE DESCRIPTION:类型描述,object header为对象头,object alignment gap为对齐补充 | ||
> - VALUE:对应内存中当前存储的值 |