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

    作为一名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中存储的是稳定的句柄地址,使用直接指针访问的方式优势就是速度更快,因为它节省了一次指针定位所带来的时间开销。

    好了,到这这篇文章就算结束了,喜欢的可以转发,分享下,乐于分享,喜欢这篇文章的可以关注扫描下方二维码进行关注<文章内容摘选来源于相关书籍>。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值