让你亲眼看见java对象内存大小(基于64位操作系统)

简介: 让你亲眼看见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),普通对象和数组对象又稍有差别,我们一起来看一看。

普通对象

  1. 对象头:markword  8个字节
  2. ClassPointer指针:-XX:+UseCompressedClassPointers 大小为4字节 -XX:-UseCompressedClassPointers不开启为8字节。表示是否启用类指针压缩,因为对于任何一个jvm中的对象而言,其内部都有一个指向自己对应类(属于哪个class)的指针(Java习惯叫引用),在64位的Java虚拟机中,默认是启动压缩的
  3. 实例数据 引用类型:-XX:+UseCompressedOops 大小为4字节 -XX:-UseCompressedOops不开启为8字节 表示是否使用普通对象指针压缩,Oops是Ordinary object pointers的缩写,就是任何指向一个在堆中的对象(非简单类型)的指针,默认也是启动压缩的
  4. Padding对齐,8的倍数(加上这个对齐的字节就是整个对象的大小,大小是8的倍数)

数组对象

  1. 对象头:markword 8个字节
  2. ClassPointer指针同上
  3. 数组长度:4字节
  4. 数组数据
  5. 对齐 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  控制是否开启自旋锁,默认开启
    重量级锁: 自旋锁自旋一定次数还没获得锁则升级为重量级锁,此时只有获取到锁的线程能执行,其余线程阻塞。

对于对象有了一个初步的认识后,小面将通过一个小实验来观测对象的大小,让大家有一个实质性的感受。

实验(观察对象的大小)

  1. 新建项目(以IDEA为例)ObjectSize (1.8)一个单独的项目
  2. 创建文件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);
    }
}

  1. src目录下创建META-INF/MANIFEST.MF
Manifest-Version: 1.0
Created-By: lnf.com
Premain-Class: com.lnf.ObjectSizeAgent
  1. 注意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

  1. 打包jar文件
  2. 创建测量类:
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
       }
   }
  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文件当成我这次运行的虚拟机

  1. 运行测量类:
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对象大小。

相关实践学习
CentOS 8迁移Anolis OS 8
Anolis OS 8在做出差异性开发同时,在生态上和依赖管理上保持跟CentOS 8.x兼容,本文为您介绍如何通过AOMS迁移工具实现CentOS 8.x到Anolis OS 8的迁移。
相关文章
|
20天前
|
编解码 JavaScript 前端开发
【Java进阶】详解JavaScript的BOM(浏览器对象模型)
总的来说,BOM提供了一种方式来与浏览器进行交互。通过BOM,你可以操作窗口、获取URL、操作历史、访问HTML文档、获取浏览器信息和屏幕信息等。虽然BOM并没有正式的标准,但大多数现代浏览器都实现了相似的功能,因此,你可以放心地在你的JavaScript代码中使用BOM。
64 23
|
2月前
|
存储 缓存 算法
JVM简介—1.Java内存区域
本文详细介绍了Java虚拟机运行时数据区的各个方面,包括其定义、类型(如程序计数器、Java虚拟机栈、本地方法栈、Java堆、方法区和直接内存)及其作用。文中还探讨了各版本内存区域的变化、直接内存的使用、从线程角度分析Java内存区域、堆与栈的区别、对象创建步骤、对象内存布局及访问定位,并通过实例说明了常见内存溢出问题的原因和表现形式。这些内容帮助开发者深入理解Java内存管理机制,优化应用程序性能并解决潜在的内存问题。
197 29
JVM简介—1.Java内存区域
|
28天前
|
Java 数据安全/隐私保护
Java 类和对象
本文介绍了Java编程中类和对象的基础知识,作为面向对象编程(OOP)的核心概念。类是对象的蓝图,定义实体类型;对象是具体实例,包含状态和行为。通过示例展示了如何创建表示汽车的类及其实例,并说明了构造函数、字段和方法的作用。同时,文章还探讨了访问修饰符的使用,强调封装的重要性,如通过getter和setter控制字段访问。最后总结了类与对象的关系及其在Java中的应用,并建议进一步学习继承等概念。
|
2月前
|
消息中间件 Java 应用服务中间件
JVM实战—2.JVM内存设置与对象分配流转
本文详细介绍了JVM内存管理的相关知识,包括:JVM内存划分原理、对象分配与流转、线上系统JVM内存设置、JVM参数优化、问题汇总。
JVM实战—2.JVM内存设置与对象分配流转
|
2月前
|
Java 数据库
【YashanDB知识库】kettle同步大表提示java内存溢出
在数据导入导出场景中,使用Kettle进行大表数据同步时出现“ERROR:could not create the java virtual machine!”问题,原因为Java内存溢出。解决方法包括:1) 编辑Spoon.bat增大JVM堆内存至2GB;2) 优化Kettle转换流程,如调整批量大小、精简步骤;3) 合理设置并行线程数(PARALLELISM参数)。此问题影响所有版本,需根据实际需求调整相关参数以避免内存不足。
|
2月前
|
设计模式 缓存 Java
重学Java基础篇—Java对象创建的7种核心方式详解
本文全面解析了Java中对象的创建方式,涵盖基础到高级技术。包括`new关键字`直接实例化、反射机制动态创建、克隆与反序列化复用对象,以及工厂方法和建造者模式等设计模式的应用。同时探讨了Spring IOC容器等框架级创建方式,并对比各类方法的适用场景与优缺点。此外,还深入分析了动态代理、Unsafe类等扩展知识及注意事项。最后总结最佳实践,建议根据业务需求选择合适方式,在灵活性与性能间取得平衡。
107 3
|
3月前
|
缓存 运维 监控
Anolis OS深度集成运维利器 阿里云操作系统控制台上线
阿里云在百万服务器运维领域的丰富经验打造。
Anolis OS深度集成运维利器 阿里云操作系统控制台上线
|
1月前
|
存储 缓存 Java
理解Java引用数据类型:它们都是对象引用
本文深入探讨了Java中引用数据类型的本质及其相关特性。引用变量存储的是对象的内存地址而非对象本身,类似房子的地址而非房子本身。文章通过实例解析了引用赋值、比较(`==`与`equals()`的区别)以及包装类缓存机制等核心概念。此外,还介绍了Java引用类型的家族,包括类、接口、数组和枚举。理解这些内容有助于开发者避免常见错误,提升对Java内存模型的掌握,为高效编程奠定基础。
71 0
|
1月前
|
Java
java中一个接口A,以及一个实现它的类B,一个A类型的引用对象作为一个方法的参数,这个参数的类型可以是B的类型吗?
本文探讨了面向对象编程中接口与实现类的关系,以及里氏替换原则(LSP)的应用。通过示例代码展示了如何利用多态性将实现类的对象传递给接口类型的参数,满足LSP的要求。LSP确保子类能无缝替换父类或接口,不改变程序行为。接口定义了行为规范,实现类遵循此规范,从而保证了多态性和代码的可维护性。总结来说,接口与实现类的关系天然符合LSP,体现了多态性的核心思想。
37 0
|
2月前
|
存储 算法 安全
Java对象创建和访问
Java对象创建过程包括类加载检查、内存分配(指针碰撞或空闲列表)、内存初始化、对象头设置及初始化方法执行。访问方式有句柄和直接指针两种,前者稳定但需额外定位,后者速度快。对象创建涉及并发安全、垃圾回收等机制。
Java对象创建和访问

热门文章

最新文章