前言

当Java程序中使用new 创建对象时,类加载机制会检查这个类是否被加载过(类加载机制可以看一下另一篇文章《Android中ClassLoader双亲委托机制》),如果没被加载过会执行对象的创建过程,流程图如下

java循环给对象重新赋值_java

类加载

一个Java程序,会通过javac编译成class文件,然后通过虚拟机加载到方法区

检查加载

检查 new 这个指令的参数是否能在常量池中定位到一个类的 符号引用,并检查类是否已经被加载、解析和初始化过。

分配内存

JVM为对象分配内存。相当于把一块确定大小的内存从堆中划分出来。分配内存过程中会涉及到分配策略问题并发安全问题

分配策略

指针碰撞

如果堆中的内存时连续、规整的,所有被占用的内存在一起,闲置的内存与被占用内存之间放着一个指针作为分界点的指示器,如果new一个新对象的时候,指针移动对象大小的距离(空间大小),这种分配策略就是指针碰撞。

采用这种对象分配策略的前提堆内存是否连续或规整,而对内存规整的前提是其垃圾回收器带有压缩整理的功能(Serial、ParNew),这种分配策略的优点是简单高效

如下图,橙色是已被占用的内存,空白的是闲置内存,假设对象占用一个方格的大小,堆上分配这个对象的内存后,指针就向后移动一个方格大小的距离。

java循环给对象重新赋值_初始化_02

空闲列表

针对堆中的内存不是规整的,虚拟机维护了一张表,表中记录着堆内存中那一块是可用的,当需要分配一个对象到堆内存的时候,会在表中查找一个足够大的空间分配给这个对象实例,并更新表上的记录。这种分配对象的方法为空闲列表。

如果垃圾回收器是带有压缩整理功能的,比如Serial、ParNew等采用指针碰撞的分配方式。
如果是CMS,理论上只能次啊用比较复杂的空闲列表方式。

移动端使用的Dalvik和ART虚拟机中采用的是CMS(Concurrent mark sweep) 垃圾回收器(Android中CMS算法(包括后来ART中的CMS)与hotspot JVM里面的CMS是不一样的。Android中的CMS是没有copy部分的。 所以是non-moving GC)

并发安全问题

JVM中创建对象是非常频繁的,当在并发的情况下,给一个对象分配内存的时候,指针还没来得及移动,又来一个对象也用这个指针移动对象大小的距离。这时候就会出现并发安全问题。有两种方式来解决这个问题

CAS机制

对分配内存空间的动作进行同步处理,虚拟机采用CAS机制再加上失败重试的方式保证更新操作的原子性。

本地线程分配缓冲

本地线程分配缓冲(Thread Local Allocation Buffer)又称TLAB,是指JVM在线程初始化时,同时也申请一块指定大小的内存,只给当前线程使用,如果对象需要分配内存,就从自己所在线程中分配的那小块内存中分配。

采用这种机制就不会有竞争的情况,它让每个线程都使用自己专属的分配指针来分配内存空间(但是对象的内存空间还是给线程共享的),减少了同步开销,大大提高分配的效率。

当Buffer(小块内存)容量不够的时候再从Eden区域申请一块继续使用。

允许在Eden区中使用线程本地分配块(TLAB)默认情况下启用此选项要禁用TLAB,请指定-XX:-UseTLAB。

内存空间初始化

内存分配完成后,虚拟机需要将分配到的内存空间都初始化为初始值(如int 的初始值 0),这一步保证了对象的实例字段在Java代码中可以不赋值就可以直接使用。

设置

将对象是属于哪个类的实例、如何才能拿到类的元数据(Class在Hotspot 中表示类元数据)、对象的哈希码和对象的分代信息等放入对象的文件头中。

对象初始化

经过设置后,在虚拟机的角度来说,一个对象已经产生了,但是从我们开发过程中来看,对象创建才刚刚开始,所有的字段还是默认的初始值,这时还需要通过构造方法来进行初始化。

最终,经过类加载-检查加载-分配内存-内存空间初始化-设置-对象初始化,程序中一个真正对象就正式创建出来了。