JAVA中Object对象内存布局JOL
- 前言:
我们都知道JAVA是面向对象的语言,JAVA中的所有的类都继承自Object;那某一天你在面试的时候,有一个为难你的面试官给你灵魂三问:“Object是什么?能干什么?占用多少内存?” 如果这三个问题都能回答上了那恭喜你,就不用往下看了 - Objcet内存整体布局初探
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uxrnWPHT-1687526591741)(./img/1.1.JOL.png)]
说明:Object对象 由12Bytes的对象头+对象体组成,由于Object对象没有属性,所以对象体不占空间,那Java Object对象是不是只有12个字节呢;答案是否定了,为了对象占用内存字节数能被8整除,所以要进行字节补齐;12字节无法被8整除,能被8整除,那就只能再补齐4个字节凑成16Bytes;所以正常情况下
Object对象占用16Bytes;后续我们会说不正常情况;
其中12字节的对象头中前8字节是markword,后面4字节是生成该对象的类指针Class point;其中m
arkword是什么,在后面回揭晓;
-
代码验证Object的对象和Persion的对象
-
验证过程:
说明:引用jol包,这个包是用来进行对象内存布局打印的;以下是jol包一些常用的方法
- 计算对象的大小(单位为字节):ClassLayout.parseInstance(obj).instanceSize()
- 查看对象内部信息: ClassLayout.parseInstance(obj).toPrintable()
- 查看对象外部信息:包括引用的对象:GraphLayout.parseInstance(obj).toPrintable()
- 查看对象占用空间总大小:GraphLayout.parseInstance(obj).totalSize()
后续通过jol包打印Objcet对象内存布局和自己写的Persion类的内存布局,Persion类就加了一个字符串类型的name属性;
<dependency> <groupId>org.openjdk.jol</groupId> <artifactId>jol-core</artifactId> <version>0.10</version> </dependency>
public class App { public static void main( String[] args ) { System.out.println("Object内部信息"); Object o=new Object(); System.out.println(ClassLayout.parseInstance(o).toPrintable()); System.out.println("Persion内部信息"); Persion p=new Persion(); System.out.println(ClassLayout.parseInstance(p).toPrintable()); } }
-
验证结果
从图中可以看见正如上面所说Object对象总共占用了16个字节,其中markword 8字节,类指针(Class point)4个字节,由于这markword和class point总共就12个字节,不能被8整除,所以添加了4字节的填充,加一起总共16字节;
由于Java对象中所有对象都继承自Objcet,所以Persion对象肯定包含Object的12位对象头,但是由于Persion类自身包含了一个字符串属性指向这个属性的指针是4字节(当然对象内存中不会直接存储这个字符串属性本身,而是存储指向字符串实际存储位置的指针),对象头+对象体=16个字节能被8整除,这个时候就无需加入填充字节;所以Persion类生成的对象和Objcet生成的对象本身
占用字节数都是16字节(当然这个不等于Object和Persion生成的对象实际占用内存一样,因为persion属性实际还要占用内存)
也许大家很疑惑,我64位机器怎么类指针只有4个字节(32位),同时Persion字符串类型的指针也只有4字节?别着急,稍后看下面的JVM参数-XX:+UseCompressedOops和 -XX:+UseCompressedClassPointers;
-
-
虚拟机参数UseCompressedOops和UseCompressedClassPointers
带着上面指针怎么只有4字节的疑问,认识一下UseCompressedOops和UseCompressedClassPointers;
UseCompressedOops:普通对象指正压缩,其中Oop就是"ordinary object pointer";UseCompressedClassPointers配置是否开启类指正压缩;默认JVM开启了普通对象指针压缩和类职称压缩;所以上面类指针和对象属性指针都只有4个字节;这就是上面说的正常情况了;如果非正常(关闭UseCompressedOops和UseCompressedClassPointers)情况Object占用多少内存呢?答案还是16Bytes,关闭普通对象指针压缩和类指针压缩,那64位机器,指针是64位也就是8字节,Object就有8字节的markword加上8字节的类指针还是16字节,和开启指针压缩的唯一区别就是这个时候类没有字节补齐;当然非正常情况下(关闭指针压缩)Persion类对象就是16字节头+8字节的Persion属性,总共24字节,由于能被8整除所以没有字节对齐填充
java -XX:+PrintCommandLineFlags --version
-XX:ConcGCThreads=2
-XX:G1ConcRefinementThreads=8
-XX:GCDrainStackTargetSize=64
-XX:InitialHeapSize=534566336
-XX:MarkStackSize=4194304
-XX:MaxHeapSize=8553061376
-XX:MinHeapSize=6815736
-XX:+PrintCommandLineFlags
-XX:ReservedCodeCacheSize=251658240
-XX:+SegmentedCodeCache
# 类指针压缩
-XX:+UseCompressedClassPointers
# 普通对象指针压缩,oops: ordinary object pointer
-XX:+UseCompressedOops
-XX:+UseG1GC
-XX:-UseLargePagesIndividualAllocation
java 17.0.4.1 2022-08-18 LTS
Java(TM) SE Runtime Environment (build 17.0.4.1+1-LTS-2)
Java HotSpot(TM) 64-Bit Server VM (build 17.0.4.1+1-LTS-2, mixed mode, sharing)
- Mark Word说明
以下是简单说明,后续文章结合synchronized进行详细说明- 锁标志:2位,锁状态的标记位,
- 偏向锁标志:1位对象是否存在偏向锁标记。锁标志与偏向锁标记共同表示锁对象处于什么锁状态。
- 分代年龄:4位,表示JAVA对象的年龄,在GC中,当survivor区中对象复制一次,年龄加1,如果到15之后会移动到老年代,并发GC的年龄阈值为6.
- identity_hashcode:31位,调用方法 System.identityHashCode()计算,并会将结果写到该对象头中。当对象加锁后(偏向、轻量级、重量级),MarkWord的字节没有足够的空间保存hashCode,因此该值会移动到线程 Monitor中。
- 当前线程指针Thread:54位,持有偏向锁的线程ID(此处的线程id是操作系统层面的线程唯一Id,与java中的线程id是不一致的,了解即可)。
- epoch:2位,偏向锁的时间戳。
- 指向线程中Lock Record: ptr_to_lock_record,62位,轻量级锁状态下,指向栈中锁记录的指针。
- 指向互斥量的指针:ptr_to_heavyweight_monitor,62位,重量级锁状态下,指向对象监视器 Monitor的指针。