让你亲眼看见java对象内存大小(基于64位操作系统)
引言
hello,大家好,我是小面!今天有个小伙伴私信我说怎么能亲眼看见java对象占用的大小呢?那小面就这个问题做一个简单的实验来,基于64位操作系统来看看对象的大小。
在开始实验之前,也有一些老生常谈的知识需要铺垫一下。
查看虚拟机配置
通过-xx参数 PrintCommandLineFlags查看
那么我们看到了一堆输出参数,参数前面带有-XX,-XX是指什么呢?
这个是JVM为我们提供了三种参数方式(标准选项、X选项、XX选项)
标准选项
这类选项功能是非常稳定的,在后续jdk版本中也不太会发生变化。
在cmd终端运行java、 java -help 就可以看到所有的标准选项。
所有的标准选项都是以 - 开头,例如-version,-server等。
X选项
这类选项功能还是很稳定,后续版本中可能会改变,也有可能不再提供了。
运行 java -X 命令可以看到所有的X选项。
这类选项都是以 -X 开头,比如 -Xmx -Xms -Xmn -Xss。
XX选项
这类选项是属于实验性,主要是给JVM开发者用于开发和调试JVM的,后续的版本中有可能会变化。
如果是布尔类型的选项,它的格式为-XX:+flag或者-XX:-flag,分别表示开启和关闭该选项。
针对非布尔类型的选项,它的格式为-XX:flag=value
例如:
-XX:InitialHeapSize=132558400 #JVM初始堆内存
-XX:MaxHeapSize=2120934400 #JVM最大堆内存
-XX:+PrintCommandLineFlags
-XX:+UseCompressedClassPointers
-XX:+UseCompressedOops
-XX:-UseLargePagesIndividualAllocation
-XX:+UseParallelGC
其中有一项是UseCompressedClassPointers,这个叫开启指针压缩
在HotSpot 虚拟机中,对象在内存中布局分为三块区域,对象头(Header)、实例数据(Instance Data)和对齐填充(Padding),普通对象和数组对象又稍有差别,我们一起来看一看。
普通对象
- 对象头:markword 8个字节
- ClassPointer指针:-XX:+UseCompressedClassPointers 大小为4字节 -XX:-UseCompressedClassPointers不开启为8字节。表示是否启用类指针压缩,因为对于任何一个jvm中的对象而言,其内部都有一个指向自己对应类(属于哪个class)的指针(Java习惯叫引用),在64位的Java虚拟机中,默认是启动压缩的
- 实例数据 引用类型:-XX:+UseCompressedOops 大小为4字节 -XX:-UseCompressedOops不开启为8字节 表示是否使用普通对象指针压缩,Oops是Ordinary object pointers的缩写,就是任何指向一个在堆中的对象(非简单类型)的指针,默认也是启动压缩的
- Padding对齐,8的倍数(加上这个对齐的字节就是整个对象的大小,大小是8的倍数)
数组对象
- 对象头:markword 8个字节
- ClassPointer指针同上
- 数组长度:4字节
- 数组数据
- 对齐 8的倍数(加上这个对齐的字节就是整个对象的大小,大小是8的倍数)
可以看见,数组对象比普通对象多了一个数组长度对象,另外普通对象和数组对象都是有对象头的,我们来看下对象头。
对象头
对象头主要包括Mark Word,对象指针,数组长度
Mark Word
Mark Word占用8字节空间,其主要用于锁升级。
无锁:Mark Word保存对象HashCode,锁标志位是01,是否偏向锁为0。
偏向锁:请求进来先检查是否包括线程id,没有的话保存线程id,修改是否偏向锁标识。如果包括线程id,则判断线程id是否是本线程id,是的话继续向下执行,不是的则进行抢锁操作,抢锁成功更改线程id,失败则升级为轻量级锁。
轻量级锁:偏向锁抢夺失败后升级为轻量级锁,jvm通过cas操作在当前线程的线程栈中开辟一块单独的空间保存指向对象锁Mark Word的指针,并且在对象锁Mark Word中保存指向这片空间的指针,如果保存成功,则表示当前线程抢到锁,继续执行。保存失败则表示抢锁失败。
轻量级锁抢锁失败JVM使用自旋锁不断重试抢锁避免线程阻塞,当达到一定次数后(jdk1.7后JVM控制自旋次数),升级为重量级锁。
-XX:-UseSpinning 控制是否开启自旋锁,默认开启
重量级锁: 自旋锁自旋一定次数还没获得锁则升级为重量级锁,此时只有获取到锁的线程能执行,其余线程阻塞。
对于对象有了一个初步的认识后,小面将通过一个小实验来观测对象的大小,让大家有一个实质性的感受。
实验(观察对象的大小)
- 新建项目(以IDEA为例)ObjectSize (1.8)一个单独的项目
- 创建文件ObjectSizeAgent
import java.lang.instrument.Instrumentation;
public class ObjectSizeAgent {
private static Instrumentation inst;
public static void premain(String agentArgs, Instrumentation _inst) {
inst = _inst;
}
public static long sizeOf(Object o) {
return inst.getObjectSize(o);
}
}
src目录下创建META-INF/MANIFEST.MF
Manifest-Version: 1.0
Created-By: lnf.com
Premain-Class: com.lnf.ObjectSizeAgent
注意Premain-Class这行必须是新的一行(回车 + 换行),确认idea不能有任何错误提示 pom打包设置:
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.1.0</version>
<configuration>
<archive>
<manifestEntries>
<Premain-Class>com.lnf.ObjectSizeAgent</Premain-Class>
<Agent-Class>com.lnf.ObjectSizeAgent</Agent-Class>
<Can-Redefine-Classes>true</Can-Redefine-Classes>
<Can-Retransform-Classes>true</Can-Retransform-Classes>
</manifestEntries>
</archive>
</configuration>
</plugin>
</plugins>
</build>
image.png
- 打包jar文件
- 创建测量类:
import com.lnf.jvm.agent.ObjectSizeAgent;
public class T03_SizeOfAnObject {
public static void main(String[] args) {
System.out.println(ObjectSizeAgent.sizeOf(new Object()));
System.out.println(ObjectSizeAgent.sizeOf(new int[] {}));
System.out.println(ObjectSizeAgent.sizeOf(new P()));
}
private static class P {
//8 _markword
//4 _oop指针
int id; //4
String name; //4
int age; //4
byte b1; //1
byte b2; //1
Object o; //4
byte b3; //1
}
}
- 在需要使用该Agent Jar的项目中引入该Jar包 project structure - project settings - library 添加该jar包
7. 运行时需要该Agent Jar的类,加入参数:
-javaagent:E:\java\test\ObjectSize\target\ObjectSize-1.0-SNAPSHOT.jar
不开启压缩指针,减号就表示不开启
-javaagent:E:\java\test\ObjectSize\target\ObjectSize-1.0-SNAPSHOT.jar -XX:-UseCompressedClassPointers
指定把哪个jar文件当成我这次运行的虚拟机
- 运行测量类:
import com.lnf.jvm.agent.ObjectSizeAgent;
public class T03_SizeOfAnObject {
public static void main(String[] args) {
System.out.println(ObjectSizeAgent.sizeOf(new Object()));
System.out.println(ObjectSizeAgent.sizeOf(new int[] {}));
System.out.println(ObjectSizeAgent.sizeOf(new P()));
}
private static class P {
//8 _markword
//4 _oop指针
int id; //4
String name; //4
int age; //4
byte b1; //1
byte b2; //1
Object o; //4
byte b3; //1
}
}
new Object()的长度16 = 对象头8+指针(不开启指针压缩长度是8)4 + padding 4
new int[] 的长度 = 对象头8+指针(不开启指针压缩长度是8)4 + int类型 4 + padding 0(长度正好16是8的2倍)
那么这里开启指针压缩,我们也可不开启指针压缩,通过JVM参数
-javaagent:E:\java\test\ObjectSize\target\ObjectSize-1.0-SNAPSHOT.jar -XX:-UseCompressedClassPointers
-XX:-UseCompressedClassPointers不开启指针压缩来看下运行结果
大家可以自行算一下,不开启压缩就是8个字节
总结
实验到这就结束了,相信大家通过这个小实验可以认识到一个对象在JVM内存中的布局。大家可以动手自己试一试,利用javaagent实测java对象大小。
相关文章
- Java进阶(二十三)java中long类型转换为int类型
- java 把对象转成map_Java对象转换成Map[通俗易懂]
- JAVA byte int 0xff 0xffffffff
- Java基础知识点笔记(一):java中的取整与四舍五入
- java是面向对象还是面向过程_Java面向对象编程和面向过程编程的区别[通俗易懂]
- java启动器_JAVA基础:Java 启动器如何查找类
- java输入Scanner基本用法[通俗易懂]
- java 唯一随机数_JAVA随机数
- JAVA对象转map_java处理字符串类型的map
- 【JAVA面试必会】JMM高并发详解(java内存模型、JMM三大特征、volatile关键字 )「建议收藏」
- java面对对象(下.2抽象类和接口,内部类)
- expect java,Expect 使用详解「建议收藏」
- 遇到刁钻面试题如何回答Java中,4种对象引用之间的区别是什么?
- Java用户管理系统【完整版】
- Java 代码 执行Python脚本(亲测有效)
- java Velocity 同时初始化两个 Engine 实例详解编程语言
- Java 对图片90度旋转详解编程语言
- 数据如何使用Java读取MySQL数据(java读取mysql)
- 利用Redis存储Java对象的方法(redis存储java对象)
- 时间设置Java程序设置Redis过期时间(redisjava过期)
- 使用Redis存储Java对象(redis存java对象)
- 机制基于Redis和Java实现高效过期机制(redisjava过期)
- 检测使用Redis与Java实现过期对象检测(redisjava过期)
- 缓存基于Redis与Java实现有效的过期缓存(redisjava过期)
- 基于Linux操作系统上实现 Java 编程(linux r java)
- Linux 下安装Java:快速从零开始(linux 下载java)
- Java实现嵌入式MySQL的新解决方案(java嵌入式mysql)
- Java配置Oracle实现稳定的跨平台数据库连接(java配置oracle)
- Java实现Redis队列锁功能(redis队列锁java)