当前位置: 首页 > news >正文

Java 创建对象过程 JVM 内存分配并发安全笔记

一、Java 创建对象的完整过程

Java 中通过 new 创建对象,其背后是 JVM 的一系列操作,主要包括:

1.1 类加载检查

当代码中出现 new 指令时,JVM 首先会检查这个类是否已经被加载、解析和初始化过。

  • 若未加载,会触发类加载过程(ClassLoader)。
  • 加载后才可以为该类分配内存。

1.2 分配内存

为新对象分配内存空间(位于堆内存),有两种方式:

分配方式说明
指针碰撞(Bump-the-pointer)如果内存是规整的(使用 Serial、ParNew 等),通过一个指针一直向后分配即可。
空闲列表(Free-list)若堆空间不规整(比如 CMS),需要维护一个可用内存块的列表,找到合适位置进行分配。

1.3 并发安全保障(重点)

在多线程环境下,为对象分配内存可能出现竞争问题(多个线程争抢分配内存)。

JVM 采用了以下几种机制来保证内存分配的并发安全:

1)CAS + 失败重试
  • 指针碰撞方式下,使用 CAS(Compare-And-Swap)操作更新指针;
  • 若失败(因为有其他线程成功更新了),则重试,直到成功。
2)Tlab(Thread Local Allocation Buffer)线程本地分配缓冲区
  • 每个线程会在堆中分配一块小内存作为私有区域,称为 TLAB;
  • 在 TLAB 中分配对象几乎不需要加锁,效率高;
  • TLAB 用完再申请,或者直接走共享堆的 CAS 方式。

-XX:+UseTLAB 参数用于开启 TLAB(默认开启)。


1.4 对象初始化

JVM 完成内存分配后,会进行以下操作:

① 将分配的内存空间清零(不包括对象头)
② 设置对象头(包括哈希码、GC 分代年龄、类型指针等)
③ 执行 <init> 构造方法初始化对象内容

二、JVM 对象分配时的内存结构

对象内存结构包含:

  1. 对象头(Header)
    • Mark Word:存储哈希值、GC 信息、锁状态等;
    • Class Pointer:指向对象的类型元数据(即 class 对象);
  2. 实例数据(Instance Data)
    • 包含类中定义的字段内容;
  3. 对齐填充(Padding)
    • 为了满足内存对齐要求(通常是8字节倍数)。

三、对象创建的 JVM 字节码体现

使用 javap -v 类名.class 查看,可以发现:

new com.example.User
invokespecial #构造方法

其中:

  • new:分配内存并创建引用;
  • dup:复制栈顶引用;
  • invokespecial:调用构造方法初始化对象。

四、对象创建方式总结

方式是否走构造函数是否可控
new✅ 是
Class.newInstance()✅ 是❌(必须有无参构造)
Constructor.newInstance()✅ 是✅(可选构造)
clone()❌ 否(浅拷贝)
反序列化❌ 否(自动恢复)

五、拓展:逃逸分析与栈上分配

配合 JIT 编译器优化,对未逃逸的对象可优化为栈上分配,从而避免堆分配和 GC。

需开启参数:

-XX:+DoEscapeAnalysis
-XX:+EliminateAllocations

六、面试高频问题总结

Q1:Java 中 new 一个对象经历了哪些步骤?

  • 类加载检查;
  • 内存分配(TLAB/CAS);
  • 对象头初始化;
  • 构造函数初始化。

Q2:JVM 如何保证对象分配的并发安全?

  • TLAB 本地分配;
  • CAS 保证共享分配指针的原子性。

Q3:对象是一定分配在堆上吗?

不是。如果开启逃逸分析、对象未逃逸,有可能在栈上分配,提高效率。


参考

《深入理解Java虚拟机》 第三版 - 周志明
https://book.douban.com/subject/34907497/

OpenJDK 官方文档:对象分配与内存布局
https://openjdk.org/

TLAB 配置详解(Oracle 官方)
https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html

Java 内存模型 JSR-133 规范
https://jcp.org/en/jsr/detail?id=133

JVM 源码阅读参考(HotSpot)
https://github.com/openjdk/jdk

http://www.dtcms.com/a/267109.html

相关文章:

  • JVM与JMM
  • Mysql底层专题(四)索引优化实战一
  • DeepSeek与诡秘之主
  • 在SoC数据加解密验证中使用 Python 的 gmssl 库
  • 03_性能优化:让软件呼吸更顺畅
  • 计算机网络(网页显示过程,TCP三次握手,HTTP1.0,1.1,2.0,3.0,JWT cookie)
  • 【网络协议安全】任务12:二层物理和单臂路由及三层vlanif配置方法
  • HarmonyOS:创建ArkTS卡片
  • 从零开始开发纯血鸿蒙应用之探析仓颉语言与ArkTS的差异
  • Vuex身份认证
  • 《C++初阶之类和对象》【经典案例:日期类】
  • Java创建型模式---单例模式
  • WSL命令
  • C#每日学习日记
  • 3dmax烘焙插件3dmax法线贴图烘焙教程glb和gltf元宇宙灯光效果图烘焙烘焙光影贴图支持VR渲染器
  • AWS WebRTC:通过shell分析viewer端日志文件
  • 深入解析享元模式:通过共享技术高效支持大量细粒度对象
  • 【力扣 简单 C】70. 爬楼梯
  • 【鸿蒙】鸿蒙操作系统发展综述
  • 递归与循环
  • 深入理解Reactor调试模式:Hooks.onOperatorDebug() vs ReactorDebugAgent.init()
  • 软件工程经济与伦理
  • 流水线(Jenkins)打包拉取依赖的时候提示无法拉取,需要登录私仓的解决办法
  • HTML知识复习2
  • HuggingFists: 无代码处理复杂PDF
  • 一个简单的网页设计
  • Vue Router 中,params参数的名称必须与路由配置中的动态路径参数名完全一致
  • Go语言基础之接口
  • CppCon 2018 学习:Sane and Safe C++ Class Types
  • FLAN-T5:规模化指令微调的语言模型