对象的创建

    1. 当虚拟机遇到new指令的时候,首先去检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已经被加载、解析和初始化过。如果没有,那先必须执行相应的类加载过程。
    1. 类加载检查通过后,就为新生对象分配内存。对象所需要的内存大小在类加载完成后便可以完全确定。

    如果堆中的内存是完全规整的,就会使用“指针碰撞”的分配方式分类内存。
    指针碰撞分配内存的过程是: 被使用的内存放在一边,空闲的内存放在一边,中间有一个指针作为分界点,分配内存就仅仅是把那个指针向空闲空间那边挪动一段与对象大小相等的距离。
    如果堆中的使用的内存和空闲的内存互相交错,则使用“空闲列表”分配方式。
    空闲列表分配方式:虚拟机维护一个列表,记录哪些内存可用,在分配的时候在内存中找到一块足够大的空间划分给对象实例,并更新列表上的记、录。
    选择哪种分配方式由堆是否规整决定,堆是否规整由垃圾处理器是否带有压缩整理功能决定。

    内存分配是一种高并发行为,可能会导致一些由于并发产生的错误。这时候有2种解决方案:
    1) 虚拟机采用CAS配上失败重试的方式保证更新操作的原子性。
    2) 把内存分配的动作按照线程划分在不同的空间之中进行。为每一个线程分配一小块内存,称为本地线程分配缓冲。可以通过-XX:+/-UseTLAB参数来设定

    1. 内存分配完成后,虚拟机要将分配到的内存空间初始化为零值。如果使用的是TLAB,则在分配TLAB的时候就进行初始化了。这一操作保证了对象实例在Java代码中可以不用赋初始值就可以使用,程序可以访问到这些字段的对应的数据类型的零值。
    1. 对对象进行必要的设置。对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的GC分代年龄等信息。

    这些信息存在对象头(Object Header)当中。

    完成上述操作后,从虚拟机角度来看,一个新的对象已经产生了。但从Java程序角度来看,对象创建才刚刚开始方法还没执行,所有字段都为零。执行new指令后,会紧接执行方法,把对象按照程序员的意愿进行初始化,这样一个对象才真正被创建出来。

    对象的内存布局
    对象头:

    • 运行时数据:哈希码、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等
    • 类型指针:对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例

    实例数据:

    • 程序代码中所定义的各种类型的字段内容。

    对齐填充:不是必然存在的,没有特殊含义,仅仅起到占位符的作用

    对象的访问定位
    对象的访问方式取决于虚拟机实现而定。目前主流有如下两种方式:

    • 使用句柄:Java堆中将会划分出一块内存来作为句柄池。reference中存储的就是对象的句柄地址,而句柄中包含了对象实例数据与类型数据各自的具体信息。
    • 直接指针:Java堆对象的布局中就必须考虑如何放置访问类型数据的相关信息,而reference中存储的直接就是对象地址。