【Java】对象的实例化过程

0、前情


对于经常写代码的同学有没有思考这样一个问题:为什么成员变量有默认值?为什么局部变量必须手动赋值?

  1. 先不考虑变量类型,如果没有默认值会怎么样?变量存储的是内存地址对应的任意随机值,如果不对其赋值,那么程序读取该内存地址时拿到的值是随机的,运行会出现意外。
  2. 对于编译器(javac)来说,局部变量没赋值很好判断,可以直接报错。而成员变量可能是运行时赋值,无法判断,误报“没默认值”又会影响用户体验,所以采用自动赋默认值。

拓展:为什么说对编译器来说,局部变量没有赋值很好判断?而成员变量不好判断?

在Java中,局部变量(在方法、构造函数或块中声明的变量)和成员变量(在类中声明的变量)在编译器的处理上有一些不同之处。

对于局部变量来说,编译器可以在编译时检查到是否对其进行了赋值。这是因为局部变量的生命周期在方法、构造函数或块的执行期间,编译器可以跟踪每个变量在代码块中是否被初始化。如果编译器发现某个局部变量在使用之前没有被赋值,它就会报告编译错误,因为这样的情况很可能是代码中的错误。这种静态检查使得编译器可以在编译时就发现潜在的错误,而不必等到运行时。

相比之下,对于成员变量来说,情况则更复杂一些。成员变量的生命周期与对象的生命周期相关联,而对象的创建可能发生在运行时。因此,编译器无法确定对象在运行时是否会对成员变量进行初始化。在Java中,如果成员变量没有被明确初始化,它们会被赋予一个默认值(如o、false或null,具体取决于变量的类型)。这样做的目的是为了确保在对象创建时,成员变量都有一个合理的初始值,以防止潜在的运行时错误。

因此,尽管对于编译器来说,局部变量没有赋值更容易判断,但成员变量的初始化更为复杂,因为它们的初始化可能发生在运行时,编译器无法静态地确定。

以下我将阐述在 Java 语言中,对象的实例化过程,相信在阅读之后上述这些问题都能迎刃而解。

1、整体流程

从整体上来看对象的整个实例化过程如下图所示:

为了故事的顺利发展,这里我们定义一个Demo,并据此详细讨论一下dc对象是如何创建并实例化出来的。

public class Demo {public static void main(String[] args) {DemoClass dc = new DemoClass();}
}
class DemoClass {private static final int a = 1;private static int b = 2;private static int c;private int d = 4;private int e;static{c = 3;}public DemoClass() {e = 5;}}

2、类初始化检查

这里我们使用 new 关键字创建对象,Java 中创建对象的方式还有好多种,比如反射,克隆,序列化与反序列化等等。这些方式不一而同,但是经过编译器编译之后,对应到 Java 虚拟机中其实就是一条 new(这里的 new 指令与前面提到的 new 关键字不同,这是虚拟机级别的指令)指令。

当Java虚拟机碰到一条 new 指令时,会首先根据这条指令所对应的参数去常量池中查找是否有该类所对应的符号引用,并判断该类是否已经被加载、解析、初始化过,也就是到方法区中检查是否有该类的类型信息,如果没有,首先要进行类加载与初始化。如果类已经加载和初始化,那么继续后续的操作。

这里假设DemoClass类还没有被加载与初始化,也就是方法区中还没有DemoClass的类型信息,这时需要进行DemoClass类的加载与初始化。

3、类加载过程

类加载过程总的可分为7个步骤:

  1. 加载
  2. 验证
  3. 准备
  4. 解析
  5. 初始化
  6. 使用
  7. 卸载

这里我们看一下前六个阶段。

加载

加载阶段主要干了三件事:

  1. 根据类的全限定名获取类的二进制字节流。
  2. 将二进制字节流所代表的静态存储结构转化为方法区中运行时数据结构。
  3. 在内存中创建一个代表该类的Java.lang.Class对象,作为方法区这个类的各种数据的访问入口。

具体到这里就是首先根据package.DemoClass全限定名定位DemoClass.class二进制文件,然后将该.class文件加载到内存进行解析,将解析之后的结果存储在方法区中,最后在堆内存中创建一个Java.lang.Class的对象,用来访问方法区中加载的这些类信息。

验证

验证阶段完成的任务主要是确保class文件中字节流中包含的信息符合Java虚拟机的规范,虽然说得很简单,但是Java虚拟机进行了很多复杂的验证工作,总的来说可分为四个方面:

  • 文件格式验证
  • 元数据验证
  • 字节码验证
  • 符号引用验证


具体到这里就是对于加载进内存的DemoClass.class中存储的信息进行虚拟机级别的校验,以确保DemoClass.class中存储的信息不会危害到Java虚拟机的运行。

准备

准备阶段完成的工作就是为类变量(也就是静态变量)分配内存并赋予初始值,通常情况下是变量所对应的数据类型的零值。但是在这个阶段,被final修饰的变量也就是常量会在这个阶段准确的被赋值。

具体到这里,在这个阶段DemoClass中的a会被赋值为1,b与c均被赋值为0。

解析

这个阶段主要的任务是将常量池中的符号引用替换为直接引用。

初始化

在之前的阶段中,除了加载阶段通过自定义的类加载器可以干预虚拟机的加载过程外,其他的阶段都是虚拟机完全主导,而在初始化阶段才开始根据程序员的意愿执行类的初始化,这个阶段主要完成的工作是执行类构造器方法(),同时虚拟机会保证执行该类的类构造器方法时,其父类的类构造器方法已经被正确的执行,同时,由于类的初始化只进行一次,当多个线程并发的进行初始化时,虚拟机可以确保多个线程只有一个可以完成类的初始化工作, 保证线程安全工作。

具体到DemoClass类,在这个阶段会将b赋值为2,c赋值为3。

4、内存分配

当类加载过程完成后,或者类本身之前已经被加载过,下一步就是虚拟机要为新生对象分配内存。对象所需要的内存空间在类加载过程完成后就可以完全确定下来,为对象分配内存空间就相当于从堆内存中划分出一块合适的内存来,分配内存的主要方式有两种:指针碰撞和空闲列表。

  • 指针碰撞:这种方式将堆内存分为空闲空间与已分配空间,使用一个指针来作为二者之间的分界线,当要为新生对象分配内存空间的时候,相当于将指针向着空闲空间的方向移动一段与对象大小相等的距离,可见这种分配方式Java堆内存必须是规整的,所有空闲空间在一边,已分配空间在另外一边。

  • 空闲列表:在虚拟机中维护一个列表,用来记录堆中哪一块内存是空闲可用的,在为新生对象分配内存时,从列表中寻找一块合适大小的可用内存块,分配完成后更新空闲列表,这种方式下堆内存的空闲空间与分配空间可以交错存在。

从上面来看,选择采用指针碰撞还是空闲列表法分配内存,主要由Java堆内存是否规整决定的,而Java堆内存是否规整又取决于所采用的垃圾收集算法,这就涉及到垃圾回收机制(可见知识都是相通的,程序员就是活到老学到死啊!),GC中压缩或者整理的动作等的能力等。

同时,由于创建对象的动作是十分频繁的,多线程可能存在多个线程同时申请为对象分配内存空间,这个时候如果不采取一定的同步机制,就有可能导致一个线程还未来得及修改指针,另一个线程就使用了原来的指针分配内存空间。

这里采用两种方式来处理并发情况下线程不安全的情况:

1)CAS配上失败重试(CAS配上重试)

对分配内存空间的动作进行同步处理——实际上虚拟机时采用 CAS 配上失败重试的方式保证跟新操作的原子性;

2)TLAB方式。

把内存分配的动作按照线程划分在不同的空间之中进行,即每个线程在 Java 堆中预先分配一小块内存,称为本地线程分配缓存。

第一种方式很好理解,多个线程使用CAS的方式更新指针,多线程下只有一个线程可以更新完成,其他线程通过不断重试完成内存指针的重新移动。

第二种方式是每个线程提前分配一块内存空间,这个内存空间就是线程本地缓冲TLAB,这样线程每次要分配内存时,先去TLAB中获取,当TLAB中内存空间不足的时候才采用同步机制继续申请一块TLAB空间,这样就降低了同步锁的申请次数。

具体到这个阶段,是在堆内存中为DemoClass对象,也就是dc对象实例开辟了一块内存空间。

5、初始化零值

在为对象分配内存完成之后,虚拟机会将分配到的这块内存初始化为零值,这样也就使得Java中的对象的实例变量可以在不赋初值的情况下使用,因为代码所访问的就是虚拟机为这块内存分配的零值。

具体到这里,就是Java虚拟机将上面分配的内存空间初始化为零值,这一步使得现在DemoClass中的d与e均被赋值为0。

6、设置对象头

对象头就像我们人的身份证一样,存放了一些标识对象的数据,也就是对象的一些元数据,我们首先看一下对象的构成。

在初始化了零值之后,怎么知道对象是哪个类的实例,就需要设置指向方法区中类型信息的指针,对象Mark Word中相关信息的设置,就在这个阶段完成。

7、实例对象初始化

这一步虚拟机将调用实例构造器方法(), 根据我们程序员的意愿初始化对象,在这一步会调用构造函数,完成实例对象的初始化。

具体到这里就是DemoClass的d被赋值为4,e被赋值为5。

8、创建引用、入栈

执行到这一步,堆内存中已经存在被创建完成的对象,但是我们知道,在Java中使用对象是通过虚拟机栈中的引用来获取对象属性,调用对象的方法,因此这一步将创建对象的引用,并压入虚拟机栈中,最终返回引用供我们使用。

在这里就是将对象的引入入栈,并返回赋值给dc,至此,一个对象被创建完成。

9、完整流程

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

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

相关文章

Dom获取属性操作

目录 1. 基本认知 1.1 目的和内容 1.2 什么是DOM 1.3 DOM对象 1.4 DOM树 2. 获取DOM元素对象 2.1 选择匹配到的第一个元素 2.2 选择匹配到的多个元素 2.3 其他获取DOM元素方法 3. 操作元素内容 3.1 元素对象.innerText 属性 3.2 元素对象.innerHTML 属性 4. 操作元…

缓存分享(1)——Guava Cache原理及最佳实践

Guava Cache原理及最佳实践 1. Guava Cache是什么1.1 简介1.2 核心功能1.3 适用场景 2. Guava Cache的使用2.1 创建LoadingCache缓存2.2 创建CallableCache缓存 缓存的种类有很多,需要根据不同的应用场景来选择不同的cache,比如分布式缓存如redis、memca…

设计模式之装饰者模式DecoratorPattern(四)

一、概述 装饰者模式(Decorator Pattern)是一种用于动态地给一个对象添加一些额外的职责的设计模式。就增加功能来说,装饰者模式相比生成子类更为灵活。装饰者模式是一种对象结构型模式。 装饰者模式可以在不改变一个对象本身功能的基础上增…

linux dma的使用

设备树配置 驱动代码 static void bcm2835_dma_init(struct spi_master *master, struct device *dev) { struct dma_slave_config slave_config; const __be32 *addr; dma_addr_t dma_reg_base; int ret; /* base address in dma-space */ addr of_get_address(master->de…

基于 React 的图形验证码插件

react-captcha-code NPM 地址 : react-captcha-code - npm npm install react-captcha-code --save 如下我自己的封装: import Captcha from "react-captcha-code";type CaptchaType {captchaChange: (captchaInfo: string) > void;code…

目前全球各类遥感卫星详细介绍

一、高分一号 高分一号(GF-1)是中国高分辨率对地观测系统重大专项(简称高分专项)的第一颗卫星。“高分专项”于2010年5月全面启动,计划到2020年建成中国自主的陆地、大气和海洋观测系统。 高分一号(GF-1&…

React的useEffect

概念:useEffect是一个React Hook函数,组件渲染之后执行的函数 参数1是一个函数,可以把它叫做副作用函数,在函数内部可以放置要执行的操作参数2是一个数组(可选参),在数组里放置依赖项&#x…

Linux系统中搭建Mosquitto MQTT服务并实现远程访问本地消息代理进行通信

文章目录 1. Linux 搭建 Mosquitto2. Linux 安装Cpolar3. 创建MQTT服务公网连接地址4. 客户端远程连接MQTT服务5. 代码调用MQTT服务6. 固定连接TCP公网地址7. 固定地址连接测试 今天和大家分享一下如何在Linux系统中搭建Mosquitto MQTT协议消息服务端,并结合Cpolar内网穿透工具…

使用 Python 和 OpenCV 进行实时目标检测的详解

使用到的模型文件我已经上传了,但是不知道能否通过审核,无法通过审核的话,就只能 靠大家自己发挥实力了,^_^ 目录 简介 代码介绍 代码拆解讲解 1.首先,让我们导入需要用到的库: 2.然后,设…

【C语言】指针篇- 深度解析Sizeof和Strlen:热门面试题探究(5/5)

🌈个人主页:是店小二呀 🌈C语言笔记专栏:C语言笔记 🌈C笔记专栏: C笔记 🌈喜欢的诗句:无人扶我青云志 我自踏雪至山巅 文章目录 一、简单介绍Sizeof和Strlen1.1 Sizeof1.2 Strlen函数1.3 Sie…

快速建站介绍

随着在线业务和电子商务的规模不断扩大,初创公司、个人网站和小型企业都需要快速地搭建自己的网站,以便更好地展示自己、推广产品和服务,并实现在线交易。快速建站已成为在线业务发展的一种主流方式,因为它能够快速地创建一个响应…

uniapp 自定义 App启动图

由于uniapp默认的启动界面太过普通 所以需要自定义个启动图 普通的图片不可以过不了苹果的审核 所以使用storyboard启动图 生成 storyboard 的网站:初雪云-提供一站式App上传发布解决方案

Java学习第02天-类型转换、运算符

目录 类型转换 自动类型转换 表达式的自动类型转换 强制类型转换 运算符 基本运算符 案例解答 连接字符串 自增自减运算符 面试习题 赋值运算符 比较运算符 逻辑运算符 基本逻辑运算符 短路逻辑运算符 三元运算符 基础知识 拓展案例 运算符优先级 读取用户…

UNeXt: a Low-Dose CT denoising UNet model with the modified ConvNeXt block

UNeXt:采用改进的ConvNeXt块的低剂量CT去噪UNet模型 论文链接:https://ieeexplore.ieee.org/document/10095645 项目链接:没找到 Abstract 近几十年来,临床医生广泛使用计算机断层扫描(CT)进行医学诊断。医疗辐射有潜在危险&am…

77、贪心-买卖股票的最佳时机

思路 具体会导致全局最优,这里就可以使用贪心算法。方式如下: 遍历每一位元素找出当前元素最佳卖出是收益是多少。然后依次获取最大值,就是全局最大值。 这里可以做一个辅助数组:右侧最大数组,求右侧最大数组就要从…

ADS1.2中的代码debug的时候不出来代码的解决办法

我总觉得ADS1.2这个软件挺奇怪的,一阵一阵的,我遇到了很多奇怪的问题,这里记录一下吧。 1、新建文件的时候,没法选择这个add to project 解决办法:如果没有已存在的.mcp文件,就先新建project,然…

项目运行到手机端

运行到真机 手机和点到连在同一个wifi网络下面点击hbuiler上面的预览得到一个,network的网址这个时候去在手机访问,那么就可以访问网页了 跨域处理 这个时候可能会访问存在跨域问题 将uniapp的H5版本运行到真机进行调试,主要涉及到跨域问题…

java-Spring-mvc-(请求和响应)

目录 📌HTTP协议 超文本传输协议 请求 Request 响应 Response 🎨请求方法 GET请求 POST请求 📌HTTP协议 超文本传输协议 HTTP协议是浏览器与服务器通讯的应用层协议,规定了浏览器与服务器之间的交互规则以及交互数据的格式…

【机器学习】HQ-Edit引领图像编辑新潮流

科技新纪元:HQ-Edit引领图像编辑新潮流 一、HQ-Edit的诞生:一场技术的革命二、技术实现与优势:强大的编辑能力和精准的匹配三、应用前景与实例展示:InstructPix2Pix的突破 在数字化时代,图像编辑技术正以前所未有的速度…

M3D-NCA: Robust 3D Segmentation with Built-In Quality Control论文速读

文章目录 M3D-NCA: Robust 3D Segmentation with Built-In Quality Control摘要方法实验结果 M3D-NCA: Robust 3D Segmentation with Built-In Quality Control 摘要 这是关于医学图像分割的一篇论文的结构化总结: 背景和挑战: 医学图像分割依赖于大型…