java虚拟机HotSpot虚拟机在java堆中对象内存分配、布局、访问

遇到new指令时

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

   

     2.如果1的情况没有,执行类的加载过程。(待描述)

   

     3.虚拟机为新生对象分配内存。对象所需内存的大小在类加载完成后完全确定,为对象分配空间的任务等同于把一块确定大小的内存从java堆中划分出来。

    在java堆中划分内存依据java是否规整。如果java堆是规整的采用:“指针碰撞”(Bump the pointer)方式分配,在规整的java堆中,所有用过的内存放在一边,空闲的内存放在一边,中间放着一个指针作为分界点的指示器,分配内存时,即将指针向空闲内存方向挪动一段与对象大小相等的距离。如果java堆是不规整的,即已使用的内存与空闲内存相互交错, 此时采用“空闲列表(Free List)”方式分配:虚拟机维护一个列表,纪录哪些内存块是可用的,分配时从列表中找到一块足够大的空间划分给对象实例,并更新列表上的纪录。

     java的规整取决于采用的垃圾收集器是否带有压缩管理功能,在使用Serial、Parnew等带Compact过程的收集器时,系统采用的分配算法是指针碰撞。使用CMS这种基于Mark-Sweep算法的收集器时,通常采用空闲列表方式。   

     注:在分配内存时,需要处理并发情况下,线程安全的问题。大多数情况下,有以下两种处理方式。1:对分配内存空间的动作做同步处理---实际上虚拟机采用CAS配上失败重试的方式保证更新操作的原子性;2:把内存分配的动作按照线程划分在不同的空间之中进行,即保证每个线程在Java堆中预先分配一小块内存,称为:本地线程分配缓存(Thread Local Allocation Buffer,TLAB)。哪个线程分配就在该线程的TLAB上分配,只有TLAB用完并分配新的TLAB时,才需要同步锁定,虚拟机是否使用TLAB,通过-XX:+/-UseTLAB参数来设定。

  

     4.内存分配完成后,虚拟机将分配到的内存空间都初始化为零值(不包括对象头???),如果采用TLAB方式分配内存,这一过程可以提前至TLAB分配时进行,这一步操作保证了对象实例字段在java代码中可以不赋初始值就直接使用,程序能访问到这些字段的数据类型所对应的零值。

     

    5.虚拟机对对象进行必要的设置,例如这个对象是哪个类的实例、如果才能找到类的元数据信息、对象的hash码、对象的GC分代年龄等信息。这些信息存放在对象的对象头(Object Header)中。

   6.从虚拟机的角度来说,上述步骤完成后,一个新的对象已经产生了,但是从java程序的角度来说,对象创建才刚开始,----init()方法还没执行,所有的字段都为零,一般来说,执行完new指令后,接着就会执行init()方法,把对象按照程序员的意愿进行初始化。

3.2 对象的内存布局

      对象在内存中存储的布局分为3块区域:对象头(Object Header)、实例数据(Instance Data)和对齐填充(Padding)。

     1.对象头分为两个部分,第一:用于存储对象自身运行时数据,入hash码(hashCoe)、GC分代年龄、锁状态标志、线程持有的锁等,这部分的数据的长度在32位和64位的虚拟机(未开启压缩指针)中分别为32bit和64bit,官方称为:“Mark Word”。Mark Word被设计为非固定的数据结构以便在极小的空间存储尽量多的信息,它会根据对象的状态复用自己的存储空间。第二:对象头的另一个部分为类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。(并不是所有的对象都保留类型指针,比如数组)

     2.实例数据部分是对象真正存储的有效信息,也是在程序代码中所定义的各种类型的字段内容。这部分的存储顺序会收到配置参数FieldsAllocationStyle和字段在java源码中定义的顺序影响。HotSpot虚拟机默认的分配策略为:longs/doubles、ints、short/chars、bytes/boolean、oops(Ordinary Object pointers),从分配策略中可以看出,相同宽度的字段总是被分配到一起。

     3.对其填充并不是必然存在的,它仅仅起着占位符的作用,由于HotSpot虚拟机自动内存管理系统要求对象起始地址必须是8字节的整数倍,即对象的大小必须是8字节的整数倍。而对象头部分正好是8字节的倍数,因此对象实例数据部分不=没有对齐时,就需要通过对齐填充来补全。

3.3 对象的访问定位

     在java程序中,是通过栈上的reference数据来操作堆上的具体对象,因为reference类型在java虚拟机规范中只规定了一个指向对象的引用,没有规定通过何种方式去定位、访问堆中的对象的具体位置。一般的定位、访问堆中的对象方式为:句柄和直接指针。

     句柄方式,java堆会划分出来一块内存来作为句柄池,reference中存储的就是对象的句柄地址,而句柄中包含了对象实例数据与类型数据各自的具体地址。优势:reference存储的是稳定的句柄地址,再对象移动后,只会改变句柄中的实例数据指针,而reference本身不需要修改。句柄地址在java堆中。

     指针方式:reference数据中存储的直接就是对象地址。优势:节省了指针定位的开销,速度更快。

猜你喜欢

转载自blog.csdn.net/shenchengxinsunwei/article/details/84313078