既然之前已经讲了运行时数据区的概念,那么现在来梳理下对象的实例化过程与内存分配是很合适的。

创建对象的方式有哪些?

这里就大致说一下,不具体举例了。


  1. 使用new关键字创建对象
  2. 通过class,构造类反射创建
  3. 使用clone方法
  4. 使用反序列化
  5. 使用unsafe类
  6. 第三方类库

创建对象的步骤

这个是重点内容,我们详细来讲一下

1.首先,我们要判断当前类是否经过加载,链接,初始化,如图所示JVM之对象实例化与内存分配_java

也就是检查类的元信息是否存在,如果不存在,那么在双亲委派机制下,需要通过类加载器+包名+类名作为key,去查询对应的.class文件是否存在,如果找到,进行类的加载,生成Class类对象,否则,抛出ClassNotFoundException异常。

2.为对象分配内存,计算占用空间的大小,因为我们创建对象,其实已经可以知道具体占了多少内存了,就像你每天回家,爸妈给你煮的饭也是刚刚好,免得浪费了。

但是我们需要注意,对象放入内存中,需要确定内存是否是规整的,这个由某种垃圾回收算法来决定。

如果内存规整:就使用指针碰撞,说来高大上,其实就是指针指向某块空闲的空间,如果后面足够存放当前对象,那么放入对象,对应指针后移即可。

如果内存不规整:那么虚拟机会维护一个列表,记录哪块内存空间是可用的,因为这时候内存空间因为垃圾回收的缘故,东边一块大,西边一块小,需要从不连续的空间中找到一块能够容纳当前对象的空间,放入即可,这也被叫做空闲列表分配。

3.处理并发问题,这是什么原因呢?因为在多线程的情况下,可能存在线程竞争,也就是两个线程同时抢夺一块内存区域,有两种方式解决

cas:采用cas算法,也就是比较与交换,线程1分配对象,发现当前内存空间还没分配对象,会再次读取,如果为null,放入,如果不为null,则找下一个空位(如有错误请指正)。

tlab分配内存:这是在Eden中一块很小的内存区域,只占1%,可以为每个线程分配独立的缓存区域,避免线程安全问题,分配成功,直接放入tlab,如果分配失败,还会有加锁机制,放入Eden中。

4.初始化分配的空间,这个比较简单,就是为基本数据类型,引用类型赋默认的零值或者null值就可以了。

5.设置对象头,这个后面会细讲,这里先省略了。

6.执行init进行显式的初始化,主要包括三种,显式属性赋值,代码块赋值,构造器赋值。

对象由哪几部分组成?

三部分,对象头,实例数据,对齐填充。

对象头在下面介绍。

实例数据存储真正有效的信息,如程序中定义的属性字段,继承于父类的字段信息。

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

对象头里有什么?

认真听,面试要考哦!

这里大致讲一下,具体可以参考这篇帖子

​Java的对象头和对象组成详解​

首先是Mark word,也被称之为运行时元数据

主要存放对象的哈希值,GC分代年龄,锁状态标志位,线程持有的锁,偏向线程id,偏向时间戳。

第二部分是类型指针,指向类的元数据信息,用来确定对象属于哪个类。

注意,如果对象是数组,那么还需要存放数组的长度信息。

访问对象的两种方式

一般来讲,主要是栈中的引用指向堆空间中的对象就可以访问了,但这里分为两种方式访问。

  1. 句柄访问

如图所示,引用指向句柄池对应的对象实例指针,由实例指针访问对象实例,类型指针访问方法区的对象类型数据,缺点在于要维护一个句柄池,但是如果对象发生了移动,reference的指针不需要变化,只需要懂句柄指针即可。

JVM之对象实例化与内存分配_python_02

  1. 直接指针
    如图所示,这是hotspot采用的方法,不需要维护句柄池,引用直接指向实例数据,但是当对象发生移动时也需要改变指针指向。

JVM之对象实例化与内存分配_python_03