1.Java内存区域

1.对象的创建过程

     (1)当遇到一个new指令的时候,首先去检查这个指令参数是否能在常量池中定位到一个类的符号引用,并检查这个符号引用代表的类是否已经被加载、解析和初始化,若没有,则先执行类的加载过程.

     (2)在类加载检查通过后,虚拟机为新生对象在堆上分配内存。对象所需内存大小在类加载完后便可确定。

那分配方案有两种:1.在java堆中内存是绝对规整的情况下,就把指针向空闲内存中移动与对象大小相等的距离,称为“指针碰撞”法,常用于Serial、ParNew等垃圾收集器;2.在已使用内存和空闲内存相互交错的情况下,采用维护一个列表,记录哪些内存块是可用的,找到一个合适大小的内存块并更新列表记录即可,这种方式称为“空闲列表”法,常用于CMS等垃圾收集器。

除此之外,还需考虑多线程问题,如果给对象A分配内存,指针还没来得及修改,对象B又使用了原来的指针来分配内存的情况。如何解决?

两种方案:1.对分配内存空间的动作进行同步处理-实际上虚拟机采用CAS配上失败重试的方式保证操作的原子性;

                 2.把内存分配动作按照线程划分在不同的空间中进行,即每个线程在Java堆中预先分配一小块内存,称为本地线程分配缓冲(TLAB).哪个线程要分配内存就在哪个TLAB上分配。当TLAB用完并重新分配新的TLAB时才进行同步。

     (3)内存分配完成后,虚拟机需要将分配的内存空间全都初始化为零值。这一步保证了对象的实例字段在java代码中可以不赋初始值就直接使用。

     (4)虚拟机对对象进行必要的设置,例如这个对象是哪个类的实例、如何找到类的元数据信息、对象的哈希吗、对象的GC分代年龄等信息。这些信息均存放在对象的对象头中。

上面工作完成后,从虚拟机视角来看,一个新的对象已经产生了,但是从java程序来看,对象的创建才刚刚开始,<init>方法还没执行,所有字段为零。执行new指令后接着执行<init>方法,把对象按照程序员的意愿进行初始化。

2.对象的内存布局

 对象在内存中存储的布局可以分为:对象头、实例数据和对齐填充。

对象头包括两部分:1.存储对象运行时数据,包括哈希码、GC分代年龄、锁状态标志、线程持有锁等,官方称为“Marke Word”2. 类型指针。即对象指向它的类元数据指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。

实例数据是对象真正存储的有效信息,即代码中定义的各种类型的字段内容。

对齐填充不是必然存在,仅起到占位符的作用。

3.对象的访问定位

java程序通过栈上的reference数据来操作堆上的具体对象。那如何访问呢?主流的方式有两种:使用句柄和直接指针两种

1.使用句柄访问,那么在java堆中划分出来一块内存作为句柄池,reference中存储的就是对象的句柄地址,而句柄中包含了对象实例数据与类型数据各自的具体地址信息。好处:reference中存放的是稳定的句柄地址,在对象被移动时只会改变句柄中的示例数据指针,而reference本身不需要修改。如下图所示:



2.直接指针访问:reference中存储的直接就是对象地址,好处:速度更快,sun公司的hotspot使用这种方式





猜你喜欢

转载自blog.csdn.net/oYeYuanXinZhiZhu1/article/details/81045553