前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >java之new一个对象是怎样的过程?

java之new一个对象是怎样的过程?

作者头像
码农王同学
发布2019-10-30 10:28:54
2.1K0
发布2019-10-30 10:28:54
举报
文章被收录于专栏:后端Coder后端Coder

作为一名java码农,在语言层面上,如何创建一个对象,想必大家的意识就是new关键字的使用了,在虚拟机中,对象的创建又是一个怎样的过程呢?

虚拟机遇到一条new指令时,首先将去检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已被加载,解析和初始化过,若没有,必须先执行相应的类加载过程。

类加载的过程在这篇文章中先不进行说明,简单地说下,类加载的过程就是将我们的java源代码编译后的class字节码文件加载进内存的过程,先说到这吧,后面会单独写一篇文章,大家一起交流交流。

在通过类加载检查通过后,接下来虚拟机将为新生对象分配内存。对象所需内存大小在类加载完成后可确定,为对象分配空间的任务相当于把一块确定大小的内存从Java堆中划分出来,一般存在两种方式,其一是指针碰撞,其二是空闲列表,具体选择哪种分配方式是根据Java堆是否规整来决定的。

Java堆的规整同时又取决于所采用的垃圾收集器是否带有压缩整理的功能所决定的,我们都知道垃圾收集器存在标记-清除,标记-整理等,因此Java堆是否规整就看你使用的是什么GC算法了。为了确保内存分配时的线程安全,通常使用两种解决方法:一种是对分配内存空间的动作进行同步处理--实际上虚拟机采用CAS配上失败重试的方式保证更新操作的原子性;

另外一种是把内存分配的动作线程划分在不同的空间之中,即每个线程在Java堆中预先分配一小块内存,称之为TLAB,是本地线程分配缓冲的简写形式,那个线程要分配内存,就在哪个线程的TLAB上分配,只有TLAB用完并分配新的TLAB时,才需要同步锁定操作。

内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值,如果使用TLAB,这一工作可以提前至TLAB分配时进行。这一步操作保证了对象的实例字段在java代码中可以不赋初始值就直接使用,程序能访问到这些字段的数据类型所对应的零值。

接下来的动作就是虚拟机要对对象进行必要的设置了,一般一个对象是属于某个类的实例中的一个,如何才能找到类的元数据信息,对象的哈希码就是hashCode了,对象的GC分代年龄等信息,这些信息是存在对象的对象头之中,当上面的工作完成了之后,从虚拟机的角度来看,一个新对象已经产生,但是从Java程序的角度来看,对象的创建才刚刚开始,一般来说,执行new执行之后会接着执行<init>方法,把对象按照程序设计人员的思维进行初始化,这样一个新对象才算完全产生出来。

在HotSpot虚拟机中,对象在内存中的存储布局可以分为三块区域:对象头,实例数据和对齐填充。

HotSpot虚拟机的对象头包括两部分信息,第一部分用于存储对象自身的运行时数据,如GC分代年龄,哈希码即hashCode,锁状态标识,线程持有的锁,偏向线程ID等信息,在这里我采用的都是文字描述,关于对象头信息,有一张图描述的很清楚,自行查阅吧,Mark Word被设计成一个非固定的数据结构,原因在于在极小的内存空间存储尽量多的信息,它会根据对象的状态复用自己的存储空间。

好了,我们继续吧,第二部分是类型指针,并不是所有的虚拟机都有,由于我们在说hotSpot,类型指针即对象指向它的类元数据的指针,虚拟机通过这个指针来确定对象是哪个类的实例(句柄和直接指针),此外,如果对象是一个Java数组,那么在对象头中还必须有一块用于记录数组长度的数据,因为虚拟机可以通过普通Java对象的元数据信息确定java对象的大小,但是从数组的元数组中却无法确定数组的大小,这块内容稍显晦涩难懂,大家有个印象就可以了,想深入了解的查阅对应的资料信息吧。

实例数据部分才是存储对象实例的有效信息,也是在程序代码中定义的各种类型的字段内容,无论是从父类继承下来的,还是在子类中定义的,都需要记录下来,这部分的存储顺序会受到虚拟机分配策略参数和字段在Java源码中定义的顺序的影响。HotSpot虚拟机默认的分配策略是相同宽度的字段总是被分配到一起,满足这个前提条件下,在父类中定义的变量会出现在子类之前。

对齐填充部分不是必然存在的,它仅仅起着占位符的作用,由于HotSpot虚拟机的自动内存管理机制要求对象的起始地址必须是8字节的整倍数,因此,当对象的实例数据部分没有对齐时,这个时候就需要对齐填充来补全了。

ok,这篇文章快要结束了,下面我们在说下一些内容,我们在程序中创建对象是为了使用对象,Java程序需要通过栈上的引用来操作堆上的具体对象,目前主流的访问方式有使用句柄和直接指针两种,如果使用句柄访问的话,那么Java堆中将会划分一块内存来作为句柄池,reference中存储的就是对象的句柄池地址,而句柄中包含了对象实例数据与类型数据各自的具体地址信息。如果使用直接指针访问,那么Java堆对象的布局中就必须考虑如何防止访问类型数据的相关信息了,而reference中存储的直接就是对象地址。

两种访问对象的方式其实各自有自己的优势,使用句柄来访问的最大好处就是reference中存储的是稳定的句柄地址,使用直接指针访问的方式优势就是速度更快,因为它节省了一次指针定位所带来的时间开销
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-10-25,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 码农王同学 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
对象存储
对象存储(Cloud Object Storage,COS)是由腾讯云推出的无目录层次结构、无数据格式限制,可容纳海量数据且支持 HTTP/HTTPS 协议访问的分布式存储服务。腾讯云 COS 的存储桶空间无容量上限,无需分区管理,适用于 CDN 数据分发、数据万象处理或大数据计算与分析的数据湖等多种场景。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档