一个Java 象的创建过程

版权声明:码字不易,转载请标明出处 https://blog.csdn.net/qq_36906627/article/details/81982023

在前面我们已经了解过JVM 的内存划分,我们知道一个Java 对象(文中讨论的限于普通Java 对象)在Java 堆上进行分配,下面我们就以HotSpot 虚拟机为例聊聊一个Java 象的创建。

对象的创建过程

当虚拟机遇到一个new 指令时,会经过以下几个步骤:

1. 判断是否加载类

首先JVM 会去检查这个指令的参数能否在方法区的常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否被加载、解析和初始化过。如果没有,那必须先执行相应的类加载过程。

2. 分配内存空间
2.1 分配内存空间的两种方式

类加载完成后,虚拟机会为新生对象分配内存。对象所需的内存在类加载后便可完全确定。分配方法分为两种:
指针碰撞(Bump the Pointer)的分配:
假设Java 堆的内存是绝对规整的,用过的内存放在一起,没用的内存放在一起(强迫症患者喜欢的那种),中间放着一个指针作为分界点的指示器,对象需要多少内存就把指针向空闲内存空间移动多少。这个方式有点像一个钟:一天的24小时就是虚拟机总内存,用过的内存就是时针走过的时间,没走过的就是剩下的空闲内存。分配一个对象就像吃饭花了一小时,指针就往空闲的地方走1格。
空闲列表(Free List)的分配:
假设Java 堆的内存空间不是规整的,已使用的和未使用的内存空间冗杂在一起,虚拟机就会维护一张列表来记录空间的使用情况,记录那些空间是可用的,那些空间是不可用的。分配对象的时候,就在改表中找到一块足够大的内存空间划分给对象实例。

选择哪种方式分配对象由Java 堆是否规整决定,而Java 堆是否规整由所采用的垃圾收集器(关于垃圾收集器后面在讲)是否带有压缩整理功能决定。
简单列列:
指针碰撞:Serial、ParNew等带Compact过程的收集器
空闲列表:CMS 基于Mark-Sweep 算法的收集器

2.2 分配的并发问题解决

另一个需要考虑的问题就是对象创建在虚拟机是否是频繁的。在并发情况下,哪怕是移动指针这个简单的动作也可能出现线程不安全的问题。可能出现线程A 分配了对象还没来得及移动指针,线程B 又使用原来的指针来分配内存的情况。如何解决?

  • 同步方案
    虚拟机通过对分配内存空间的动作采用CAS 配上失败重试的方式保证内存更新操作的原子性。
  • 非同步方案
    非同步方案就是把内存分配的动作按照线程划分在不同的空间中进行,即每个线程在Java 堆中预先分配一小块内存,称为本线程分配缓冲(Thread Local Allocation Buffer,TLAB)。只有在本地线程分配完了并分配新的TLAB 时,才需要同步锁定。
3. 内存空间初始化零值

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

4. 对象设置

在内存空间初始化后,虚拟机要对对象进行必要的设置,例如这个对象是那个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的GC 分代年龄等信息。

参考资料:
《深入理解Java虚拟机》

猜你喜欢

转载自blog.csdn.net/qq_36906627/article/details/81982023