对象的创建
JVM遇到一条字节码new指令时,首先检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并检查这个符号引用所代表的类是否已经被加载、解析和初始化。若没有,先进行响应的类加载过程。
类加载检查通过后,虚拟机为新生对象分配内存(对象所需内存大小在类加载后就能完全确定)。Java堆中有两种内存分配方式:指针碰撞、空闲列表。指针碰撞(Bump The Pointer)就是将Java堆中使用过的内存放一边,没事用的放另一边,中间使用一个指针作为分界点的指示器。空闲列表(Free List)的形式中,Java堆不是规整的,已使用的内存和没事用的内存交错在一起,虚拟机要维护一个列表,记录哪些内存块是可用的,在分配的时候从列表中找到一块足够大的空间划分给实例对象,并更新列表上的记录。选择哪种分配方式跟Java堆规不规整有关,而Java堆是否规整又是由所使用的垃圾收集器是否带有空间压缩能力(Compact)的能力决定的。因此使用Serial、ParNew等使用压缩整理过程的垃圾收集器,分配算法才用的就是指针碰撞;使用CMS这种基于清除算法的垃圾收集器,理论上就只能才用较为复杂的空闲列表。
对象创建修改指针,在并发时不安全。虚拟机用了CAS加上失败重试来保证原子性,或者每个线程在堆中有一小片自己的内存,这片内存用完了才进行同步操作。
内存分配完后,除了对象头都初始化为0值。对象头中的信息也要进行设置,比如markword和指针。
从虚拟机的角度来讲,对象已经产生了。但对于Java程序而言,对象创建才刚开始,构造方法都还没调用呢。
对象的内存布局
对象有对象头、实例数据、对齐填充。
对象头中有MarkWord、类型指针。如果是数组还有数组长度。
实例数据就是对象的有效信息,代码里的各种字段。
对齐填充不一定存在,因为对象是8字节的整数倍,对象头是8字节或16字节,但实例数据不一定是8字节的整数倍,所以有对齐填充。
对象的访问
有两种形式,使用句柄或者直接访问。
使用句柄时,堆中会有一个句柄池,栈帧中的局部变量表中的reference会指向句柄池中的句柄,这个句柄有指向实例数据的指针(指向堆)和指向类型数据的指针(指向方法区)。
直接访问就是reference指向堆中的对象(对象头中有指向类型数据的指针)
直接指针的好处是访问速度快,少了一次指针定位的开销。使用句柄可以不用改reference。
HotSpot主要使用直接访问来找到对象。