Java随笔-JVM内存模型

Java随笔-JVM内存模型
JVM 内存模型运行时数据区分为线程共享和线程私有两大部分上图是 JDK 8 版本的完整结构。一、线程共享区域1. 堆Heap作用存放所有对象实例和数组参数控制-Xms初始大小、-Xmx最大大小分代设计年轻代Young新对象优先分配在Eden区Minor GC 后存活对象进入 Survivor 区S0/S1 通过复制算法交替使用老年代Old对象年龄达到阈值默认 15或 Survivor 空间不足时晋升使用标记-清除/整理算法大对象超过 -XX:PretenureSizeThreshold 直接进入老年代2. 元空间MetaspaceJDK 8 替代永久代PermGen使用本地内存Native Memory存储类元数据、常量池、字段/方法数据、静态变量参数-XX:MaxMetaspaceSize 控制上限OOM 场景动态生成大量类如 CGLIB 代理、反射直接内存Direct MemoryNIO 使用ByteBuffer.allocateDirect()通过 Unsafe.allocateMemory() 分配不受 JVM 堆大小限制参数-XX:MaxDirectMemorySize二、线程私有区域区域作用特点虚拟机栈VM Stack存储栈帧局部变量表、操作数栈、动态链接、方法返回地址每个方法调用对应一个栈帧-Xss控制栈大小本地方法栈Native Stack为 Native 方法C/C服务HotSpot 中与虚拟机栈合并实现程序计数器PC Register记录当前线程执行的字节码行号唯一不会 OOM 的区域线程切换时恢复执行位置三、对象从创建到销毁的完整生命周期1. 对象生命周期总览2. 每个阶段的详细流程① 类加载检查ObjectobjnewObject();// 触发类加载步骤内容检查类是否已加载在方法区元空间查找该类的类型信息未加载则触发类加载加载 → 验证 → 准备 → 解析 → 初始化检查访问权限private/protected/public是否允许当前上下文访问② 内存分配这是最关键的一步JVM 有三种分配策略策略 A堆上分配最常用// 绝大多数对象在堆上分配PersonpnewPerson();// 在 Eden 区分配分配流程TLAB 详解特性说明全称Thread Local Allocation Buffer作用每个线程在 Eden 区预先分配一小块私有内存避免多线程竞争大小默认 Eden 的 1%-XX:TLABWasteTargetPercent优势无锁分配性能极高溢出TLAB 放不下时回退到 Eden 区 CAS 分配策略 B栈上分配逃逸分析优化publicvoidmethod(){PersonpnewPerson();// p 不会逃逸出方法p.sayHello();}// p 随栈帧销毁无需 GC逃逸分析Escape AnalysisJVM 判断对象是否逃逸出方法/线程├─ 不逃逸 → 栈上分配随栈帧弹出自动销毁├─ 线程逃逸 → 堆上分配但可能标量替换└─ 全局逃逸 → 必须在堆上分配标量替换Scalar Replacement// 如果 Person 对象不逃逸JVM 可能不创建对象// 而是直接将其字段拆分为局部变量PersonpnewPerson();p.nameTom;// → 直接变成 String name Tom;p.age20;// → 直接变成 int age 20;开启参数-XX:DoEscapeAnalysisJDK 8 默认开启-XX:EliminateAllocations标量替换默认开启策略 C大对象直接进入老年代// 超过 -XX:PretenureSizeThreshold 的大数组/对象byte[]bigArraynewbyte[1024*1024*10];// 10MB直接进入老年代③ 对象初始化内存分配完成后JVM 执行三步初始化┌─────────────────┐│ 1. 零值初始化 │ 所有字段设为默认值int0, booleanfalse, refnull│ 2. 设置对象头 │ 设置 Mark Word、Klass Pointer、数组长度│ 3. 执行 │ 执行构造方法字段赋值、代码块、构造函数└─────────────────┘对象头Object Header结构④ 引用建立PersonpnewPerson();// p 是栈帧中的局部变量指向堆中对象引用关系⑤ 对象使用对象被引用期间可以被读取/修改字段调用方法作为参数传递被其他对象引用⑥ 引用失效引用失效的几种情况场景说明作用域结束方法执行完毕栈帧弹出局部变量引用消失显式置 nullp null;重新赋值p new Person();旧对象引用断开容器清理list.remove(obj)、map.clear()弱引用回收WeakReference被 GC 时回收软引用回收SoftReference内存不足时回收虚引用PhantomReference仅用于跟踪回收⑦ 可达性分析判断垃圾JVM 使用可达性分析算法Reachability Analysis而非引用计数GC Roots根对象集合├─ 虚拟机栈中局部变量引用的对象├─ 方法区中静态变量引用的对象├─ 方法区中常量引用的对象├─ 本地方法栈中 JNI 引用的对象├─ JVM 内部的引用基本数据类型对应的 Class 对象├─ 所有被同步锁synchronized持有的对象└─ 反映 JVM 内部情况的 JMXBean、JVMTI 中注册的回调等⑧ GC 回收根据对象所在区域触发不同的 GC年轻代回收Minor GC / Young GC触发条件Eden 区空间不足回收流程STWStop The World暂停所有应用线程标记 Eden From Survivor 中的存活对象复制存活对象到 To Survivor清空 Eden From SurvivorFrom 和 To 交换角色对象年龄 1年龄达到阈值默认15→ 晋升老年代恢复应用线程老年代回收Major GC / Old GC触发条件├─ 老年代空间不足├─ 空间分配担保失败└─ CMS 的并发标记周期回收算法├─ CMS标记-清除并发低停顿但碎片多├─ G1标记-整理 复制Region 化可预测停顿└─ ZGC/Shenandoah并发整理几乎零停顿Full GC触发条件├─ 老年代空间不足且无法分配├─ 元空间不足├─ System.gc() 调用建议性└─ CMS 的并发模式失败Concurrent Mode Failure特点回收整个堆年轻代 老年代 元空间停顿时间长⑨ 内存释放对象被 GC 回收后├─ 内存被标记为空闲├─ 空闲内存加入空闲列表Free List├─ 下次分配时复用└─ 长期运行后可能触发内存整理Compact消除碎片内存整理Compact整理前标记-清除后┌────┬────┬────┬────┬────┬────┬────┬────┐│存活│空闲│存活│空闲│存活│空闲│空闲│存活│ ← 大量碎片└────┴────┴────┴────┴────┴────┴────┴────┘整理后标记-整理┌────┬────┬────┬────┬────────────────────┐│存活│存活│存活│存活│ 连续空闲空间 │ ← 无碎片└────┴────┴────┴────┴────────────────────┘3. 完整流程图文字版阶段核心内容类加载检查双亲委派模型、加载→验证→准备→解析→初始化逃逸分析栈上分配、标量替换、不逃逸/线程逃逸/全局逃逸内存分配TLAB 快速分配、Eden CAS 分配、大对象直接进入老年代零值初始化所有字段自动设默认值设置对象头Mark Word、Klass Pointer、数组长度执行构造方法init()的完整执行顺序引用建立栈帧局部变量 → 堆对象四种引用类型对象使用读写字段、调用方法、同步锁引用失效作用域结束、置 null、容器清理、弱/软引用回收可达性分析GC Roots、两次标记、finalize() 复活机制GC 回收Minor GC复制算法、Major GCCMS/G1/ZGC、Full GC内存释放空闲列表、内存整理Compact4. 关键参数汇总| 参数 | 作用 || ------------------------------ | ---------------------------- ||-Xms/-Xmx| 堆初始/最大大小 ||-Xmn/-XX:NewRatio| 年轻代大小 / 年轻代:老年代比例 ||-XX:SurvivorRatio| Eden : Survivor 比例默认 8:1:1 ||-XX:MaxTenuringThreshold| 晋升老年代年龄阈值默认 15 ||-XX:PretenureSizeThreshold| 大对象直接进入老年代的阈值 ||-XX:UseTLAB| 开启 TLAB默认开启 ||-XX:TLABWasteTargetPercent| TLAB 占 Eden 的比例 ||-XX:DoEscapeAnalysis| 开启逃逸分析默认开启 ||-XX:EliminateAllocations| 开启标量替换默认开启 ||-XX:UseG1GC/-XX:UseZGC| 使用 G1 / ZGC 收集器 |四、关键对比特性JDK 7 及以前JDK 8方法区实现永久代PermGen在堆内元空间Metaspace在本地内存字符串常量池永久代堆静态变量永久代元空间OOM 风险PermGen spaceMetaspace五、常见 OOM 场景异常类型发生区域典型原因Java heap space堆内存泄漏、大对象、堆设置过小GC overhead limit exceeded堆98% 时间用于 GC回收不足 2% 空间Metaspace元空间动态生成大量类、类加载器未释放Unable to create new native thread虚拟机栈线程数超过系统限制Direct buffer memory直接内存NIO 使用不当、未释放 DirectByteBuffer