new Object在内存中占多少字节?
作者:行百里er
博客:https://chendapeng.cn (opens new window)
提示
这里是 行百里er 的博客:行百里者半九十,凡事善始善终,吾将上下而求索!
如何知道一个对象在内存中的大小呢?C语言有个叫sizeOf的东西,很方便就能知道对象大小。但是Java没有这样的东西啊,不慌,Java本身有一个Agent技术。
# Java Agent技术
Agent是个是个什么机制呢?
jvm虚拟机,要load一个class到内存,在load内存的过程中可以加一个Agent代理,这个代理可以截获这些class文件(001 010等二进制码),并可以对他做任意修改,当然也就可以读取到整个Object的大小。
可以参考这篇文章了解更多 Java 动态调试技术原理及实践 (opens new window)
# 制作一个探测Object大小的Agent
新建一个项目ObjectSizeAgent,并制作成jar包。
- 写一个agent类,格式一般比较固定
public class ObjectSizeAgent {
//Java内部字节码处理调试用的是Instrumentation
//所以在使用代理装到我们jvm的时候可以截获这个Instrumentation
private static Instrumentation inst;
/**
* 必须要有premain函数
* 参数固定
* 第二个参数就是Instrumentation,这个是虚拟机调用的,会自动帮我们初始化Instrumentation
* 在这里通过给自己定义的成员变量赋值,赋完值就能拿到Instrumentation
* @param agentArgs
* @param _inst
*/
public static void premain (String agentArgs, Instrumentation _inst) {
inst = _inst;
}
/**
* 在premain里拿到Instrumentation后,可以调用getObjectSize获取对象大小
* @param o
* @return
*/
public static long sizeOf (Object o) {
return inst.getObjectSize(o);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
- 制作jar包
不同的IDE工具打jar的方式不同。
我在IDEA 2020.2中打jar的过程如下:
File->Project Structure
选择Artifacts
输入jar名称,创建ManiFest,存放在src目录下
创建完成后,目录如下:
文件MANIFEST.MF的内容:
Manifest-Version: 1.0
Premain-Class: pers.xblzer.tryout.agent.ObjectSizeAgent
2
最后,打成jar包:
# 使用Agent探测Object大小
在实验项目中导入制作好的jar包,maven导入或是普通jar包导入都可以。
PS:我还是写一下制作成本地maven jar包的流程吧。
- 将制作的jar包拷贝到一个地方,D:\works\3rd_jar,没别的,就是为了方便
- 打包
mvn install:install-file -DgroupId=pers.xblzer.tryout -DartifactId=ObjectSizeAgent -Dversion=1.0 -Dpackaging=jar -Dfile=D:\works\3rd_jar\ObjectSizeAgent.jar -DgeneratePom=true -DcreateChecksum=true -Dmaven.wagon.http.ssl.insecure=true -Dmaven.wagon.http.ssl.allowall=true
- 其他项目引用:
<dependency>
<groupId>pers.xblzer.tryout</groupId>
<artifactId>ObjectSizeAgent</artifactId>
<version>1.0</version>
</dependency>
2
3
4
5
实验代码:
public class TestObjectSize {
public static void main(String[] args) {
// // 配置 XX:+UseCompressedClassPointer XX:+UseCompressedOops
// 16字节 = markword 8 + classpointer 4 + padding 4
System.out.println("new Object size:" + ObjectSizeAgent.sizeOf(new Object()));
// 16字节 = markword 8 + classpointer 4 + 数组长度 4 + padding 0(前面已经是8的倍数了)
System.out.println("new array size:" + ObjectSizeAgent.sizeOf(new int[]{}));
// 32 (最终必须满足8的倍数)
System.out.println("new a common class size:" + ObjectSizeAgent.sizeOf(new P()));
// 配置 XX:-UseCompressedClassPointer XX:+UseCompressedOops
// 16字节 = markword 8 + classpointer 8 + padding 0
// System.out.println("new Object size:" + ObjectSizeAgent.sizeOf(new Object()));
// // 24字节 = markword 8 + classpointer 8 + 数组长度 4 + padding 4(补齐至8的倍数)
// System.out.println("new array size:" + ObjectSizeAgent.sizeOf(new int[]{}));
// // 40 (最终必须满足8的倍数)
// System.out.println("new a common class size:" + ObjectSizeAgent.sizeOf(new P()));
}
private static class P {
// markword 8
// ClassPinter 4 (-UseCompressedClassPointer时 为8;+UseCompressedClassPointer时 为4)
// 4
int id;
// Oops 4
String name;// 这是一个引用 +UseCompressedOops时 为8 -UseCompressedOops时 为4
// 1
byte b1;
// 1
byte b2;
//Oops 4
Object o;
// 8
long i;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
运行时添加我们制作的agent:运行时添加运行参数
-javaagent:D:\works\3rd_jar\ObjectSizeAgent.jar
先运行一下看结果,运行环境jdk 1.8,jvm参数是默认:
从运行结果看
new Object()
占用16字节;
new int[]{}
占用16字节;
new P()
自己定义的里面含有各种数据类型属性的类占用40字节。
结果出来了,我们来分析一下,他们为什么占用那么多字节。
# 对象在内存中的布局
作为对象的内存布局来讲分为两种,一种是普通对象,一种是数组对象。
普通对象内存各部分字节占用分配情况:
- 对象头,在Hotspot里面称为
markword
,它的长度是8byte
ClassPointer
指针(比如Object.class)
- -XX:+UseCompressedClassPointers的情况为
4byte
,默认 - -XX:-UseCompressedClassPointers的情况为
8byte
- 实例数据
其中引用类型
字节占用情况:
- -XX:+UseCompressedOops
4byte
,默认 - -XX:-UseCompressedOops
8byte
非引用类型
字节占用情况:
类型 | 存储 | 取值范围 |
---|---|---|
int | 4byte | -2^31 ~ 2^31 - 1 |
short | 2byte | -2^15 ~ 2^15 - 1 |
long | 8byte | (-2)^63 ~ 2^63 - 1 |
byte | 1byte | -128 ~ 127 |
float | 4byte | |
double | 8byte | |
boolean | 1byte | |
char | 2byte |
Hotspot实现的JVM开启内存压缩的规则(64位机器):
- 4G以下,直接砍掉高32位
- 4G~32G,默认开启内存压缩
- 32G以上,压缩无效,使用64位
所以,内存并不是越大越好。
Padding
对齐
这个对齐,对齐的是8的倍数。最为64位机器来说,它是按照块来读的,不是按照字节来读,每一块存的都是8的倍数个字节,因此它有一个对齐机制。
数组对象内存各部分字节占用分配情况:
- 对象头
markword
8字节 ClassPointer
指针,同普通对象,压缩4字节,不压缩8字节- 数组长度 4字节
- 数组数据
- 对齐 8的倍数
# 计算new Object()占用的字节数
基于上面的分析,我们来验证一下前面写的程序计算结果:
对于new Object()
- 首先,
markword
占8字节
ClassPointer
:我的机器内存是8G,JVM默认是开启了内存压缩规则的,所以这里ClassPointer会占用4字节
- 实例数据:我只是new了一个Object(),没有任何的引用类型和费用用类型,这部分没有占用字节
Padding
对齐:前面8+4+0=12字节,因为要满足8的倍数,所以这里需要补齐
至16字节
所以,new Object()
占用16字节。
对于new int[]{}
markword
: 8字节ClassPointer
:4字节数组长度
:4字节数组数据
:0字节Padding
对齐:前面8+4+4+0=16,已经是8的倍数了,这里不需要对齐
因此,new int[]{}
占用16字节。
对于我自己定义的new P()
private static class P {
// markword 8
// ClassPinter 4 (-UseCompressedClassPointer时 为8;+UseCompressedClassPointer时 为4)
// 4
int id;
// Oops 4
String name;// 这是一个引用 +UseCompressedOops时 为8 -UseCompressedOops时 为4
// 1
byte b1;
// 1
byte b2;
//Oops 4
Object o;
// 8
long i;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
markword
8字节ClassPointer
4字节实例数据
:- int 4字节
- String 引用类型 4字节
- 两个byte 1*2=2字节
- Object o 引用类型 4字节
- long 8字节
Padding
对齐:先算一下是否满足8的倍数 8+4+4+4+2+4+8=34,需要补齐至8的倍数,补至40
因此,本例中new P()
占用40字节。
大家可以根据jvm参数来调试这个程序,会得到不同的结果。主要是下面两个参数:
-XX:(+/-)UseCompressedClassPointers
-XX:(+/-))UseCompressedOops
2
Over。以上。
首发公众号 行百里er ,欢迎老铁们关注阅读指正。