1、对象的创建
java是面向对象的语言,因此对象的创建无时无刻都存在。在语言层面,使用new关键字即可创建出一个对象。但是在虚拟机中,对象创建的创建过程则是比较复杂的。
首先,虚拟机运到new指令时,会去常量池检查是否存在new指令中包含的参数,比如new People(),则虚拟机首先会去常量池中检查是否有People这个类的符号引用,并且检查这个类是否已经被加载了,如果没有则会执行类加载过程。
在类加载检查过后,接下来为对象分配内存当然是在java堆中分配,并且对象所需要分配的多大内存在类加载过程中就已经确定了。为对象分配内存的方式根据java堆是否规整分为两个方法:1、指针碰撞(Bump the Pointer),2、空闲列表(Free List)。指针碰撞:如果java堆是规整的,即所有用过的内存放在一边,没有用过的内存放在另外一边,并且有一个指针指向分界点,在需要为新生对象分配内存的时候,只需要移动指针画出一块内存分配和新生对象即可;空闲列表:当java堆不是规整的,意思就是使用的内存和空闲内存交错在一起,这时候需要一张列表来记录哪些内存可使用,在需要为新生对象分配内存的时候,在这个列表中寻找一块大小合适的内存分配给它即可。而java堆是否规整和垃圾收集器是否带有压缩整理功能有关。
在为新生对象分配内存的时候,同时还需要考虑线程安全问题。因为在并发的情况下内存分配并不是线程安全的。有两种方案解决这个线程安全问题,1、为分配内存空间的动作进行同步处理;2、为每个线程预先分配一小块内存,称为本地线程分配缓存(Thread Local Allocation Buffer, TLAB),哪个线程需要分配内存,就在哪个线程的TLAB上分配。
内存分配后,虚拟机需要将每个对象分配到的内存初始化为0值(不包括对象头),这也就是为什么实例字段可以不用初始化,直接为0的原因。
接来下,虚拟机对对象进行必要的设置,例如这个对象属于哪个类的实例,如何找到类的元数据信息。对象的哈希吗、对象的GC年代等信息,这些信息都存放在对象头之中。
执行完上面工作之后,所有的字段都为0,接着执行<init>指令,把对象按照程序员的指令进行初始化,这样一个对象就完整的创建出来。
2、对象的内存布局
对象在内存的存储布局中包括:对象头、实例数据、对齐填充
对象头(Header):包含两部分信息。1、存储对象自身的运行时数据,比如哈希码、GC分代年龄等;2、类型指针:通过这个指针确定这个对象属于哪个类。
实例数据(Instance Data):存储代码中定义的各种类型的字段内容。
对齐填充(Padding):这部分信息没有任何意义,仅仅是为了使得对象占的内存大小为8字节的整数倍。
3、对象的访问定位
创建对象是为了使用对象,java程序需要通过栈上的reference数据来操作栈上的具体对象。目前主流的访问对象方式有使用句柄和直接指针两种。1、使用句柄方式:会在java堆中创建一个句柄池,reference指向的这块句柄池,句柄池中包括两个指针,其中一个指针指向对象实例数据,另外一个指针指向对象的类型数据。2、使用指针的方式:reference存储的直接就是对象的地址。
两种方式各有各的特点,如果使用句柄方式的话,最大的好处是reference存放的是稳定的句柄地址,在对象移动时只会改变句柄中的实例数据指针,而reference本身不需要修改。使用指针的方式优势则是速度快,并且省去了一次指针定位的开销。