#1. 对象的创建
揭秘对象创建的过程new Object()
在创建对象之前,会检查该对象的类的符号引用
能否在常量池
中定位到,如果能定位到,再继续检查该符号引用
代表的类是否已经被加载
、解析
和初始化
,如果没有定位到,就需要执行相应类加载过程
。
在上面的检查
操作执行完之后,会对新生对象
进行内存分配
,分配的内存大小在类加载
时已经确定(类加载知识应该会讲到咋确定的,我现在也不确定它是咋确定的),内存的具体分配操作有两种
指针碰撞
的假设是,java堆内存是绝对规整
的。将内存区域分为两侧,一侧是使用的内存区域,一侧是空闲(没有被使用)的内存区域,中间放着一个指针作为分界点的指示器
,分配内存空间就是将指针向空闲方向移动一段与新生对象所需内存大小相等
的距离。
注意:
在并发情况下指针的修改并不是线程安全的
,有可能A对象分配内存时,还未修改指针,此时B对象再使用原来的指针
来分配内存
解决方案
CAS
加上失败重试
来保证指针
修改的原子性。线程
划分再堆
中不同的空间
进行,即每个线程在堆
中预先
分配一小块内存,称为本地线程分配缓冲(Thread Local Allocation Buffer)简称 TLAB
,线程创建对象都在自己的本地线程分配缓冲区
中进行分配,只有本地线程分配缓冲区
中的内存用完了后,分配新的缓冲区
时才需要同步锁定
,通过-XX:+/-UseTLAB
参数决定是否使用本地线程分配缓冲区
如果java堆内存不是规整的,被使用和空闲的内存交织
在一起,就无法使用碰撞指针
的方式分配内存,虚拟机就需要维护一个列表
,记录哪块内存是可用的
,分配时从列表中找到足够大的内存空间,并更新列表
上的记录。
选择哪种分配方式由java堆内存是否规整
决定的,而java堆空间是否规整又取决于所采用的垃圾收集器是否具有空间压缩整理
的能力决定的。
因此,当使用Serial
、ParNew
等带有压缩整理
过程的收集器时,系统采用的内存分配方式是指针碰撞
,简单高效。
而当使用CMS
等基于清除
算法的垃圾回收器时,理论上就只能采用较为复杂的空闲列表
来分配内存
内存空间(不包括对象头)
都初始化为零
,如果使用了TLAB
,初始化会在TLAB
时顺便进行,初始化为零
操作保证了对象的实例字段
在java代码中不赋初值
直接使用,使程序访问到这些字段数据类型所对应的零值
。类的实例
,如何找到类的元数据信息
,对象的哈希码
(实际上的哈希码会延后
到真正调用Object::hashCode()
方法时才会计算),对象的GC分代年龄
等信息,将会存放在对象的对象头(Object Header)中
,根据虚拟机当前的运行状态不同,如是否使用偏向锁
等,对象头
会有不同的设置方式。构造函数(init<>方法)
还未执行,所有的字段都是默认的零值
,执行完init<>方法后一个真正可用的对象才被构建出来。