怎么将对象引用到实例?详细完整的对象实例化过程
对象的实例化过程需要做哪些工作呢?首先Java是一门面向对象的语言,类是对所属于一类的所有对象的抽象,对象的所有结构化信息都定义在了类中,因此对象的创建需要根据类中定义的类型信息,也就是类所对应的class二进制字节流,所以这就涉及到了类的加载与初始化。其次,对象大多存储在堆内存中,这就涉及到内存的分配。除此之外,还有变量的初始化零值,对象头的设置,在栈中创建对象的引用等等,本文我们来一起详细的分析一下对象的完整实例化过程。
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个步骤:加载、验证、准备、解析、初始化、使用、卸载。这里我们看一下前六个阶段。
加载
加载阶段主要干了三件事:
- 根据类的全限定名获取类的二进制字节流。
- 将二进制字节流所代表的静态存储结构转化为方法区中运行时数据结构。
- 在内存中创建一个代表该类的Java.lang.Class对象,作为方法区这个类的各种数据的访问入口。
具体到这里就是首先根据package.DemoClass全限定名定位DemoClass.class二进制文件,然后将该.class文件加载到内存进行解析,将解析之后的结果存储在方法区中,最后在堆内存中创建一个Java.lang.Class的对象,用来访问方法区中加载的这些类信息。
验证
验证阶段完成的任务主要是确保class文件中字节流中包含的信息符合Java虚拟机的规范,虽然说得很简单,但是Java虚拟机进行了很多复杂的验证工作,总的来说可分为四个方面:
准备
准备阶段完成的工作就是为类变量(也就是静态变量)分配内存并赋予初始值,通常情况下是变量所对应的数据类型的零值。但是在这个阶段,被final修饰的变量也就是常量会在这个阶段准确的被赋值。
具体到这里,在这个阶段DemoClass中的a会被赋值为1,b与c均被赋值为0。
解析
这个阶段主要的任务是将常量池中的符号引用替换为直接引用。
初始化
在之前的阶段中,除了加载阶段通过自定义的类加载器可以干预虚拟机的加载过程外,其他的阶段都是虚拟机完全主导,而在初始化阶段才开始根据程序员的意愿执行类的初始化,这个阶段主要完成的工作是执行类构造器方法(),同时虚拟机会保证执行该类的类构造器方法时,其父类的类构造器方法已经被正确的执行,同时,由于类的初始化只进行一次,当多个线程并发的进行初始化时,虚拟机可以确保多个线程只有一个可以完成类的初始化工作, 保证线程安全工作。
具体到DemoClass类,在这个阶段会将b赋值为2,c赋值为3。
4、分配内存当类加载过程完成后,或者类本身之前已经被加载过,下一步就是虚拟机要为新生对象分配内存。对象所需要的内存空间在类加载过程完成后就可以完全确定下来,为对象分配内存空间就相当于从堆内存中划分出一块合适的内存来,分配内存的主要方式有两种:指针碰撞和空闲列表。
从上面来看,选择采用指针碰撞还是空闲列表法分配内存,主要由Java堆内存是否规整决定的,而Java堆内存是否规整又取决于所采用的垃圾收集算法,这就涉及到垃圾回收机制(可见知识都是相通的,程序员就是活到老学到死啊!),GC之后是否具有压缩或者整理的动作等等。
同时,由于创建对象的动作是十分频繁的,多线程可能存在多个线程同时申请为对象分配内存空间,这个时候如果不采取一定的同步机制,就有可能导致一个线程还未来得及修改指针,另一个线程就使用了原来的指针分配内存空间,因此衍生出来了两种解决方案:CAS配上失败重试、TLAB方式。
第一种方式很好理解,多个线程使用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,至此,一个对象被创建完成。
对象实例化的完整流程
根据上面的讨论,我们再来回顾一下对象实例化的整个流程:
作者:厚朴
链接:https://juejin.cn/post/6919694056071118861
来源:掘金
- 11-22学日语平假名和片假名都要学会吗?30分钟教你背出日语平假名片假名
- 11-06全世界最厉害的程序员 全球最厉害的,14位程序员
- 11-14概率统计基础知识笔记:以概率思维对抗不确定性
- 12-29做外贸怎么直接报价?做外贸第一次报价怎么报
- 12-07如何教孩子学跳绳孩子学不会?孩子学跳绳常见一般错误有哪些
- 11-05高性价比大屏手机排行 绝对霸气那些大屏威武手机
- 05-02春夏交替时期为什么经常感冒?早春乍暖还寒易感冒三个方面做好预防
- 12-24金色的草原教学反思,金色的草地,教学反思
- 03-16肯德基会员经营方式:3.3亿会员会员贡献占比62
- 02-24克服烦恼中学生作文 中学生优秀作文用音乐化解烦恼
- 04-23创建定时关机快捷方式,1分钟做一个定时关机倒计时关机
- 03-02一天不喝水不渴什么症状?晨不吐口水午不泄精水
- 02-03厦门地铁6号线有重大进展:厦门地铁6号线最新进展来了
- 02-22复古黑白滤镜调色教程图解,灰调看厌了,奶咖色旧时光滤镜
- 01-25英语单词速记和押韵记忆:英语巧辩词汇8,affecteffect
- 02-04衣服上的强力万能胶怎么去除?如何除去衣服上的强力胶
热门
推荐
- 1银行从业公共基础知识真题及答案373
- 2商务礼仪与谈判考试要点429
- 3简单的小学生春节手抄报资料368
- 4西洋参的功效与禁忌有哪些223
- 5霸气的男生签名档259
- 6男人的长相往往和他的的才华成反比352
- 7古代唯美流行的爱情诗句鉴赏316
- 8大师人物水下摄影206