美文网首页
第二章 Java内存区域与内存溢出异常(2)

第二章 Java内存区域与内存溢出异常(2)

作者: cwjbest | 来源:发表于2017-06-02 10:32 被阅读10次

对象的创建,内存布局和访问定位

1. 对象的创建

Java中创建对象经常用的new关键字,那么在虚拟机中,对象(只讨论普通Java对象,不包括数组和Class对象)是怎么被创建的呢?
  虚拟机遇到new关键字时,首先取检查这个指令的参数是否能在常量池中定位到一个类的符号引用,如果没有,说明这个类不存在,直接报错;如果有,还需要检查这个符号引用代表的类是否已经被加载,解析和初始化,如果没有的话需要先对其进行类加载过程。
  接下来就是分配内存了,一般分配有两种方式

1) 指针碰撞

假设Java堆中的内存是绝对规整的,所用使用过的内存在一边,空闲内存在另一边,中间会有一个指针来作为分界,分配内存的时候就是将指针向空闲区移动一段与对象大小相等的距离。

2) 空闲列表

假设Java堆的内存并不是规整的,已使用内存和空闲内存交错排列,这样就没办法进行简单的指针碰撞了,而是要维护一块列表,记录哪些是可用内存,然后分配,更新列表。

具体选择哪一种方式需要根据堆是否规整来决定,而堆是否规整又要根据垃圾回收算法来决定, 如果使用带compact过程的GC时,使用指针碰撞,如果是Mrak-Sweep过程的GC,就使用空闲列表
  创建对象是很频繁的行为,自然也会有并发的问题,可能正在给A分配内存,指针来没来得及修改,对象B又同时使用了同一个指针来分配。解决这个问题有两种方案:一个是加同步锁来保证分配的原子性;二是把内存分配的动作按照线程划分到不同的空间之中进行。
  内存分配完之后,虚拟机会将分配到的内存初始化为零值,以保证实力字段在Java代码中没有赋初值就可以使用了

2. 对象的内存布局

HotSpot虚拟机中,对象的存储布局分为三个部分:对象头,实例数据,对齐填充

1) 对象头

对象头又包括两部分信息,第一部分用于存储对象的运行时数据,比如HashCode,GC分代年龄,所状态标志等等。第二部分是类型指针,即对象指向它类元数据的指针,虚拟机通过这个指针来确定这是哪个类的实例。另外如果是数组对象,对象头里还要有一块数据来记录数组长度

2) 实例数据

这部分是对象真正储存的有效信息,程序代码中定义的所有类型的字段内容都存在这里。这部分的存储顺序会受到虚拟机分配策略和字段定义顺序的影响,一般相同宽度的字段会被放到一起。

3) 对齐填充

这一部分不是必然存在的,因为JVM系统要求对象的起始地址必须是8字节的整数倍,所以当对象的实例数据部分没有对齐时,就用对齐填充来补全

3.对象的访问定位

Java程序需要通过栈上的reference数据来操作具体在堆中的对象,而reference怎么去访问,主要有两种方式:句柄访问和直接指针访问

1) 句柄访问

使用句柄的话,Java堆中会划分一块内存来作为句柄池,reference中存储的就是对象的句柄地址,而句柄中包含了对象的实例数据和类型数据的具体地址

句柄方式

2) 直接指针访问

如果使用这种方式,那reference中存储的直接就是对象的地址

指针访问

两种方式各有优势,使用句柄的好处是reference中存储的额是稳定的句柄地址,在对象被移动时,只改变句柄中的实例数据指针,而reference本身不需要修改;而使用直接指针访问的好处就是速度快,节省了一次指针定位的开销。HotSpot使用的是第二种

相关文章

网友评论

      本文标题:第二章 Java内存区域与内存溢出异常(2)

      本文链接:https://www.haomeiwen.com/subject/xxwyfxtx.html