Java对象创建的过程流程分析

 更新时间:2026年01月10日 15:46:30   作者:栗子叶  
本文详细介绍了Java对象创建的整个过程,包括类加载、内存分配、对象头设置、构造器初始化以及内存分配的安全问题,文章还讨论了新生代和老年代的内存分配策略,特别是使用指针碰撞和空闲列表两种方式,感兴趣的朋友跟随小编一起看看吧

一、类加载过程

  1. 类加载检查
    • 当Java虚拟机(JVM)遇到new关键字时,它会先检查要创建的对象类是否已经被加载、链接和初始化。如果尚未加载,JVM会通过类加载器(ClassLoader)加载对应类的.class文件。
  2. 类加载
    • 类加载包括三个子步骤:加载、连接、初始化。
      • 加载:通过权限定类名,读取 class 文件内容为二进制流;二进制流转换成方法区(永久代或元数据区)的运行时 C++类字节码对象 Klass;最后再在堆上生成一个 Class 对象,用来间接获取元数据区的类定义信息,静态对象也保存在 Class 对象中。
      • 连接
        • **验证:**验证文件格式、字节码元数据、语法、符号引用;
        • 准备:为类的静态变量分配内存,并赋予默认初始值(例如0或null),但不会执行任何实际的初始化代码。
        • **解析:**将符号引用替换为直接引用。
      • 初始化:类的静态变量赋予正确的初始值,执行静态块;可能涉及父类初始化。
  3. 内存分配
    • JVM为新创建的对象分配内存空间。对象内存主要包括对象头、实例数据以及可能的对齐填充。
      • 对象头:存储对象自身的元数据如哈希码、锁状态标志、GC分代年龄等信息,以及指向其类元数据(Class对象)的指针。
      • 实例数据:存储类中定义的字段的实际数据。
      • 对齐填充:非必须,为了满足JVM对内存地址对其的要求而填充的额外空间。
  4. 初始化零值
    • 分配内存后,JVM会对对象的所有字段(包括实例变量)分配默认的初始值。
  5. 显式初始化:接下来,如果有在字段声明时直接赋予的初始值(例如int a = 10;),这些值会在构造函数执行前被赋予相应的变量。
  6. **对象头 必要信息设置 **主要是对象头中类的源数据信息,哈希码 ,对象 GC 分代年龄等。
  7. 初始化
    • 构造器初始化:调用类的构造方法(即构造器)进行初始化,执行构造器中的初始化代码,此时才会给实例变量赋予程序员指定的初始值。
    • 如果类中有父类并且还没有被初始化,则先初始化父类。
  8. 对象构造完成
    • 构造方法执行完毕后,对象就完全构造出来了,可以被程序正常使用。

二、对象内存分配方式

内存分配方式根据不同的收集器策略可分为两种,不同的收集器的堆内存规整程度不一致所以有两种分配策略。

指针碰撞 Bump The Pointer

在使用指针碰撞策略时,Java堆被假设为一个连续的内存空间,被分为已用和未用两部分,中间由一个指针作为分界线。当新对象需要内存时,JVM只需将指针向未用空间一侧移动与对象大小相等的距离即可。这种方式适用于使用标记-清除复制算法的垃圾收集器,因为这些算法能够整理出连续的内存空间。

空闲列表 Free List

如果Java堆中的内存不是连续的,或者已被使用的内存和未被使用的内存相互交错(这种情况通常发生在使用标记-整理或分代收集算法的垃圾收集器中),那么空闲列表策略更为适用。在这种情况下,JVM维护一个列表来记录堆中各个小块的可用内存空间。当新对象需要内存时,JVM会从列表中找到一个足够大的空闲块分配给对象,并更新列表。这种方法不需要连续的内存空间,但管理成本相对较高。

三、内存分配的安全问题

堆是线程间共享的一块儿区域,所以多个线程同时创建对象时都涉及对内存空间的申请和分配,那么内存分配就可能出现线程安全问题。依赖以下机制解决多线程安全问题,一种是** CAS 乐观锁机制**,一种是 TLAB 本地线程分配缓冲机制

Thread Local Allocation Buffer (TLAB) 本地线程分配缓冲:每个线程有一个属于自己的预分配内存空间,JVM 首先通过 CAS 为线程申请一块儿预分配内存。这样当某个线程需要申请新的内存空间时首先现在自己的 TLAB 上分配,能减少内存分配冲突。后续 TLAB 内存不足了才会 CAS 申请一块儿新的 LATB 或者直接在 Eden 区直接分配。

Compare-and-Swap (CAS) 在某些JVM实现中,可能会使用CAS操作来实现无锁的线程安全内存分配。CAS是一种硬件级别的原子操作,允许线程在不加锁的情况下比较并交换内存中的值,从而减少锁带来的性能开销,并能有效防止数据竞争。

四、对象如何进入老年代

  1. 新生代:刚创建的对象默认进入新生代的 Eden 区
  2. 进入老年代的条件:四种情况
    1. 熬过了多次 minorGC ,每次 MinorGC 过后对象的年龄就会+1,存活超过 15 次之后就会进入老年代。该次数可通过参数控制 -XX:MaxTenuringThreshold
    2. 动态年龄判断机制:MinorGC 后,如果 Survivor 区中的一批对象大雨了这块 Survivor 区的 50%就会将大于等于这批对象年龄最大值的所有对象直接进入老年代。
      1. 举例 S1 中有 年龄为 1 、2、3、4 的一批对象,其中 234 年龄的加起来超过 S1 的 50%,那么年龄大于等于 4 的对象就直接进入老年代了。
    3. Serial 和 ParNew 收集器,大对象直接进入老年代。例如大字符串和数组 可通过-XX:PertenureSizeThreshold 配置 默认为 1M
    4. MinorGC 后,存活的对象太多无法放入 Sruvivor 区域,会触发空间分配担保机制。将存活的对象移入老年代

分配担保机制(空间担保) Allocation Assurance Mechanism

什么是分配担保机制

在 JVM 中,空间分配担保机制(Space Allocation Guarantee Mechanism)是一种确保在进行垃圾收集时,有足够的空间来处理对象晋升分配的策略。这种机制主要用于新生代垃圾收集(Minor GC)和老年代垃圾收集(Major GC 或 Full GC)之间的协调,以避免出现内存不足的情况。

:::success
用老年代的空间,来担保新生代的垃圾回收可以成功执行并腾出空间。会将新生代存活的对象转移到老年代中。保证新分配内存能直接成功。

  1. young 分区内存不足以创建新对象

:::

内存担保的原理

MinorGC前

  1. 第一步:判断老年代可用内存是否小于新时代对象全部对象大小,如果小于则继续判断,大于则可进行MainorGC
  2. 第二步:老年代小于存活对象,则判断老年代内存是否小于每次MinorGC后进入老年代的平均大小
    1. 小于平均大小,则进行FullGC,再判断是否能保存得下存活对象,放不下则OOM
    2. 大于平均大小,则进行MinorGC

MinorGC后

  1. 如果存活对象小于Survivor区,则直接进入Survivor区
  2. 如果存活对象大于Survivor区,但是小于老年代可用内存,则直接进入老年代
  3. 如果存活对象大于Survivor区,还大于老年代,则尝试进行一次FullGC,FullGC后再次判断,如果放不下存活对象则会OOM

分配担保的配置

  1. **-XX:HandlePromotionFailure:**这个参数控制是否允许晋升失败。如果设置为 true,JVM 会在 Minor GC 时尝试晋升对象,即使老年代空间不足,也会尝试进行一次 Minor GC。如果失败,则触发 Full GC。这个参数在 Java 6 之后已经被默认取消使用。
  2. -XX:PretenureSizeThreshold:这个参数指定大对象直接在老年代分配的大小阈值。超过该阈值的对象直接分配到老年代,避免在新生代频繁复制。
  3. **-XX:MaxTenuringThreshold:**这个参数控制对象在新生代中经历多少次 GC 后晋升到老年代。较高的阈值可以减少对象晋升,但会增加新生代的 GC 频率。
  4. -XX:TargetSurvivorRatio:这个参数控制每次 Minor GC 后目标存活区(Survivor Space)的利用率。JVM 会根据这个参数调整对象晋升的阈值。

五、验证

大对象直接进入老年代

/**
* 测试:大对象直接进入到老年代
* -Xmx60m -Xms60m -XX:NewRatio=2 -XX:SurvivorRatio=8 -XX:+PrintGCDetails
* -XX:PretenureSizeThreshold
*
*/
public class YoungOldArea {
    public static void main(String[] args) {
        byte[] buffer = new byte[1024*1024*20]; //20M
    }
}
-XX:NewRatio=2 新生代与老年代比值
-XX:SurvivorRatio=8 新生代中,Eden与两个Survivor区域比值
-XX:+PrintGCDetails 打印详细GC日志
-XX:PretenureSizeThreshold 对象超过多大直接在老年代分配,默认值为0,不限制

对象内存分代晋升演示

/*
-Xmx600m -Xms600m -XX:+PrintGCDetails
*/
public class HeapInstance {
  public static void main(String[] args) {
    List<Picture> list = new ArrayList<>();
    while (true){
      try {
        Thread.sleep(20);
     } catch (InterruptedException e) {
        e.printStackTrace();
     }
      list.add(new Picture(new Random().nextInt(1024 * 1024)));
   }
 }
}
class Picture{
  private byte[] pixels;
  public Picture(int length){
    this.pixels = new byte[length];
 }
}

通过可视化插件可以看到

  1. Eden区满了之后,就会进行MinorGC,MinorGC时会将Survior放不下的对象存到old老年代
  2. 老年代也满了之后,发生了三次MinorGC,未释放出可用空间后,进行了三次FullGc最后抛出了OOM

到此这篇关于Java对象创建的过程的文章就介绍到这了,更多相关Java对象创建内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 详解Java中的实例初始化块(IIB)

    详解Java中的实例初始化块(IIB)

    在本篇文章里我们针对Java中的实例初始化块(IIB)做想详细分析,有需要的朋友们可以跟着学习参考下。
    2018-10-10
  • Java获取一个类的隐藏属性的几种方法

    Java获取一个类的隐藏属性的几种方法

    这篇文章主要讨论了在Java中如何访问或修改类的私有字段,包括使用公共的getter和setter方法、反射、继承和序列化机制,文章强调了尊重类的封装性,感兴趣的小伙伴跟着小编一起来看看吧
    2025-02-02
  • Java的动态代理模式之Cglib代理详解

    Java的动态代理模式之Cglib代理详解

    这篇文章主要介绍了Java的动态代理模式之Cglib代理详解,Cglib代理也叫作 子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能扩展, 有些书也将Cglib代理归属到动态代理,需要的朋友可以参考下
    2023-11-11
  • IDEA设置多行展示导航栏方式

    IDEA设置多行展示导航栏方式

    在IDEA中开启多行导航栏可以增加工作效率,具体操作步骤包括访问“File”,进入“Settings”,选择“Editor”后修改“EditorTabs”设置中的“Show tabs in one row”选项,取消勾选后保存即可,这使得在打开多个文件时,导航栏可以显示更多标签,便于管理和查看代码
    2024-09-09
  • SpringBoot实现阿里云短信发送的示例代码

    SpringBoot实现阿里云短信发送的示例代码

    这篇文章主要为大家介绍了如何利用SpringBoot实现阿里云短信发送,文中的示例代码讲解详细,对我们学习或工作有一定帮助,需要的可以参考一下
    2022-04-04
  • 使用maven一步一步构建spring mvc项目(图文详解)

    使用maven一步一步构建spring mvc项目(图文详解)

    这篇文章主要介绍了详解使用maven一步一步构建spring mvc项目,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-09-09
  • java8学习教程之函数引用的使用方法

    java8学习教程之函数引用的使用方法

    这篇文章主要给大家介绍了关于java8学习教程之函数引用的使用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习下吧。
    2017-09-09
  • Eclipse 2020-06 汉化包安装步骤详解(附汉化包+安装教程)

    Eclipse 2020-06 汉化包安装步骤详解(附汉化包+安装教程)

    这篇文章主要介绍了Eclipse 2020-06 汉化包安装步骤(附汉化包+安装教程),本文通过图文并茂的形式给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-08-08
  • 解决对接JAVA SM2加密遇到的坑

    解决对接JAVA SM2加密遇到的坑

    这篇文章主要介绍了解决对接JAVA SM2加密遇到的坑,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-10-10
  • 在IDEA中搭建最小可用SpringMVC项目(纯Java配置)

    在IDEA中搭建最小可用SpringMVC项目(纯Java配置)

    这篇文章主要介绍了在IDEA中搭建最小可用SpringMVC项目(纯Java配置),文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-12-12

最新评论