类初始化和加载
创建对象
Java 对象创建的核心流程:
类加载检查
内存分配
初始化零值
设置对象头
执行构造方法
内存分配策略和并发控制机制(如 TLAB(Thread-Local Allocation Buffer))直接影响性能
而对象内存布局的设计优化了 GC 效率与访问速度。
对象生命周期
Java 对象的生命周期可概括为:
创建阶段
应用阶段(被强引用持有)
不可达阶段(GC Roots 无法访问)
垃圾回收阶段(可能触发 finalize() 方法)
内存回收阶段
类加载器
类加载器有哪些?
启动类加载器:Bootstrap ClassLoader
用来加载 Java 核心类库,无法被 Java 程序直接引用
如加载存放在 JDK\jre\lib(JDK 代表 JDK 的安装目录,下同)下
或被 -Xbootclasspath 参数指定的路径中的,并且能被虚拟机识别的类库
扩展类加载器:Extension ClassLoader
该加载器由 sun.misc.Launcher$ExtClassLoader 实现
它负责加载 JDK\jre\lib\ext 目录中,或者由 java.ext.dirs 系统变量指定的路径中的所有类库(如 javax.* 开头的类)
开发者可以直接使用扩展类加载器
应用程序类加载器:Application ClassLoader
该类加载器由 sun.misc.Launcher$AppClassLoader 来实现
它负责加载用户类路径(ClassPath)所指定的类,开发者可以直接使用该类加载器
用户自定义类加载器,通过继承 java.lang.ClassLoader 类的方式实现
双亲委派模型
双亲委派模型的作用
工作过程
如果一个类加载器收到了类加载的请求,它首先不会自己去加载这个类,而是把这个请求委派给父类加载器去完成
每一层的类加载器都是如此,这样所有的加载请求都会被传送到顶层的启动类加载器中
只有当父加载无法完成加载请求(它的搜索范围中没找到所需的类)时,子加载器才会尝试去加载类
作用
避免类的重复加载:通过父类加载器优先加载类,确保同一类在JVM中仅被加载一次,减少内存占用和版本冲突风险
保证核心类库的安全性:防止用户自定义类覆盖JVM核心类(如
java.lang.String
),通过父类加载器优先加载核心类库实现安全隔离提高类加载效率:父类加载器已加载的类可直接复用,减少重复加载的开销
隔离类加载器的命名空间:不同类加载器加载的类处于独立命名空间,避免类冲突(如Tomcat多Web应用场景)
维护类的统一行为:确保核心类(如
Object
)在所有加载器中行为一致,避免因类版本不同导致的逻辑异常
类加载过程
类从被加载到虚拟机内存中开始,到卸载出内存为止
它的整个生命周期包括:
加载(Loading)
验证(Verification)
准备(Preparation)
解析(Resolution)
初始化(Initialization)
使用(Using)
卸载(Unloading)
其中验证、准备、解析 3 个部分统称为连接(Linking)
详见 类加载过程
类加载和双亲委派原则
就是把类加载过程和双亲委派机制加载步骤说明下就行了
垃圾回收
垃圾判断
3种方法
引用计数法:个对象维护一个引用计数器,记录当前被引用的次数
可达性分析算法:从 GC Roots 出发,遍历所有可达对象,未被遍历到的对象视为不可达(即垃圾)
引用类型判定法:根据引用类型的不同,决定对象回收的优先级和条件
详见 对象已死?
垃圾回收算法
分代收集算法
标记-清除算法
标记-整理算法
复制算法。部分也表述为标记-复制算法
详见 垃圾收集算法
标记清除缺点
标记清除算法的缺点是什么?
内存碎片化问题
执行效率低:标记和清除阶段均需遍历堆中的所有对象,时间复杂度为 O(n)
无法处理循环引用
不可预测的回收时机:被动触发,标记清除算法通常在全堆内存不足时触发回收,导致回收时机不可预测,可能在高负载时引发性能抖动
内存访问冲突风险:多线程问题,在标记和清除过程中,若与程序线程并发访问内存,可能引发数据不一致或崩溃
垃圾回收器
分类
经典垃圾回收器
Serial 收集器
特点:单线程串行回收,采用 "Stop-The-World" 机制;新生代使用复制算法,老年代(Serial Old)使用标记-整理算法
适用场景:单核 CPU 或小内存客户端应用(如早期桌面程序)
参数:
-XX:+UseSerialGC
ParNew 收集器
特点:Serial 的多线程版本,新生代并行回收(复制算法),需与 CMS 搭配使用
适用场景:JDK8 及之前版本的服务器端低延迟场景
参数:
-XX:+UseParNewGC
Parallel Scavenge/Old(PS+PO)
特点:多线程并行回收,以吞吐量优先(JDK8 默认组合),支持自适应调节堆大小
适用场景:后台批处理、大数据计算等高吞吐场景
参数:
-XX:+UseParallelGC
CMS(Concurrent Mark Sweep)
特点:并发标记清除(减少停顿时间),老年代使用标记-清除算法;存在内存碎片和浮动垃圾问题
适用场景:Web 服务、订单系统等低延迟场景(JDK14 后已移除)
参数:
-XX:+UseConcMarkSweepGC
现代垃圾回收器
G1(Garbage-First)
特点:分区式(Region)、并行与并发结合;采用复制算法和预测性停顿模型,支持大堆内存(6GB+)
优势:平衡吞吐量与延迟,JDK9 后成为默认回收器
参数:
-XX:+UseG1GC
ZGC(Z Garbage Collector)
特点:超低延迟(停顿 <10ms),支持 TB 级堆内存;通过读屏障和并发压缩实现
适用场景:金融交易、实时系统等对延迟敏感的场景(JDK11+ 支持)
参数:
-XX:+UseZGC
Shenandoah
特点:通过“颜色指针”和并发整理减少停顿时间,适合大堆且低延迟需求
适用场景:大堆内存、低延迟场景(需手动启用)
参数:
-XX:+UseShenandoahGC
Epsilon GC
特点:无操作的回收器,仅分配内存不回收;用于性能测试或内存管理完全可控的场景
适用场景:调试、短期任务或内存泄漏检测
参数:
-XX:+UseEpsilonGC
详见 经典的垃圾收集器
G1回收器
G1 回收器的特色是什么?
基于分区的内存管理机制
将堆内存划分为多个等大小的 Region(默认约 2048 个),每个 Region 可动态切换为 Eden、Survivor、Old 或 Humongous 区域
支持动态分代调整:年轻代占比通过
-XX:G1NewSizePercent
和-XX:G1MaxNewSizePercent
在 5%~60% 间自动伸缩大对象优化:超过单个 Region 50% 大小的对象分配至 Humongous 区域,避免内存碎片
可预测的停顿时间模型
通过
-XX:MaxGCPauseMillis
设定目标停顿时间(如 200ms),实现软实时回收优先级回收策略:按 Region 垃圾价值(回收空间/耗时)排序,优先处理高收益 Region(Garbage-First 设计理念)
衰减标准差算法:根据历史 GC 数据动态预测回收能力,确保目标时间内完成回收
并行与并发混合执行
并行回收:利用多核 CPU 并行执行 Young GC 和 Mixed GC,缩短 STW 时间
并发标记:后台线程与用户线程并发运行,仅初始标记/最终标记需短暂 STW
增量式回收:将老年代回收拆分为多次 Mixed GC,避免单次长时间停顿
高效空间整合与低碎片
全局使用标记-整理算法(Mark-Compact),局部使用标记-复制算法(Mark-Copy)
零内存碎片:支持长期运行服务稳定分配大对象
连续内存分配:通过指针碰撞(Bump-the-Pointer)直接分配,无需空闲链表管理
智能化数据结构支持
记忆集(RSet):每个 Region 维护跨 Region 引用记录,避免全堆扫描
全局卡片表(Card Table):标记 512B 卡片单元,精准追踪对象引用变化
并发标记快照(SATB):记录标记开始时的对象引用关系,防止漏标
适用场景对比
最大堆内存
CMS:≤4GB
G1:≥8GB(支持 TB 级堆)
停顿时间控制
CMS:无明确预测模型
G1:可设定目标停顿时间
内存碎片问题
CMS:需定期 Full GC 整理
G1:自动整合(零碎片)
JDK 版本兼容性
CMS:JDK8 及以下主流
G1:JDK7u4+ 可用,JDK9+ 默认
调优建议
关键参数:
-XX:+UseG1GC
-XX:MaxGCPauseMillis=200
-XX:G1HeapRegionSize=4M
-XX:InitiatingHeapOccupancyPercent=45
监控工具:使用 JMX 或
jstat -gcutil
观察 Region 分布与回收效率
CMS vs G1
垃圾回收器 CMS 和 G1 的比较?
分代与分区设计
CMS:物理分代,严格划分新生代和老年代,需配合其他新生代收集器(如 ParNew)使用
G1:逻辑分代 + 物理分区,堆内存划分为多个等大小 Region(默认约 2048 个),支持动态调整分代
回收算法与内存碎片
CMS:基于标记-清除算法,老年代回收后产生内存碎片,可能触发 Full GC
G1:基于标记-整理算法,通过 Region 间对象移动避免内存碎片
回收阶段差异
CMS 四阶段:
初始标记(STW 短暂) → 并发标记 → 重新标记(STW) → 并发清除
G1 四阶段:
初始标记(STW) → 并发标记 → 最终标记(STW) → 筛选回收(按 Region 价值排序回收)
停顿时间控制
CMS:以最小停顿时间为目标,但无法预测具体停顿时间
G1:支持可预测停顿模型(如
-XX:MaxGCPauseMillis=200ms
),适合大堆场景
内存碎片与浮动垃圾
CMS:存在内存碎片和浮动垃圾,可能因并发模式失败触发 Full GC
G1:无内存碎片,通过 SATB 机制避免浮动垃圾导致漏标
大对象处理
CMS:大对象直接进入老年代,加剧碎片问题
G1:大对象分配至 Humongous 区域(跨多个 Region),避免过早晋升
辅助数据结构
CMS:依赖 Card Table 记录跨代引用
G1:额外使用 Remembered Set(RSet)记录跨 Region 引用,占用约 20% Region 内存
执行负载与读写屏障
CMS:
仅需写后屏障维护卡表(Card Table),记录跨代引用,同步操作开销低
并发标记阶段使用增量更新策略,重新标记阶段计算量较小
G1:
需写前屏障(支持 SATB 快照)和写后屏障(维护 RSet),异步队列处理开销高
维护 Remembered Set(RSet)占用约 20% Region 内存,CPU 资源竞争更激烈
适用场景
CMS:中小型堆(≤4GB)、低延迟敏感场景(如 Web 服务),JDK8 及以下版本
G1:大型堆(≥8GB)、需平衡吞吐量与延迟的场景(如实时系统),JDK9+ 默认回收器
什么情况下使用 CMS,什么情况使用 G1?
CMS 适用场景
低延迟需求:适用于对响应时间敏感的应用(如 Web 服务、实时交易系统),需最小化 STW 停顿
中小型堆内存(≤4GB):内存碎片风险可控,需预留 20% 空间存放浮动垃圾
老年代为主的回收:适合对象晋升缓慢、老年代占用率高的场景(需配合 ParNew 收集器)
JDK 版本限制:推荐在 JDK 8 及以下版本使用
G1 适用场景
大堆内存(≥8GB):高效管理数十 GB 至数百 GB 堆内存,避免传统分代模型碎片问题
可预测停顿时间:通过
-XX:MaxGCPauseMillis
设置目标停顿(如 200ms),适合金融交易等实时系统内存碎片敏感场景:长期运行服务(如云原生应用),依赖标记-整理算法避免 Full GC
混合代际回收:支持同时回收新生代和老年代,适合对象生命周期复杂的应用
JDK 版本兼容性:JDK 7u4+ 可用,JDK 9+ 默认推荐
Java 垃圾回收
什么是 Java 里的垃圾回收?
为什么要 GC ?
如何触发垃圾回收?
定义
Java 垃圾回收(Garbage Collection, GC)是 JVM 提供的自动内存管理机制,用于回收程序中不再使用的对象所占用的内存空间
目标
其核心目标是如下 3 点:
内存泄漏:防止因对象长期未被释放导致的内存耗尽(如未关闭的文件句柄或数据库连接)
手动管理复杂性:避免开发者手动分配/释放内存时可能出现的错误(如野指针、双重释放)
内存碎片化:通过特定策略减少内存碎片,提升内存分配效率
触发条件
内存分配失败
Minor GC(新生代回收):当 Eden 区满时触发,通过复制算法快速回收存活率低的对象
Major GC(老年代回收):老年代空间不足时触发,通常伴随 Full GC
显式调用
通过
System.gc()
或Runtime.getRuntime().gc()
建议 JVM 执行垃圾回收,但 JVM 可能忽略该请求
阈值触发
a. 堆内存使用率:当堆内存使用超过预设阈值(如 70%)时触发 GC
b. 永久代/元空间满:Java 8 之前 PermGen 区满触发;Java 8+ Metaspace 区满触发时间间隔
部分 JVM 实现基于固定时间间隔(如每小时)触发 GC,无论内存使用情况
并发标记清除(CMS/G1)
低延迟回收器(如 CMS、G1)可能在并发标记阶段因内存碎片等问题触发 Full GC
stop the world
垃圾回收算法哪些阶段会 stop the world?
简述
STW 的触发与算法设计强相关
传统算法(如标记-清除、复制)的标记和转移阶段需全局暂停
并发算法(如 CMS、G1)通过并发标记减少 STW,但关键阶段仍无法避免
现代算法(如 ZGC)通过硬件优化(指针染色、读屏障)将 STW 压缩至极限
实际应用中,需根据业务需求(低延迟或高吞吐)选择合适的回收器,并通过调整堆大小、分代策略等参数优化 STW 时间
详细介绍
标记阶段
初始标记(Initial Mark)
触发原因:需暂停应用线程以确保根对象(GC Roots)的一致性,标记直接引用的存活对象
适用算法:CMS、G1(混合回收的初始阶段)
耗时:毫秒级(仅处理根对象)
重新标记(Remark)
触发原因:修正并发标记期间因应用线程修改引用导致的标记错误(漏标/误标)
适用算法:CMS、G1(混合回收的最终标记阶段)
耗时:毫秒级(通过写屏障优化缩短)
清除与复制阶段
复制/转移阶段(Evacuation)
触发原因:复制存活对象时需暂停线程,防止引用被修改
适用算法:复制算法(年轻代回收)、G1(混合回收的复制阶段)
耗时:与存活对象数量成正比(STW 主要瓶颈)
清理阶段(Sweep)
触发原因:统计分区存活情况时需短暂暂停线程
适用算法:G1。
耗时:微秒级(仅处理分区元数据)
分代回收的 STW 场景
年轻代回收(Minor GC)
触发原因:年轻代(如 Eden 区)空间不足时复制存活对象
适用算法:所有分代算法(Serial、Parallel、G1 等)
耗时:与年轻代存活对象数量相关(通常较短)
老年代回收(Full GC)
触发原因:老年代空间不足时触发全堆回收
适用算法:Serial Old、Parallel Old、CMS(并发模式失败时退化)
耗时:秒级(遍历全堆对象)
现代算法的优化与例外
并发标记与增量回收
CMS/G1:并发标记阶段无 STW,但初始/最终标记仍需暂停
ZGC/Shenandoah:通过读屏障和并发压缩,仅根扫描阶段需亚毫秒级 STW
Epsilon GC
特点:无回收操作,完全避免 STW
适用场景:特殊用途(如性能测试)
minorGC & majorGC & fullGC
minorGC、majorGC、fullGC 的区别,什么场景触发 full GC
核心区别
Full GC 的触发场景
老年代空间不足
对象晋升或大对象直接分配至老年代失败(通过
-XX:PretenureSizeThreshold
控制大对象阈值)
显式调用
System.gc()
开发者主动触发(需未禁用
-XX:+DisableExplicitGC
)
元空间/永久代不足
类/方法元数据加载过多(需合理设置
-XX:MaxMetaspaceSize
)
晋升失败(Promotion Failure)
Survivor 区空间不足,且老年代无法提供担保空间
CMS 并发模式失败
CMS 回收器无法及时回收足够空间,退化为 Serial Old 触发 Full GC
分配大对象失败
Eden 区无法分配大对象,且老年代空间不足
空间分配担保失败
老年代连续空间 < 新生代存活对象总大小(未开启
HandlePromotionFailure
时触发)
堆转储或诊断操作
使用
jmap -dump
等工具强制触发
Minor GC 和 Major GC:详见 分代收集理论
回收方法区
主要回收两部分内容
废弃的常量
不再使用的类型
参考:回收方法区。
分代垃圾回收器
分代垃圾回收器是怎么工作的?
以 CMS 收集器为例说明
分代回收器有两个分区
新生代:默认的空间占比总空间的 1/3
老生代:默认占比是 2/3
新生代使用的是复制算法,新生代里有 3 个分区:Eden、To Survivor、From Survivor,它们的默认占比是 8:1:1,它的执行流程如下:
把 Eden + From Survivor 存活的对象放入 To Survivor 区
清空 Eden 和 From Survivor 分区
From Survivor 和 To Survivor 分区交换,From Survivor 变 To Survivor,To Survivor 变 From Survivor
每次在 From Survivor 到 To Survivor 移动时都存活的对象,年龄就 +1,当年龄到达 15(默认配置是 15)时,升级为老生代
大对象也会直接进入老生代。老生代当空间占用到达某个值之后就会触发全局垃圾收回,一般使用标记整理的执行算法
以上这些循环往复就构成了整个分代垃圾回收的整体执行流程
GC流程
JVM 中一次完整的 GC 流程是怎样的,对象如何晋升到老年代?
GC 流程全解析
标记阶段
可达性分析:从 GC Roots(虚拟机栈局部变量、静态属性、常量等)出发标记存活对象
两次标记机制:首次标记不可达对象,二次筛选未覆盖
finalize()
或已执行过的方法的对象直接回收
垃圾回收
新生代回收(Minor GC)
触发条件:Eden 区空间不足时自动触发
执行步骤:
存活对象从 Eden 和 Survivor From 区复制到 Survivor To 区(年龄 +1)
清空 Eden 和 Survivor From 区,交换 From/To 区角色
Survivor 空间不足时,存活对象通过分配担保机制直接进入老年代
老年代回收(Full GC/Major GC)
触发条件:老年代空间不足、大对象分配失败、显式调用
System.gc()
等执行步骤:
采用标记-整理算法清理老年代内存碎片
回收后仍内存不足则抛出
OutOfMemoryError
内存整理与分配
碎片整理:将存活对象紧凑排列(复制或移动)确保连续内存空间
指针碰撞分配:直接分配连续内存,避免空闲链表管理开销
对象晋升到老年代的条件
年龄阈值机制
对象每经历一次 Minor GC 且存活,年龄 +1,达到阈值(默认 15)时晋升
调整参数:
-XX:MaxTenuringThreshold
。
动态年龄判定
Survivor 区中相同年龄对象总大小 > Survivor 空间的 50% 时,年龄 ≥ 该值的对象直接晋升
大对象直存老年代
对象大小超过
-XX:PretenureSizeThreshold
设定值(如 30MB)时直接分配至老年代
Survivor 区空间不足
Minor GC 后存活对象无法全部放入 Survivor 区时提前晋升
分配担保策略
Minor GC 前检查老年代剩余空间:
剩余空间 < 历次晋升对象的平均大小时触发 Full GC
Full GC 后仍不足则部分存活对象强制晋升
面试题
垃圾回收器的基本原理是什么?
对于 GC 来说,当程序员创建对象时,GC 就开始监控这个对象的地址、大小以及使用情况
通常,GC 采用有向图的方式记录和管理堆(heap)中的所有对象
通过这种方式确定哪些对象是"可达的",哪些对象是"不可达的"
当 GC 确定一些对象为"不可达"时,GC 就有责任回收这些内存空间
垃圾回收器可以马上回收内存吗?
可以
有什么办法主动通知虚拟机进行垃圾回收?
程序员可以手动执行 System.gc(),通知 GC 运行,但是 Java 语言规范并不保证 GC 一定会执行
JVM 的元空间(永久代)中会发生垃圾回收么?
注意下 Java8 中已经移除了永久代,新加了一个叫做元空间的 native 内存区
垃圾回收不会发生在永久代,如果永久代满了或者是超过了临界值,会触发完全垃圾回收(Full GC)
如果你仔细查看垃圾收集器的输出信息,就会发现永久代也是被回收的
这就是为什么正确的永久代大小对避免 Full GC 是非常重要的原因
新生代垃圾回收器和老年代垃圾回收器都有哪些?有什么区别?
新生代回收器:Serial、ParNew、Parallel Scavenge
老年代回收器:Serial Old、Parallel Old、CMS
整堆回收器:G1
新生代垃圾回收器一般采用的是复制算法,复制算法的优点是效率高,缺点是内存利用率低
老年代回收器一般采用的是标记-整理的算法进行垃圾回收
虚拟机执行子系统
类的生命周期
类的生命周期是 Java 虚拟机(JVM)管理类从加载到卸载的全过程,共分为五个阶段:
加载(Loading)→ 连接(Linking)→ 初始化(Initialization)→ 使用(Using)→ 卸载(Unloading)
详见 类加载的过程
类装载的执行过程
类装载是 Java 虚拟机(JVM)将类的字节码文件(.class)加载到内存并转化为可执行代码的过程
其执行流程可概括为加载、链接、初始化三个阶段,其中链接阶段进一步分为验证、准备、解析 三个子步
详见 类加载的过程
JVM 加载 class 文件的原理机制
类的加载流程
将其放在运行时数据区的方法区内
然后在堆区创建一个 java.lang.Class 对象
用来封装类在方法区内的数据结构
类的加载的最终产品
是位于堆区中的 Class 对象,Class 对象封装了类在方法区内的数据结构
并且向 Java 程序员提供了访问方法区内的数据结构的接口
通常是创建一个字节数组读入 .class 文件,然后产生与所加载类对应的 Class 对象
加载完成后,Class 对象还不完整,所以此时的类还不可用
当类被加载后就进入连接阶段,这一阶段包括:
验证
准备(为静态变量分配内存并设置默认的初始值)
解析(将符号引用替换为直接引用)三个步骤
最后 JVM 对类进行初始化,包括:
如果类存在直接的父类并且这个类还没有被初始化,那么就先初始化父类
如果类中存在初始化语句,就依次执行这些初始化语句
类加载器
什么是类加载器,类加载器有哪些?
定义
通过一个类的全限定名来获取描述该类的二进制字节流”这个动作放到 Java 虚拟机外部去实现
以便让应用程序自己决定如何去获取所需的类
实现这个动作的代码被称为“类加载器”
分类
启动类加载器(Bootstrap Class Loader)
由 JVM 自身用 C/C++ 实现,不继承
java.lang.ClassLoader
,无法在 Java 代码中直接访问功能:加载核心 Java 类库(如
rt.jar
、resources.jar
),路径为<JAVA_HOME>/jre/lib
或-Xbootclasspath
指定的目录
扩展类加载器(Extension Class Loader)
由
sun.misc.Launcher$ExtClassLoader
实现,父加载器为启动类加载器功能:加载扩展目录(
<JAVA_HOME>/jre/lib/ext
或java.ext.dirs
指定路径)中的 JAR 文件
应用程序类加载器(Application Class Loader/System Class Loader)
由
sun.misc.Launcher$AppClassLoader
实现,父加载器为扩展类加载器功能:默认加载用户类路径(ClassPath)下的类,是程序中默认的类加载器
自定义类加载器(User-Defined Class Loader)
开发者继承
ClassLoader
类实现,需重写findClass()
方法用途:动态加载网络资源、加密字节码、模块化隔离(如 Tomcat 热部署)等
详见 双亲委派模型
双亲委派模型
双亲委派模型是 Java 类加载器的层级委托机制
子加载器在加载类时会优先委派给父加载器处理,依次递归至最顶层的启动类加载器(Bootstrap ClassLoader)
若父加载器无法加载,子加载器才会自行加载,以此保证核心类库的安全性(如防止用户篡改 java.lang.String)并避免重复加载
详见 双亲委派模型
Tomcat 类加载器为什么要破坏双亲委派机制?open in new window
Java 程序运行机制
首先利用 IDE 集成开发工具编写 Java 源代码,源文件的后缀为 .java
再利用编译器(javac 命令)将源代码编译成字节码文件,字节码文件的后缀名为 .class
运行字节码的工作是由解释器(java 命令)来完成的
从上图可以看,java 文件通过编译器变成了 .class 文件,接下来类加载器又将这些 .class 文件加载到 JVM 中
其实可以一句话来解释:类的加载指的是将类的 .class 文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个 java.lang.Class 对象,用来封装类在方法区内的数据结构
Java 对象创建过程
首先让我们看看 Java 中提供的几种对象创建方式:
下面是对象创建的主要流程
虚拟机遇到一条 new 指令时,先检查常量池是否已经加载相应的类,如果没有,必须先执行相应的类加载
类加载通过后,接下来分配内存
若 Java 堆中内存是绝对规整的,使用“指针碰撞“方式分配内存
如果不是规整的,就从空闲列表中分配,叫做”空闲列表“方式
划分内存时还需要考虑一个问题-并发,也有两种方式
CAS 同步处理
本地线程分配缓冲(Thread Local Allocation Buffer, TLAB)
然后内存空间初始化操作,接着是做一些必要的对象设置(元信息、哈希码…),最后执行
init
方法
Java 对象结构
Java 对象由三个部分组成:对象头、实例数据、对齐填充
对象头由两部分组成
第一部分存储对象自身的运行时数据:对象的哈希码、GC 分代年龄、锁标识状态、线程持有的锁、偏向线程 ID(一般占 32/64 bit)
第二部分是指针类型,指向对象的类元数据类型(即对象代表哪个类)
如果是数组对象,则对象头中还有一部分用来记录数组长度
实例数据用来存储对象真正的有效信息(包括父类继承下来的和自己定义的)
对齐填充:JVM 要求对象起始地址必须是 8 字节的整数倍(8 字节对齐)
评论区