【JVM技术专题】「原理专题」全流程分析Java对象的创建过程及内存布局

news2024/10/5 16:26:19

前言概要

对应过程则是:对象创建、对象内存布局、对象访问定位的三个过程。

对象的创建过程

对象的创建方式

java中对象的创建方式有很多种,常见的是通过new关键字和反射这两种方式来创建。除此之外,还有clone、反序列化等方式创建


通过new关键字创建
// Person zhangsan = new Person(id, height, weight)
Person zhangsan = new Person();

通过反射创建

反射创建对象,可以通过class.newInstance()调用无参的构造器创建对象,也可以使用构造器来创建constructor.newInstance()


//Class clz = Class.forName("Person类的全限定类名")
Class clz = Person.class;
Person zhangsan = clz.newInstance()
// 使用构造器创建
Constructor<Person> cons = clz.getConstructor()
// 也可以指定参数类型获取有参构造器
Person zhangsan1 = cons.newInstance()

通过clone创建对象

当类实现了Cloneable接口时,可以使用clone()方法复制一个对象。需要留意是clone方法是浅拷贝。

Person libo = new Person(name: "李博", age:12, ...)
Person Livonor = new Person(name: "Livorno", age:32, ...)
libo.setFather(Livonor)
Person zhangsi = libo.clone() // 此时,张四和张三的名字、父亲在内存中都引用了相同的对象
反序列化创建

通过读取IO数据流创建,非本节重点

对象的创建过程

检查类加载(包含是否初始化、是否被加载、是否被解析)

对于new和反射两种创建方式而言,需要检查创建对象所使用的参数是否已完成类加载(比如它的类型和参数类型)。如果没有,要先完成类加载过程

分配内存空间

  • 虚拟机为对象分配内存,即起始地址和偏移量

  • 对象所需要的空间在创建前就可以确定,但是起始地址需要在分配时去内存中找到一块足够大的空间。地址的分配有两种方式:指针碰撞和空闲列表

指针碰撞

指针碰撞的方式是假设内存空间是规整的,被使用的和空闲的内存被分割成了两整块,通过一个指针记录分界点。在给对象分配内存的时候,将指针空闲区域移动一段与对象大小相等的距离即可。

空闲列表

如果内存不规整,那么就需要维护一张表,来记录内存中那些地址是空闲的。分配对象时,通过空闲列表去找到一块足够大的空闲内存分配给对象并更新空闲列表。

多个线程创建的对象内存的冲突

举个例子,线程1和2同时要创建两个对象,指针是同一个。它们各自将指针加载到了cpu缓存,然后去执行分配地址空间的指令。结果就导致,后分配的哪一个,可能将先分配的那个对象的地址给覆盖了。

解决的办法有两种,一种是对分配内存的动作进行同步处理,即采用CAS加失败重试的方式,保证更新操作的原子性

// 伪代码表示CAS+失败重试
while(true){
    oldPtr = ptr //读取共享指针
    newPtr = oldPtr + sizeOfInstance
    if(compareAndSet(oldPtr, ptr, newPtr)){break}
}

另一种是使用TLAB的方式将线程的分配空间在堆内存中隔离开,在堆中为每个线程预先分配一小块不同的空间,每个线程创建对象都在自己对应的空间中完成

即每个线程在 Java堆中预先分配一小块内存(本地线程分配缓冲(Thread Local Allocation Buffer ,TLAB)),哪个线程要分配内存,就在哪个线程的TLAB上分配,只有TLAB用完并分配新的TLAB时才需要同步锁。虚拟机是否使用TLAB,可以通过-XX:+/-UseTLAB参数来设定。


分配完内存之后,对象就已经存在于虚拟机的堆中了,此时虚拟机要将分配的内存空间初始化为零值(对象头例外)。

设置对象头

对象头包含了两种信息:MarkWord和类型指针。

  • MarkWord:存放对象本身的运行时状态数据(如HashCode, GC分代年龄、锁状态、是否偏向信息等)

  • KlassPointer:类型指针指向它的类型的元数据。对象头在对象的内存布局中细讲

    • 即对象指向它的类元数据的指针
    • 虚拟机通过这个指针来确定这个对象是哪个类的实例
  • 数据长度:如果对象 是 数组,那么在对象头中还必须有一块用于记录数组长度的数据

    • 因为虚拟机可以通过普通Java对象的元数据信息确定对象的大小,但是从数组的元数据中却无法确定数组的大小。
执行对象实例构造函数

首先递归的执行父类的构造函数,然后收集本类中为实例变量赋值的语句并执行,最后执行构造方法中的语句

public class AddA {
    public static void main(String[] args) {
        Father guy = new Son(30);
        guy.saySomething();
        System.out.println(guy.age);
    }
}

class Father{

    int age = 60;

    public Father() {
        saySomething();
    }

    public Father(int age) {
        this.age = age;
    }

    public void saySomething(){
        System.out.println("I am the father, " + age + "years old");
    }
}

class Son extends Father{

	int age = 20;

    public Son(int age) {
        // super();  不写则隐式调用方法,写则必须在子类构造方法的第一句
        saySomething();
        this.age = age;
        saySomething();
    }

    public void saySomething(){
        System.out.println("I am the son, " + age + " years old");
    }
}

因为它涉及到了多态与方法的动态分派。在这里先简单描述一下它的执行过程,用来掌握构造方法的执行还是ok的。

  • 首先,创建一个Son对象,然后调用其有参构造方法Son(int age)。

    • 在有参构造方法中隐式调用了父类的无参构造方法,然后父类的构造方法继续调Object的构造方法。接下来收集为父类成员变量赋值的语句并执行。

    • 由于多态中子类的成员变量会覆盖父类的成员变量,因此子类对象的age仍然是0。

    • 同时无参构造方法中的saySomething()此时是被子类对象调用的,因此打印了第一句I am the son, 0 years old。

  • 然后,super()方法出栈,回到子类构造方法中。此时应该收集为子类成员变量赋值的语句并执行。对象的age=20,saySomething()打印出第二句I am the son, 20 years old。

    • 然后执行构造方法中的赋值语句int age = age;saySomething();

    • 第三句话被打印I am the son, 30 years old。

  • 子类对象创建完成,回到main方法。此时使用多态,将对象转成Father对象。

由于多态的规则:被重写的方法使用动态分派,查找(vtable)方法表,该方法实际是属于子类对象的

因此guy.saySomething()实际调用的是子类对象的方法,打印出第四句话,I am the son, 30 years old。

最后,输出guy.age. 成员变量不具备多态性,因此打印出父类对象的age 60.

I am the son, 0 years old
I am the son, 20 years old
I am the son, 30 years old
I am the son, 30 years old

对象的内存布局

对象在堆中的存储布局划分为三个部分:对象头、实例数据和对其填充(padding)

对象的内存布局

对象头

对象头中包含markword(标记字段)和类型指针【数组长度】。

markword

markword存储与对象自身定义数据无关的信息,用来表示对象的运行时状态。包括了HashCode,GC年龄,锁状态等信息。在一个32位的虚拟机中,markword用一个32位的bitmap表示,bitmap最后两位存放锁状态信息,如下图。

  • markword

    • 普通状态下,状态为01,存储hashcode,分代年龄,偏向锁状态为0。

    • 偏向锁状态下,状态为01,存储持有偏向锁的线程和重入次数,分代年龄,偏向锁状态为1。此时hashcode没了,但是,hashcode可以通过Object的hashcode()方法计算出来,只要没有重写该方法,那么得到的哈希码始终是一致的。

    • 轻量级锁,状态为00。通过cas方式将对象的markword信息原子性地交换到了持有该对象锁的线程中,存储在lockRecord内,并同时将lockRecord的指针存放在对象头Markword的前30位。

    • 重量级锁状态下,前30位存放指向锁控制器Monitor的指针,锁状态为10.

    • 对象被标记为待回收状态时,最后两位状态为11.

  • KlassPointer(类型指针)

指向类型元数据,从而可以通过对象来访问到它的类型信息

  • 数组长度(array length)

主要记录数组的长度信息一般为4字节(根据int的范围来考虑)

实例数据
  • 实例数据中存放了对象的字段信息。无论是从父类继承的,还是在子类中定义的,都保存在实例数据中

  • 按照一定顺序存放,在满足这个顺序的条件下,父类定义的字段又会出现在子类定义的变量之前

即代码中定义的字段内容

注:这部分数据的存储顺序会受到虚拟机分配参数(FieldAllocationStyle)和字段在Java源码中定义顺序的影响。

// HotSpot虚拟机默认的分配策略如下:

longs/doubles、ints、shorts/chars、bytes/booleans、oop(Ordinary Object Pointers)

  • // 从分配策略中可以看出,相同宽度的字段总是被分配到一起
  • // 在满足这个前提的条件下,父类中定义的变量会出现在子类之前

CompactFields = true;

// 如果 CompactFields 参数值为true,那么子类之中较窄的变量也可能会插入到父类变量的空隙之中。

对齐填充

如果对象的实例数据占用空间不是8的整数倍,则填充0值让对象的占用空间位8的整数倍。

对象的访问定位

常见的有两种方式,句柄访问和直接指针访问。

在这里插入图片描述

句柄访问

  • 使用句柄访问的话,对象的引用(如zhangsan),指向的是句柄池中的某个句柄,该句柄存放了指向实际实例对象的指针和指向方法区数据类型的指针

    • 其好处是,当对象被移动的时候(比如垃圾回收时,整理内存空间需要大量移动对象),不需要频繁的修改引用,只需要修改句柄中实例数据指针。

  • 通过指针访问,则是对象的引用直接指向了该对象。其好处是,通过引用访问对象时,不需要多一次的指针定位,使得访问速度更快

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/37661.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

CSS @property,让不可能变可能

本文主要讲讲 CSS 非常新的一个特性&#xff0c;CSS property&#xff0c;它的出现&#xff0c;极大的增强的 CSS 的能力&#xff01; 根据 MDN -- CSS Property&#xff0c;property CSS at-rule 是 CSS Houdini API 的一部分, 它允许开发者显式地定义他们的 CSS 自定义属性&…

vue项目分环境配置打包处理

vue项目分环境配置打包处理 目录 vue项目分环境配置打包处理 本地环境配置 生产环境配置 打包处理 打包配置处理&#xff08;扩展&#xff09; 本地环境配置 定义需要的变量&#xff0c;这里定义了各种变量标识&#xff0c;可参考使用&#xff0c;自定义项目需要的变量&…

设计问卷调查有哪些技巧?

调查问卷可以很好地帮助我们进行市场调研&#xff0c;所以想要做出一份有效的调查问卷&#xff0c;首先要确定问卷的主题。明确的主题就是定基调&#xff0c;可以让我们的后续过程更加顺利。然后我们再开始进行题目的设置和问卷的设计等动作。不过&#xff0c;在问卷设计的过程…

【js】【爬虫】fetch + sessionStorage 快速搭建爬虫环境及各种踩坑

文章目录导读需求开发环境fetch介绍为什么选择fetchfetch的封装使用数据存储数据访问封装多页面处理方案数据过大&#xff0c;拆分处理参考资料导读 需求 一说爬虫&#xff0c;很多人都会向导python&#xff0c;不过&#xff0c;真正省心的方案&#xff0c;应当是通过js控制获…

Reactive源码分析

Reactive用来绑定引用数据类型, 例如对象和数组等,实现响应式。 Reactive API 接口 export function reactive<T extends object>(target: T): UnwrapNestedRefs<T>相关API包括readonly、createReactiveObject、shallowReadonly、isReactive、toReactive。源码运…

Eureka注册中心

文章目录一、认识服务提供者和服务调用者二、Eureka 的工作流程三、服务调用出现的问题及解决方法四、搭建 eureka-server五、注册 user-service、order-service六、在 order-service 完成服务拉取&#xff08;order 模块能访问 user 模块&#xff09;七、配置远程服务调用八、…

从入门到进阶!当下火爆的大数据技术及算法怎么还能不知道 一起来学习互联网巨头的大数据架构实践!

大数据被称为新时代的黄金和石油&#xff0c;相关技术发展迅猛,所应用的行业也非常广泛&#xff0c;从传统行业如医疗、教育、金融、旅游&#xff0c;到新兴产业如电商、计算广告、可穿戴设备、机器人等。大数据技术更是国家科技发展和智慧城市建设的基础。 当前“互联网”新业…

骨传导耳机是怎么传声的?骨传导耳机会伤害耳朵吗?

作为一个耳机发烧友&#xff0c;平时最喜欢的就是捣鼓耳机。这几年入手了几十款耳机&#xff0c;头戴式、入耳式、半入耳、有线无线都会接触一些来玩&#xff0c;其中还有骨传导耳机这个相对小众的款类。 说到骨传导耳机&#xff0c;我应该算是最早一批的用户了&#xff0c;很…

web网页设计—— 指环王:护戒使者(13页) 电影网页设计 在线电影制作 个人设计web前端大作业

HTML实例网页代码, 本实例适合于初学HTML的同学。该实例里面有设置了css的样式设置&#xff0c;有div的样式格局&#xff0c;这个实例比较全面&#xff0c;有助于同学的学习,本文将介绍如何通过从头开始设计个人网站并将其转换为代码的过程来实践设计。 文章目录一、网页介绍一…

数据结构-ArrayList解析和实现代码

arrayList结构的实现是数组结构&#xff0c;数组没有扩容机制&#xff0c;arrayList的扩容机制采用的是复制数组&#xff0c;了解你会发现ArrayList虽然实现比较简单&#xff0c;但是设计还是很巧妙的。咱们先来简单实现下.. 咱们看下定义的全局变量 1.默认初始化空间为10&am…

docker 安装 Jenkins

一、Jenkins 安装 增加挂载目录和权限 # 增加挂载目录和权限mkdir /workspace/jenkins_homechown -R 1000:1000 /workspace/jenkins_home/创建容器 docker run --name jenkins -d \ -p 9999:8080 \ -p 8888:8888 \ -p 50000:50000 \ -v /workspace/jenkins_home:/var/jenkins…

[附源码]java毕业设计智慧教室预约

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

java检验mp4文件完整性的一个方法:使用ffmpeg

问题引入 最近笔者在写一个多线程下载视频文件的程序&#xff0c;打算让这个程序在我的空闲服务器上运行&#xff0c;但是几轮测试之后发现&#xff0c;有时候会存在下载的视频文件不完整的情况&#xff0c;这就导致了有些文件无法正常播放 问题排查 经过一周的排查后&#…

小白学流程引擎-FLowable(一) —FLowable是什么

小白学流程引擎-FLowable(一) | FLowable是什么 一、什么是流程引擎&#xff1f; 通俗的说&#xff0c;流程引擎就是多种业务对象在一起合作完成某件事情的步骤&#xff0c;把步骤变成计算机能理解的形式就是流程引擎。 流程引擎&#xff0c;用来驱动业务按照设定的固定流程…

《Kafka 源码实战》看完面试不慌!

Kafka 一开始是 LinkedIn 公司开发的消息队列&#xff0c;随着 Kafka 代码被贡献给 Apache 软件基金会后&#xff0c;就成功孵化成 Apache 顶级项目&#xff0c;世界上有越来越多的公司和个人开始使用 Kafka&#xff0c;所以 Kafka 使用的范围是很普遍的。 同时&#xff0c;值得…

vue实现文件上传压缩优化处理

vue js实现文件上传压缩优化处理 两种方法 &#xff1a; 第1种是借助canvas的封装的文件压缩上传第2种&#xff08;扩展方法&#xff09;使用compressorjs第三方插件实现 目录 vue js实现文件上传压缩优化处理 借助canvas的封装的文件压缩上传 1.新建imgUpload.js 2.全局引…

grafana变量使用

注&#xff1a;基于Grafana v8.3.6编写 1 添加变量 在dashboard界面点击setting&#xff0c;就能进入设置页面&#xff0c; 再点击Variables tab&#xff0c;就可以添加变量 比如我们添加一个系统架构的变量&#xff0c;用于区分Linux和Windows系统&#xff0c;通过node_una…

这可能是2022年把微服务讲的最全了:SpringBoot+Cloud+Docker

前言 最近几年&#xff0c;微服务可谓是大行其道。在业务模型不完善&#xff0c;超大规模流量的冲击的情况下&#xff0c;许多企业纷纷抛弃了传统的单体架构&#xff0c;拥抱微服务。这种模式具备独立开发、独立部署、可扩展性、可重用性的优点的同时&#xff0c;也带来这样一…

【云原生】K8S master节点更换IP以及master高可用故障模拟测试

文章目录一、前言二、配置 多个master 节点1&#xff09;节点信息1&#xff09;安装docker或containerd2&#xff09;安装kubeadm&#xff0c;kubelet和kubectl1、配置k8s yum源2、修改sandbox_image 镜像源3、配置containerd cgroup 驱动程序systemd4、开始安装kubeadm&#x…

SpringBoot SpringBoot 原理篇 1 自动配置 1.7 bean 的加载方式【五】

SpringBoot 【黑马程序员SpringBoot2全套视频教程&#xff0c;springboot零基础到项目实战&#xff08;spring boot2完整版&#xff09;】 SpringBoot 原理篇 文章目录SpringBootSpringBoot 原理篇1 自动配置1.7 bean 的加载方式【五】1.7.1 register1 自动配置 1.7 bean 的…