首页 > 系统相关 >承前启后,Java对象内存布局和对象头

承前启后,Java对象内存布局和对象头

时间:2024-02-21 15:35:17浏览次数:28  
标签:Java 字节 对象 Object JVM 承前启后 new

承前启后,Java对象内存布局和对象头

大家好,我是小高先生。在我之前的一篇文章《并发编程防御装-锁(基础版)》中,我简要介绍了锁的基础知识,并解释了为什么Java中的任何对象都可以作为锁。在那里,我提到了对象头中有一个指向ObjectMonitor的指针,但没有深入探讨Java对象的内存结构。本文将引导大家深入了解Java对象的内存布局以及对象头结构,帮助大家更好地理解Java中的对象和锁,并为之后学习synchronized和锁升级打下基础。

  • new Object()怎么理解?
  • JOL
  • GC分代年龄

new Object()怎么理解?

平时我们开发的时候经常要new一个对象,很少想过这个对象存在哪里以及这个对象是有什么构成的,所以关于new Object的理解可归于两点:

  1. 位置所在(where)

学过JVM的同学都应该知道JVM内存结构,我们new出来的对象就在堆区,大对象在老年代,小对象在新生代。

  1. 构成布局(what)

1.对齐填充

先讲一下对齐填充,这个比较简单。HotSpot虚拟机的自动内存管理系统要求对象的起始地址必须是8字节的整数倍,也就是对象的大小是8字节的整数倍。对象头已经被设计成8字节的倍数,通常是1倍或2倍,如果实例数据大小不是8字节的整数倍,就需要用对齐填充添加一下,使对象大小为8字节的整数倍。

2.对象头

对象头是对象的另一个重要组成部分,它包含了一些关于对象的元信息。具体来说,对象头包括Mark Word和类元信息(类型指针)。

Mark Word

平时写代码的时候可能会通过hashcode()方法获取对象的哈希码,那哈希码保存在哪里呢?还有JVM中垃圾回收机制里说过,当一个对象在新生代经过15次Minor GC之后还存活下来就会进入老年代,那对象怎么记录GC次数呢?这些信息都记录在Mark Word中,先不需要都知道里面的标记代表什么,后续会讲到,这里只需要知道Mark Word存储了对象的一些重要信息。

存储内容 标志位 状态
对象哈希码、对象分代年龄 01 未锁定
指向锁记录的指针 00 轻量级锁定
指向重量级锁的指针 10 重量级锁定
空,不需要记录信息 11 GC标记
偏向线程ID、偏向时间戳、对象分代年龄 01 可偏向

在64位系统中,Mark Word占8字节,类型指针占8字节,一共16字节。所以当我们new一个对象,还没有实例数据,那这个对象就是16字节。64为系统下,Mark Word就是64bit大小,存储结构如下:

比如现在对象是无锁状态,那Mark Word前25bit不用,之后的31bit存hashCode,偏向锁标志位为1,锁标志位为01,其他锁状态会在相关文章中讲解。

Mark Word默认存储hashCode、分代年龄和锁标志位等相关信息,这些信息都是与对象自身定义无关的数据,会根据对象的状态复用自己的存储空间,运行期间数据会随着锁状态而改变

类元信息(类型指针)

Phone ph = new Phone(),创建一个对象,会在JVM方法区存放对象的Klass类元信息,对象头的类型指针就是这个对象指向它的类元信息的指针,JVM通过类型指针确定这个对象是哪个类的实例,比如我创建Animal类,new Animal()实例的类型指针就会指向方法区中的Animal的Klass类元信息。由于Animal里面没有创建属性,所以Animal对象还没有实例数据,大小为16字节。

public class ObjectHeadDemo {
    public static void main(String[] args) {
        Animal a = new Animal();
    }
}
class Animal{

}

我给大家百度了Klass类元信息具体是什么,因为一开始我也不懂。

Klass类元信息是JVM中表示类的元信息的数据结构

JVM中,每个Java类都对应一个Klass实例,是用C++编写的一个类,存储了Java类的所有元信息,包括属性、方法、修饰符等。Klass模型是JVM内部使用的一个概念,是Java类的内部表示。这个模型包含了类的所有信息,如类的完整名称、访问修饰符、父类、实现的接口、属性和方法等。这些信息在类加载的过程中被读取并存储到方法区。

这么看起来,Klass就想是这个类的模板。

3.实例数据

class Animal{
	int id;
    boolean flag = false;
}

如果创建了一个Animal对象,大小就是对象头(16字节)+ int(4字节)+ boolean(1字节)= 21字节,需要有对齐填充,21 + 3 = 24字节。

JOL

有关Java对象布局的理论知识已经学完了,那能不能从代码层面验证一下对象的结构呢。

JOL(Java Object Layout)是一个专门用于分析Java虚拟机(JVM)中对象内存布局的工具箱。它能够提供非常精确的关于对象如何分布在JVM内存中的信息。

1.先在POM文件中导入坐标

<dependency>
    <groupId>org.openjdk.jol</groupId>
    <artifactId>jol-core</artifactId>
    <version>0.17</version>
</dependency>

2.通过指令查看JVM详细细节

public class JOLDemo {
    public static void main(String[] args) {
        //VM的详细细节
        System.out.println(VM.current().details());
        //获取当前JVM实例的对象对齐方式
        System.out.println(VM.current().objectAlignment());
       
    }
}
class Customer{
    int id;
    boolean flag = false;
}

Object alignment指的是对象在内存中的对齐方式。8 bytes表示对象的大小必须是8的倍数,即对象的起始地址必须是8的整数倍。

我们创建一个Object类,看看这个类具体的结构是什么样的,通过ClassLayout.parseInstance(o).toPrintable()来查看。

public class JOLDemo {
    public static void main(String[] args) {
        Object o = new Object();
        System.out.println(ClassLayout.parseInstance(o).toPrintable());
    }
}

参数 作用
OFFSET 偏移量,也就是这个字段起始位置
SIZE 字节大小
TYPE Class中定义的类型
DESCRIPTION 类型描述
VALUE 是TYPE在内存中的值

我们这里会发现类型指针只占了4字节,之前说的是占8字节,这是为什么?这个我们稍后讨论。

另外一个细节会发现Instance size为16字节,因为对象头不够8的倍数,要补齐,也就是new一个对象默认大小为16字节。刚刚是创建了一个Object对象,现在再创建一个自己写的类看看结果,并且类中没有实例数据。结果表明自己写的类和Object类new出来的对象内存布局是一样的。

public class JOLDemo {
    public static void main(String[] args) {
        Object o = new Object();
        //System.out.println(ClassLayout.parseInstance(o).toPrintable());
        
        Customer c = new Customer();
        System.out.println(ClassLayout.parseInstance(c).toPrintable());
    }
}
class Customer{
	//只有对象头,没有实例数据
}

另外一种情况是类中有实例数据,看看结果,这次除了对象头以外多了实例数据,然后也是用对齐填充补到8的倍数。

public class JOLDemo {
    public static void main(String[] args) {
        Object o = new Object();
        //System.out.println(ClassLayout.parseInstance(o).toPrintable());

        Customer c = new Customer();
        System.out.println(ClassLayout.parseInstance(c).toPrintable());
    }
}
class Customer{
    int id;
    String name;
}

现在来研究研究为什么类型指针是4而不是8,这是因为压缩指针的影响。使用java -XX:+PrintCommandLineFlags -version指令,启动Java程序的时候把所有参数打印出来。-XX:+UseCompressedClassPointers这个参数就表示使用了压缩指针,默认开启,结果就导致类型指针是4字节。


试试不开启指针压缩是什么样,正常工作中是不会用到的,这里就是跟大家尝试一下,看看结果而已。

GC分代年龄

GC年龄在Mark Word中用4bit存储,最大为15,也就是JVM参数-XX:MaxTenuringThreshold = 15。我们也可以验证一下,调整参数改成16试试,运行时是有异常的。

总结

本文和朋友们一起学习Java对象内存布局的知识,对象由对象头、实例数据和对齐填充组成。对象头包括Mark Word和类型指针,Mark Word记录一些对象的关键信息,主要包括锁的状态,类型指针是可以确定这个对象是哪个类的实例。JVM要求对象大小是8字节的整数倍,所以当对象头和实例数据大小不是8字节整数倍的时候,就需要对齐填充帮忙补齐。

东西不多但是挺重要的,可以更好地理解之前讲的synchronized底层分析,也可以对未来要学的锁升级做好准备。

标签:Java,字节,对象,Object,JVM,承前启后,new
From: https://www.cnblogs.com/GXH980804/p/18025256

相关文章

  • Java的配置
    环境变量配置找到配置的位置右击此电脑-->属性-->高级系统设置-->环境变量-->系统变量配置Path环境变量(必须配置的)(目的:为了可以在任意目录下找到javac和java命令)方式1:直接在Path变量中添加jdk的bin目录的完整路径系统变量-->Path-->新建-->D:\soft\java\jdk\bin方式2:(推荐......
  • java练习2(四位数字进行加密)
    packagecom.shujia.zuoye;importjava.util.Scanner;/*某个公司采用公用电话传递数据,数据是四位的整数,在传递过程中是加密的,加密规则如下:每位数字都加上5,然后用和除以10的余数代替该数字,再将第一位和第四位交换,第二位和第三位交换。结果如图所示。*/publicclass加密......
  • java练习1(求圆的周长与面积)
    packagecom.shujia.zuoye;importjava.util.Scanner;/*输入圆形半径,求圆形的周长和圆形的面积,并将结果输出。/publicclass求圆的面积{publicstaticvoidmain(String[]args){Scannersc=newScanner(System.in);System.out.println("请输入圆的半径:");doubler=s......
  • 在阿里云部署javaspringboot项目
    记住自己服务器的账号密码配置安全组  用xshell连接服务器(xftp同理) 到官网去下载jdk的Linux版本,官网地址:https://www.oracle.com/technetwork/java/javase/downloads 安装JDK我自己用的是jdk21,下载完毕后用xftp传到服务器上(解压一下)#tar-zxvf压缩包.tar.......
  • 验证对象和map赋值一样,多个方法赋值,只要中间没有重新new对象,值就会一直存在
    packageservice;importbase.BaseSpringTest;importcom.bestpay.settle.unity.certify.integration.model.CertifyInfoBO;importlombok.extern.log4j.Log4j2;importorg.junit.Test;importjava.util.HashMap;importjava.util.Map;/***场景测试类**@authorzhangkuankuan......
  • java~Date和LocalDateTime及Instant的使用场景
    在Java中,LocalDateTime、Date和Instant分别代表了不同的日期时间类型,它们之间有一些区别和适用场景。Date:java.util.Date是Java早期的日期时间类,它包含了日期和时间信息,但是在设计上存在一些问题,因此并不推荐在新的代码中使用。Date类存在线程安全性问题,同时它的......
  • Java基本语法
    Java基本语法1.1注释1.单行注释//2.多行注释/**/3.文档注释/***/1.2标识符和关键字Java所有的组成部分都需要名字。类名,变量名,方法名都被称为标识符。关键字:所有标识符都应该以字母,$,下划线开头。首字母之后可以是字母,$,__或者数字任何字符组合。关键......
  • Java入门
    Java入门1.1Java特性和优势简单性面向对象可移植性高性能分布式动态性多线程安全性健壮性1.2Java三大版本JavaSE:标准版JavaME:嵌入式开发JavaEE:企业级开发1.3JDK,JRE,JVMJDK:JavaDevelopmentKitJRE:JavaRuntimeEnvironmentJVM:JavaVirtualMachi......
  • 面向对象编程
    1.对象面向对象与面向过程1)两者都是一种编程思想2)面向对象强调的是事情的结果,我们通过对象完成对应的功能3)面向过程强调的是事情的过程,我们做任何事情,都要亲力亲为,经过每一个过程4)Java是一门面向对象的语言类与对象1)定义类通过关键字class来定义,类是一类事物的抽......
  • JavaSE的第七步 —— 开发者工具、控制语句、if单分支,if-esle双分支,if-else if-else多
    一、开发者工具工欲善其事,必先利其器。作为一个学习Java的小白,一个好的工具对我们的开发来说可以说是事半功倍。在网上看了很多大神们都推荐的使用IDEA开发工具,前30天可以免费使用。而在30天后大神提供了相应的解决方法,只要想学,办法总比困难多。加油每一个求学者。二、控制语句......