基础回顾——Java对象创建、内存分配、访问定位概述

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u014738140/article/details/77993768

对象创建与内存分配

Java中创建对象并为其分配内存的过程如下:

  1. Java语言层面执行new关键字

    在Java中,通常使用new关键字来创建一个对象(还没有对象的可以试试^_^),对应在虚拟机中会触发一条new指令。

  2. 虚拟机处理new指令

    当虚拟机触发一条new指令时,首先根据指令参数去常量池中检查是否有相关类的符号引用,进一步检查它是否已经被加载、解析和初始化,如果没有,则需要先加载类。对类加载相关的信息判断完成之后,才开始为对象分配内存。

  3. 为对象分配内存

    根据堆内存的规整状态(堆内存是否规整又由垃圾收集器的算法规则决定)常用如下两种方式:

    1. 指针碰撞

      适用于堆内存绝对规整,没有碎片,用过的内存在一边,未用过的内存在另一边的情况。两段内存之间放置一个边界指针,分配内存时仅需要把指针向空闲内存段移动对象大小的距离即可。

    2. 空闲列表

      适用于堆内存无规则,用过的内存与空闲内存交错的情况。此时虚拟机维护一个记录内存使用状态的列表,分配内存时,寻找一块能容下对象大小的空闲片段,并更新列表记录。

      扫描二维码关注公众号,回复: 3368454 查看本文章

      说明:由于堆内存是线程共享区域,如果多线程同时为对象分配内存,则上述方法必然存在线程安全问题。通常可想到的方案是对内存分配动作进行同步加锁处理,但这种方式必然影响效率;另一种方案为采用TLAB(Thread Local Allocation Buffer)方式,即先为每个线程分配一块内存,然后哪个线程需要为对象分配内存时,就在各个线程的内存区域内部分配,这也可以保证各个线程间互不影响。当TLAB空间用完重新分配时,才需要同步处理。

  4. 将分配的内存空间初始化为零值

    内存分配完成后,虚拟机有个初始化零值的过程,如果使用TLAB方式,此操作也可能提前到TLAB分配时进行。正因为如此,我们平时创建一个对象后,其字段有初始值,如int返回0,boolean返回false。

  5. 虚拟机对对象进行必要设置

    主要是存储一些信息,如此对象属于哪个类、怎么样获取类的元数据、对象的hashcode、对象的GC分代年龄等信息,这些信息存放在对象的对象头中(对象在内存中的存储布局分为3个区域:对象头、实例数据、对齐填充)。

  6. <init>方法执行,完成对象的真正初始化

    上述5步之后,已经创建了一个对象了,但各个字段被置为零值,接下来会执行<init>方法,根据开发者的代码逻辑来初始化对象,这样才算真正完成了对象的创建。

对象访问方式

创建好Java对象之后,就需要使用对象。在虚拟机栈上只定义了一个指向堆内的引用,主流有两种方式去操作对象:

  1. 使用句柄

    此种方案会在堆中创建一个句柄池,栈中的引用存储的是对象句柄的地址,因此栈中的引用指向堆中的句柄池中的某个句柄,而对象句柄则存储了对象实例数据和类型数据的信息(它们的具体地址),示意图(来自《深入理解Java虚拟机》)如下:

    这里写图片描述

  2. 直接指针

    此种方案就是栈中的引用存储的就是堆中对象的地址,直接指向堆中的对象,因此在堆中对象的存储布局中就要考虑如何存放类型数据等信息,示意图(来自《深入理解Java虚拟机》)如下:

    这里写图片描述

    说明:上述两种方式各有优缺点。

    对于句柄方式,由于栈引用存储的是对象句柄地址,因此对象移动时(GC引发)只需要修改对应句柄指向的对象地址即可,栈中的引用不用修改;其缺点就是访问较慢,因为有两次指针定位。

    对于直接指针方式,它的优点就是只有一次指针定位,访问速度快,当然句柄方式的优点也是其缺点。Sun的HotSpot虚拟机是使用直接指针的方式来访问对象的。

参考文献

《深入理解Java虚拟机:JVM高级特性与最佳实践》

猜你喜欢

转载自blog.csdn.net/u014738140/article/details/77993768