《对象创建的秘密:Java 内存布局、逃逸分析与 TLAB 优化详解》
大家好呀!今天我们来聊聊Java世界里那些"看不见摸不着"但又超级重要的东西——对象在内存里是怎么"住"的,以及JVM这个"超级管家"是怎么帮我们优化管理的。放心,我会用最接地气的方式讲解,保证连小学生都能听懂!😉
一、先来认识下Java对象在内存里的"小别墅"🏠
1.1 对象在内存里长啥样?
想象一下,每个Java对象就像一栋小别墅,里面有不同的房间存放不同的东西。一个标准的Java对象在内存中主要包含三部分:
-
对象头(Header) 👔 - 相当于别墅的门牌号
- Mark Word(标记字段):存储对象的哈希码、GC分代年龄、锁状态等
- Klass Pointer(类型指针):指向类元数据的指针
- 数组长度(如果是数组的话)
-
实例数据(Instance Data) 📦 - 别墅里的各个房间
- 存放对象的所有成员变量
- 包括从父类继承下来的变量
-
对齐填充(Padding) ⚖️ - 别墅的院子
- 不是必须的,只是为了补齐字节数
- HotSpot要求对象大小必须是8字节的整数倍
// 举个栗子🌰
public class Person {private String name; // 实例数据private int age; // 实例数据// ... 对象头和填充对程序员是透明的
}
1.2 对象头详细解剖(32位系统为例)
内容 | 位数 | 说明 |
---|---|---|
Mark Word | 25 | 哈希码、GC年龄等 |
偏向锁标识 | 1 | 是否启用偏向锁 |
锁标志位 | 2 | 00-轻量级锁,01-无锁,10-重量级锁 |
Klass Pointer | 32 | 指向类元数据的指针 |
数组长度(可选) | 32 | 如果是数组对象的话 |
🔄 64位系统下:Mark Word变成64位,Klass Pointer可能被压缩成32位(开启压缩指针时)
二、对象是怎么"安家落户"的?——内存分配全流程 🚚
2.1 创建对象的完整旅程
-
类加载检查 🔍
- JVM遇到new指令时,先检查这个类是否已加载
- 如果没有,先执行类加载过程
-
分配内存 💰
- 指针碰撞(Bump the Pointer):内存规整时使用
- 空闲列表(Free List):内存不规整时使用
- 选择哪种方式由GC收集器决定
-
初始化零值 0️⃣
- 为所有实例变量赋默认值(0、false、null等)
-
设置对象头 🎩
- 设置Mark Word和Klass Pointer
-
执行init方法 🏗️
- 按照程序员意愿初始化对象
// 我们写的代码
Person p = new Person("张三", 25);// JVM背后实际执行的操作:
1. 检查Person类是否加载 → 2. 分配内存 → 3. 初始化name=null, age=0
→ 4. 设置对象头 → 5. 调用构造方法赋值
2.2 内存分配策略(对象住哪的问题)
-
栈上分配(逃逸分析优化)🏃♂️
- 小对象且未逃逸出方法时,直接在栈上分配
- 生命周期随方法结束而结束,无需GC
-
TLAB分配(Thread Local Allocation Buffer)🧵
- 每个线程在Eden区有一块私有区域
- 避免多线程竞争,提升分配效率
- 默认占Eden区的1%
-
Eden区分配 🌱
- 大多数新对象在这里出生
- 空间不足时触发Minor GC
-
老年代分配 👴
- 大对象直接进入老年代(-XX:PretenureSizeThreshold)
- 长期存活的对象(默认15次GC后晋升)
三、JVM的"家政服务"——垃圾回收与优化 🧹
3.1 对象生死判定(怎么判断别墅没人住了?)
-
引用计数法(Python用)🔢
- 每个对象有个计数器,被引用时+1,引用失效时-1
- 为0时判定可回收
- 缺点:无法解决循环引用问题
-
可达性分析(Java用)🕵️♂️
- 从GC Roots出发,走不到的对象就是垃圾
- GC Roots包括:
- 虚拟机栈中的引用
- 方法区静态属性引用
- 方法区常量引用
- Native方法引用的对象
3.2 四种引用类型(租房的不同方式)
-
强引用 💪
Object obj = new Object(); // 只要强引用存在,对象绝不会被回收
-
软引用 �
SoftReference softRef = new SoftReference<>(new Object()); // 内存不足时才回收
-
弱引用 🤏
WeakReference weakRef = new WeakReference<>(new Object()); // 下次GC时就会回收
-
虚引用 👻
PhantomReference phantomRef = new PhantomReference<>(new Object(), queue); // 就像没有引用一样,主要用于跟踪对象被回收的状态
3.3 垃圾收集算法(清洁工的工作方式)
-
标记-清除 🗑️
- 先标记所有需要回收的对象,然后统一清除
- 缺点:产生内存碎片
-
复制算法 📋
- 把内存分成两块,每次只用一块
- 垃圾回收时把存活对象复制到另一块
- 适合新生代(Eden区和Survivor区)
-
标记-整理 🧹
- 先标记需要回收的对象
- 然后让所有存活对象向一端移动
- 适合老年代
-
分代收集 🧓👶
- 新生代用复制算法
- 老年代用标记-清除或标记-整理
四、JVM优化三十六计 🎯
4.1 内存分配优化
-
逃逸分析优化 🏃♂️
- 开启参数:-XX:+DoEscapeAnalysis
- 分析对象作用域,未逃逸的对象可以栈上分配
-
标量替换 🔢
- 开启参数:-XX:+EliminateAllocations
- 把对象拆解成基本类型,直接在栈上分配
-
TLAB优化 🧵
- 调整TLAB大小:-XX:TLABSize
- 观察TLAB使用情况:-XX:+PrintTLAB
4.2 GC优化参数
-
新生代优化 👶
-Xmn512m # 设置新生代大小 -XX:SurvivorRatio=8 # Eden和Survivor比例
-
老年代优化 👴
-XX:MaxTenuringThreshold=15 # 晋升老年代的年龄阈值 -XX:PretenureSizeThreshold=1m # 直接分配到老年代的对象大小
-
选择合适的GC收集器 🧹
-XX:+UseSerialGC # 串行收集器(单CPU环境) -XX:+UseParallelGC # 并行收集器(吞吐量优先) -XX:+UseConcMarkSweepGC # CMS收集器(低延迟) -XX:+UseG1GC # G1收集器(大堆内存)
4.3 内存泄漏排查技巧 🔍
-
常用工具 🛠️
- jps:查看Java进程
- jstat:监控GC情况
- jmap:生成堆转储快照
- jstack:查看线程栈
- VisualVM:图形化分析工具
-
实战步骤 🥋
# 1. 找到进程ID jps -l# 2. 监控GC情况(每1秒打印一次) jstat -gcutil 1000# 3. 生成堆转储文件 jmap -dump:format=b,file=heap.hprof # 4. 用MAT或VisualVM分析heap.hprof
五、对象内存布局实战分析 🔬
让我们通过一个实际例子来看看对象在内存中到底占多少空间:
public class Student {private int id; // 4字节private String name; // 引用4字节(开启压缩指针)private boolean sex; // 1字节private double score; // 8字节private Object o; // 引用4字节
}
📏 计算对象大小(64位系统,开启压缩指针):
- 对象头:Mark Word(8) + Klass Pointer(4) = 12字节
- 实例数据:id(4) + name(4) + sex(1) + score(8) + o(4) = 21字节
- 对齐填充:总大小12+21=33 → 需要补到8的倍数 → 40字节
🔍 可以用JOL工具验证:
// 添加依赖:org.openjdk.jol:jol-core
System.out.println(ClassLayout.parseClass(Student.class).toPrintable());
六、常见面试题深度解析 💼
6.1 对象在内存中的布局是怎样的?
(答案参考第一部分,记住对象头+实例数据+对齐填充三部分)
6.2 Java中的四种引用类型有什么区别?
(答案参考3.2节,重点区分强软弱虚四种引用的回收时机)
6.3 如何判断对象是否存活?
(答案参考3.1节,Java用可达性分析而非引用计数)
6.4 JVM内存分配有哪些策略?
(答案参考2.2节,包括栈上分配、TLAB、Eden区、老年代等)
6.5 如何优化GC性能?
(答案参考第四部分,包括选择合适的收集器、调整分代大小等)
七、终极优化建议 🚀
-
不要过度优化 ⚠️
- JVM已经很智能,先让它自动优化
- 只有遇到性能问题时才手动调优
-
理解业务场景 🏢
- 高吞吐场景:选择ParallelGC
- 低延迟场景:选择CMS或G1
- 超大堆场景:选择G1或ZGC
-
监控先行 📊
- 先收集GC日志和分析内存使用情况
- 基于数据做决策,而非猜测
-
循序渐进 🐢
- 每次只调整一个参数
- 观察效果后再决定下一步
-
工具链准备 🧰
# GC日志参数 -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:gc.log# 堆内存溢出时自动转储 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=./oom.hprof
八、总结与展望 🌈
今天我们深入浅出地探讨了Java对象内存模型和JVM优化策略,从对象的内存布局到分配策略,从垃圾回收到性能优化,涵盖了大部分核心知识点。记住:
- 对象在内存中是"三居室"结构(对象头+实例数据+对齐填充)🏠
- JVM是个"智能管家",会自动做很多优化工作🤖
- 优化要基于数据,不要盲目调参📊
- 工具链是你的好帮手,学会使用各种诊断工具🛠️
未来Java内存管理会越来越智能,比如ZGC和Shenandoah等新一代收集器已经可以实现亚毫秒级的停顿时间。但万变不离其宗,理解这些基础原理能让你在面对新技术时更快上手!
推荐阅读文章
-
由 Spring 静态注入引发的一个线上T0级别事故(真的以后得避坑)
-
如何理解 HTTP 是无状态的,以及它与 Cookie 和 Session 之间的联系
-
HTTP、HTTPS、Cookie 和 Session 之间的关系
-
什么是 Cookie?简单介绍与使用方法
-
什么是 Session?如何应用?
-
使用 Spring 框架构建 MVC 应用程序:初学者教程
-
有缺陷的 Java 代码:Java 开发人员最常犯的 10 大错误
-
如何理解应用 Java 多线程与并发编程?
-
把握Java泛型的艺术:协变、逆变与不可变性一网打尽
-
Java Spring 中常用的 @PostConstruct 注解使用总结
-
如何理解线程安全这个概念?
-
理解 Java 桥接方法
-
Spring 整合嵌入式 Tomcat 容器
-
Tomcat 如何加载 SpringMVC 组件
-
“在什么情况下类需要实现 Serializable,什么情况下又不需要(一)?”
-
“避免序列化灾难:掌握实现 Serializable 的真相!(二)”
-
如何自定义一个自己的 Spring Boot Starter 组件(从入门到实践)
-
解密 Redis:如何通过 IO 多路复用征服高并发挑战!
-
线程 vs 虚拟线程:深入理解及区别
-
深度解读 JDK 8、JDK 11、JDK 17 和 JDK 21 的区别
-
10大程序员提升代码优雅度的必杀技,瞬间让你成为团队宠儿!
-
“打破重复代码的魔咒:使用 Function 接口在 Java 8 中实现优雅重构!”
-
Java 中消除 If-else 技巧总结
-
线程池的核心参数配置(仅供参考)
-
【人工智能】聊聊Transformer,深度学习的一股清流(13)
-
Java 枚举的几个常用技巧,你可以试着用用
-
由 Spring 静态注入引发的一个线上T0级别事故(真的以后得避坑)
-
如何理解 HTTP 是无状态的,以及它与 Cookie 和 Session 之间的联系
-
HTTP、HTTPS、Cookie 和 Session 之间的关系
-
使用 Spring 框架构建 MVC 应用程序:初学者教程
-
有缺陷的 Java 代码:Java 开发人员最常犯的 10 大错误
-
Java Spring 中常用的 @PostConstruct 注解使用总结
-
线程 vs 虚拟线程:深入理解及区别
-
深度解读 JDK 8、JDK 11、JDK 17 和 JDK 21 的区别
-
10大程序员提升代码优雅度的必杀技,瞬间让你成为团队宠儿!
-
探索 Lombok 的 @Builder 和 @SuperBuilder:避坑指南(一)
-
为什么用了 @Builder 反而报错?深入理解 Lombok 的“暗坑”与解决方案(二)
相关文章:
《对象创建的秘密:Java 内存布局、逃逸分析与 TLAB 优化详解》
大家好呀!今天我们来聊聊Java世界里那些"看不见摸不着"但又超级重要的东西——对象在内存里是怎么"住"的,以及JVM这个"超级管家"是怎么帮我们优化管理的。放心,我会用最接地气的方式讲解,保证连小学…...

LeetCode 高频 SQL 50 题(基础版) 之 【高级查询和连接】· 下
上部分链接:LeetCode 高频 SQL 50 题(基础版) 之 【高级查询和连接】 上 题目:1164. 指定日期的产品价格 题解: select product_id,10 price from Products group by product_id having min(change_date) > 201…...
Java并发编程:读写锁与普通互斥锁的深度对比
在Java并发编程中,锁是实现线程安全的重要工具。其中,普通互斥锁(如synchronized和ReentrantLock)和读写锁(ReentrantReadWriteLock)是两种常用的同步机制。本文将从多个维度深入分析它们的区别、适用场景及…...
Spring Boot Actuator未授权访问漏洞修复
方案1:在网关的配置文件里增加以下配置 management:endpoints:web:exposure:include: []enabled-by-default: falseendpoint:health:show-details: ALWAYS 方案二:直接在nginx配置拦截actuator相关接口 location /actuator { return 403; …...

机器学习——SVM
1.什么是SVM 支持向量机(support vector machines,SVM)是一种二分类模型,它将实例的特征向量映射为空间中的一些点,SVM 的目的就是想要画出一条线,以 “最好地” 区分这两类点,以至如果以后有了…...

【音视频】FFmpeg 硬件(NVDIA)编码H264
FFmpeg 与x264的关系 ffmpeg软编码是使⽤x264开源项⽬,也就是说ffmpeg软编码H264最终是调⽤了x264开源项⽬,所以我们要先理解ffmpeg和x264的调⽤关系,这⾥我们主要关注x264_init。对于x264的参数都在 ffmpeg\libavcodec \libx264.c x264\co…...

贪心算法应用:超图匹配问题详解
贪心算法应用:超图匹配问题详解 贪心算法在超图匹配问题中有着广泛的应用。下面我将从基础概念到具体实现,全面详细地讲解超图匹配问题及其贪心算法解决方案。 一、超图匹配问题基础 1. 超图基本概念 **超图(Hypergraph)**是普…...
OpenCV CUDA模块结构分析与形状描述符------计算指定阶数的矩(Moments)所需的总数量函数:numMoments
操作系统:ubuntu22.04 OpenCV版本:OpenCV4.9 IDE:Visual Studio Code 编程语言:C11 算法描述 该函数用于计算指定阶数的矩(Moments)所需的总数量。 在图像处理中,矩(moments)是一…...

【Web应用】若依框架:基础篇13 源码阅读-前端代码分析
文章目录 ⭐前言⭐一、课程讲解过程⭐二、自己动手实操⭐总结 标题详情作者JosieBook头衔CSDN博客专家资格、阿里云社区专家博主、软件设计工程师博客内容开源、框架、软件工程、全栈(,NET/Java/Python/C)、数据库、操作系统、大数据、人工智能、工控、网…...

[java八股文][JavaSpring面试篇]SpringCloud
了解SpringCloud吗,说一下他和SpringBoot的区别 Spring Boot是用于构建单个Spring应用的框架,而Spring Cloud则是用于构建分布式系统中的微服务架构的工具,Spring Cloud提供了服务注册与发现、负载均衡、断路器、网关等功能。 两者可以结合…...
深度学习篇---face-recognition的优劣点
face_recognition库是一个基于 Python 的开源人脸识别工具,封装了 dlib 库的深度学习模型,具有易用性高、集成度强的特点。以下从技术实现、应用场景等维度分析其优劣势: 一、核心优势 1. 极简 API 设计,开发效率极高 代码量少:几行代码即可实现人脸检测、特征提取和比对…...

基于分布式状态机的集装箱智能道口软件架构方法
集装箱码头对进出场道口的通过能力始终是要求最高的,衡量道口的直接指标为道口通行效率,道口通行效率直接体现了集装箱码头的作业效率以及对外服务水平,进而直接影响到码头的综合能力。所以,码头普遍使用智能道口实现24小时无人值…...
Oracle的Hint
racle的Hint是用来提示Oracle的优化器,用来选择用户期望的执行计划。在许多情况下,Oracle默认的执行方式并不总是最优的,只不过由于平时操作的数据量比较小,所以,好的执行计划与差的执行计划所消耗的时间差异不大&…...
手动事务的使用
使用原因: 公司需要写一个定时任务,涉及增改查操作, 定时将前端页面配置的字典数据(标签数据)同步到数据库特定的表(标签表) 查询字典表数据 字典有,数据库表没有新增 都有,判断名称,名称不同修…...

Vue 树状结构控件
1、效果图如下所示: 2、网络请求的数据结构如下: 3、新建插件文件:menu-tree.vue,插件代码如下: <template><div class"root"><div class"parent" click"onParentClick(pare…...
Spring Boot的启动流程,以及各个扩展点的执行顺序
目录 1. 初始化阶段执行顺序 1.1 Bean的构造方法(构造函数) 1.2 PostConstruct 注解方法 1.3 InitializingBean 的 afterPropertiesSet() 1.4 Bean(initMethod "自定义方法") 2. 上下文就绪后的扩展点 2.1 ApplicationContext 事件监听…...

【LUT技术专题】图像自适应3DLUT代码讲解
本文是对图像自适应3DLUT技术的代码解读,原文解读请看图像自适应3DLUT文章讲解 1、原文概要 结合3D LUT和CNN,使用成对和非成对的数据集进行训练,训练后能够完成自动的图像增强,同时还可以做到极低的资源消耗。下图为整个模型的…...
Apache Doris 在数据仓库中的作用与应用实践
在当今数字化时代,企业数据呈爆炸式增长,数据仓库作为企业数据管理和分析的核心基础设施,其重要性不言而喻。而 Apache Doris,作为一款基于 MPP(Massively Parallel Processing,大规模并行处理)…...

vscode使用“EIDE”和“Cortex-Debug”插件利用st-link插件实现程序烧写以及调试工作
第一步:安装vscode插件“EIDE”EIDE和“Cortex-Debug”。 第二步:配置EIDE 2.1安装“实用工具”: 2.2 EIDE插件配置:根据安装的keil C51 keil MDK IAR的相关路径设置 第三步:配置Cortex-Debug插件 点击settings.jso…...

Spring @Value注解的依赖注入实现原理
Spring Value注解的依赖注入实现原理 一,什么是Value注解的依赖注入二,实现原理三,代码实现1. 定义 Value 注解2. 实现 InstantiationAwareBeanPostProcessor3. 实现 AutowiredAnnotationBeanPostProcessor4. 占位符解析逻辑5. 定义 StringVa…...

三、kafka消费的全流程
五、多线程安全问题 1、多线程安全的定义 使用多线程访问一个资源,这个资源始终都能表现出正确的行为。 不被运行的环境影响、多线程可以交替访问、不需要任何额外的同步和协同。 2、Java实现多线程安全生产者 这里只是模拟多线程环境下使用生产者发送消息&…...
商品模块中的多规格设计:实现方式与电商/ERP系统的架构对比
在商品管理系统中,多规格设计(Multi-Specification Product Design)是一个至关重要但又极具挑战性的领域。无论是面向消费者的电商系统,还是面向企业管理的ERP系统,对商品规格的处理方式直接影响库存管理、订单履约、数…...
(三)动手学线性神经网络:从数学原理到代码实现
1 线性回归 线性回归是一种基本的预测模型,用于根据输入特征预测连续的输出值。它是机器学习和深度学习中最简单的模型之一,但却是理解更复杂模型的基础。 1.1 线性回归的基本元素 概念理解: 线性回归假设输入特征和输出之间存在线性关系。…...

Axure形状类组件图标库(共8套)
点击下载《月下倚楼图标库(形状组件)》 原型效果:https://axhub.im/ax9/02043f78e1b4386f/#g1 摘要 本图标库集锦精心汇集了8套专为Axure设计的形状类图标资源,旨在为产品经理、UI/UX设计师以及开发人员提供丰富多样的设计素材,提升原型设计…...

20250530-C#知识:String与StringBuilder
String与StringBuilder string字符串在开发中经常被用到,不过在需要频繁对字符串进行增加和删除时,使用StringBuilder有利于提升效率。 1、String string是一种引用类型而非值类型(某些方面像值类型)使用“”进行两个string对象的…...

从 Docker 到 Containerd:Kubernetes 容器运行时迁移实战指南
一、背景 Kubernetes 自 v1.24 起移除了 dockershim,不再原生支持 Docker Engine,用户需迁移至受支持的 CRI 兼容运行时,如: Containerd(推荐,高性能、轻量级) CRI-O(专为 Kuberne…...

uniapp中view标签使用范围
不止用于微信小程序。兼容型号,是uniapp内置组件之一,在uniapp中进行了跨平台适配。支持所有uniapp的平台。如微信小程序、h5、app、支付宝小程序...
Celery 核心概念详解及示例
Celery 核心概念详解及示例 Celery 是一个简单、灵活且可靠的分布式系统,用于处理大量消息,提供对任务队列的操作,并支持任务的调度和异步执行。它常用于深度优化 Web 应用的性能和响应速度,通过将耗时的操作移到后台异步执行&am…...

欢乐熊大话蓝牙知识14:用 STM32 或 EFR32 实现 BLE 通信模块:从0到蓝牙,你也能搞!
🚀 用 STM32 或 EFR32 实现 BLE 通信模块:从0到蓝牙,你也能搞! “我能不能自己用 STM32 或 EFR32 实现一个 BLE 模块?” 答案当然是:能!还能很帅! 👨🏭 前…...

IDEA 在公司内网配置gitlab
赋值项目链接 HTTPS 将HTTP的链接 ip地址换成 内网地址 例如:https:172.16.100.18/...... 如果出现需要需要Token验证的情况: 参考:Idea2024中拉取代码时GitLab提示输入token的问题_gitlab token-CSDN博客...