当new一个对象时在JVM中会有哪些操作

本文详细介绍了Java虚拟机(JVM)的内存分布,包括程序计数器、本地方法栈、虚拟机栈、方法区和堆的用途。同时深入探讨了类加载机制的三个阶段:加载、连接和初始化,以及类加载过程中的执行顺序。

目录

首先我们了解一下虚拟机(JVM)的内存分布

类加载机制

1、加载

2、连接 

3、初始化


首先我们了解一下虚拟机(JVM)的内存分布

     程序计数器(program count register):当前线程所执行的字节码的行号指示器(每个线程一个计数器,互不影响)

     本地方法栈是jvm调用操作系统方法所使用的栈。

     虚拟机栈是存储局部变量表、操作栈、动态链接、方法出口等信息

     方法区存放了一些常量、静态变量、类信息等,可以理解成class文件在内存中的存放位置。

     虚拟机堆是存储对象和数组,所有线程共享的一块内存区域

类加载机制

JVM把class文件加载到内存,并对数据进行校验、解析和初始化,最终形成JVM可以直接使用的java类型的全过程。

 

1、加载

class文件字节码内容加载到内存中,并将这些静态数据转换成方法区中的运行时数据结构,在中生成一个代表这个类的java.lang.Class对象,作为方法区类数据的访问入口,这个过程需要类加载器参与

存在继承时:
原则:先静后非,先父后子,先块后器
执行顺序如下:
第一步:父类静态成员变量(方法区)
第二步:父类静态代码块(多个按照顺序执行)
注意:根据静态代码块和变量位置顺序初始化变量
第三步:子类静态成员变量(方法区)
第四步:子类静态代码块

2、连接 

  • 验证:确保Class文件中字节流包含的信息是否符合虚拟机的要求,保证不会危害虚拟机自身的安全
  • 准备:正式为类变量(static变量)分配内存并设置类变量初始值的阶段,这些内存都将在方法区中进行分配
  • 解析:虚拟机将常量池中的符号引用替换为直接引用的过程

 方法区:

  1. 常量池(Constant Pool):常量池数据编译期被确定,是Class文件中的一部分。存储了类、方法、接口等中的常量,当然也包括字符串常量。

  2. 字符串池/字符串常量池(String Pool/String Constant Pool):是常量池中的一部分,存储编译期类中产生的字符串类型数据。

  3. 运行时常量池(Runtime Constant Pool):方法区的一部分,所有线程共享。虚拟机加载Class后把常量池中的数据放入到运行时常量池。

3、初始化

  1. 当使用new创建一个对象时,jvm会在堆中分配一个内存存放该对象
  2. 父类成员变量和子类成员变量堆内存创建一片内存,指向值为null,先父类成员变量显式初始化(如果有的话)
  3. 父类代码块(父类成员变量初始化
  4. 父类构造器
  5. 子类成员变量显式初始化(如果有的话)
  6. 子类代码块(子类成员变量初始化
  7. 子类构造器 
<think>我们正在讨论Java中new创建对象的底层机制和具体过程。根据提供的引用资料,我将整理出详细的步骤和内存分配机制。需要注意的是,对象创建过程涉及类加载、内存分配、初始化、设置对象头、执行init方法等步骤。同,内存分配有多种策略,如指针碰撞、空闲列表,并考虑了并发问题。具体步骤如下:1.**类加载检查**:当遇到new指令JVM首先检查这个类是否已经被加载、解析和初始化过。如果没有,则先执行类加载过程。2.**分配内存**:在类加载检查通过后,JVM为新生对象分配内存。分配方式有两种:-**指针碰撞**(BumpthePointer):如果Java堆内存是规整的(即用过的内存和空闲内存分界指针),那么分配内存就是将指针向空闲空间方向移动与对象大小相等的距离。-**空闲列表**(FreeList):如果Java堆内存不规整,则JVM会维护一个记录可用内存块的列表,从列表中找出一块足够大的空间分配给对象,并更新列表记录。-解决并发分配的问题:在并发场景下,可能会出现多个线程同修改指针位置或空闲列表的情况。JVM采用两种方式解决:-**CAS(CompareAndSwap)**:通过原子操作保证更新的原子性。-**TLAB(ThreadLocalAllocationBuffer)**:为每个线程预先分配一小块内存,线程在自己的TLAB上分配内存,避免竞争。当TLAB用完并分配新的TLAB,才需要同步锁定。3.**初始化零值**:将分配到的内存空间(除对象头外)初始化为零值。这样实例变量在未赋初值也能使用默认值(如0、false、null等)。4.**设置对象头**:对象在内存中的布局包括对象头、实例数据和对齐填充。对象头包含两部分:-**MarkWord**:存储对象的哈希码、GC分代年龄、锁状态标志等信息。-**KlassPointer**:指向对象所属类元数据的指针,JVM通过这个指针确定该对象是哪个类的实例。此外,若对象是数组,对象头中还会包含数组长度。5.**执行<init>方法**:执行对象的构造函数(即`<init>`方法)和初始化代码块,按照程序员的意愿为实例变量赋初值并执行其他初始化操作。注意,这是程序员定义的值,与第三步的零值初始化不同。关于内存分配机制,根据对象的不同,JVM会选择不同的分配区域:-**栈上分配**:通过逃逸分析,如果对象没有逃逸出方法,可能直接在栈上分配,这样对象会随方法结束而自动回收,减少GC压力。-**Eden区分配**:大多数情况下,对象在新生代的Eden区分配。当Eden区空间不足,会触发MinorGC。-**大对象直接进入老年代**:大对象(例如很长的字符串或数组)可能会直接分配到老年代,避免在新生代复制操作的性能消耗。可以通过`-XX:PretenureSizeThreshold`参数设置大对象的阈值。-**长期存活的对象进入老年代**:通过分代年龄(存储在对象头的MarkWord中)判断。对象在Eden区经过一次MinorGC后仍然存活,并且能被Survivor区容纳,则被移动到Survivor区,年龄设为1。以后每经历一次MinorGC且存活,年龄加1。当年龄达到阈值(默认为15,可通过`-XX:MaxTenuringThreshold`设置)则进入老年代。-**对象动态年龄判断**:如果在Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,则年龄大于或等于该年龄的对象可以直接进入老年代。-**老年代空间分配担保机制**:在MinorGC之前,JVM会检查老年代最大可用连续空间是否大于新生代所有对象总空间。如果条件成立,则MinorGC是安全的。如果不成立,则JVM会查看是否允许担保失败(HandlePromotionFailure),如果允许,则检查老年代最大可用连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,则尝试MinorGC(尽管有风险);否则,进行一次FullGC。通过以上步骤,我们可以理解new创建对象的具体流程和内存分配机制。接下来,生成相关问题。</think> ### new 创建对象的底层机制和具体流程 在 Java 中,使用 `new` 关键字创建对象一个复杂的过程,涉及类加载、内存分配、初始化和对象头设置等步骤。以下是具体流程: 1. **类加载检查** JVM 检查该类是否已加载、验证、准备、解析和初始化。如果未加载,则执行类加载过程[^3]。这是通过查找常量池中的符号引用完成的。 2. **内存分配**(核心步骤) JVM 根据堆内存状态选择分配策略: - **指针碰撞**(Bump the Pointer) 当堆内存规整(已用/空闲内存分界明确),将分配指针向空闲区域移动对象大小的距离[^2][^4]。 - *并发问题处理*:通过 CAS(Compare-and-Swap)或 TLAB(Thread-Local Allocation Buffer)解决多线程竞争问题[^2][^3]。 - **空闲列表**(Free List) 当堆内存不规整JVM 维护空闲内存列表,分配搜索足够空间并更新列表[^3][^4]。 3. **初始化零值** 分配的内存空间(除对象头外)被初始化为零值(0/false/null),确保未初始化字段有默认值[^3][^4]。 4. **设置对象头**(对象内部结构) 对象头包含两部分: - **Mark Word**:存储哈希码、GC 分代年龄、锁状态标志等[^3]。 - **Klass Pointer**:指向类元数据的指针,用于确定对象类型[^3][^4]。 5. **执行 init 方法** 调用构造函数(`<init>`),执行程序员定义的初始化逻辑(如字段赋值、构造代码块)[^3]。 *注意*:这与步骤 3 的零值初始化不同,是显式赋值。 --- ### 内存分配机制的关键策略 1. **栈上分配** 通过逃逸分析,若对象未逃逸方法作用域,直接在栈帧分配,避免堆内存开销[^3]。 2. **Eden 区分配** 大多数对象在新生代 Eden 区分配。当 Eden 区满触发 Minor GC[^3]。 3. **大对象直入老年代** 对象大小超过 `-XX:PretenureSizeThreshold`(默认约 1MB),直接进入老年代,避免复制开销[^5]。 4. **长期存活对象晋升** 对象年龄(经历 GC 次数)达到 `-XX:MaxTenuringThreshold`(默认 15)进入老年代[^3]。 5. **空间分配担保** Minor GC 前检查老年代剩余空间: - 若空间 > 新生代对象总大小 → 安全执行 Minor GC - 否则触发 Full GC[^3]。 --- ### 示例流程图 ``` 类加载检查 → 内存分配(指针碰撞/空闲列表) → 初始化零值 → 设置对象头 → 执行 init 方法 ↑ 并发控制(CAS/TLAB) ``` ### 相关问题 1. **如何验证对象是否逃逸到堆外?逃逸分析对性能的影响是什么?** 2. **指针碰撞和空闲列表的适用场景有何区别?如何查看当前 JVM 使用的分配策略?** 3. **对象头中的 Mark Word 在不同锁状态(无锁、偏向锁、重量级锁)下如何存储信息?** 4. **设置 `-XX:PretenureSizeThreshold` 参数需要注意哪些兼容性问题?** 5. **Minor GC 和 Full GC 在对象分配过程中的触发条件有什么本质区别?**
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值