第25篇:Java JVM入门:内存模型与垃圾回收,理解JVM底层

第25篇:Java JVM入门:内存模型与垃圾回收,理解JVM底层 第25篇Java JVM入门内存模型与垃圾回收理解JVM底层 专栏Java底层核心进阶 前言很多Java开发者日常CRUD开发熟练使用各种语法和框架却始终对JVM底层一知半解。遇到内存溢出、程序卡顿、GC频繁、服务雪崩等线上问题时完全无从下手更别说JVM调优。读完本文你将彻底搞懂JVM整体架构及各模块核心职责堆、栈、方法区、程序计数器等内存区域的作用与特点垃圾回收GC的核心逻辑与触发条件三大经典GC算法的原理、优缺点及适用场景内存泄漏与内存溢出的区别、成因及解决思路一、JVM 整体架构概述JVMJava Virtual MachineJava虚拟机是一款跨平台的虚拟机是Java程序实现一次编译到处运行的核心。Java源码编译为.class字节码文件后由JVM解析、加载、执行屏蔽了底层操作系统、硬件的差异。标准JVM整体架构分为三大核心模块 运行时内存区四大模块协同完成程序运行1. 类加载器ClassLoader负责加载磁盘中的.class字节码文件将类信息、方法信息、常量等资源加载到JVM内存中分为启动类加载器、扩展类加载器、应用类加载器遵循双亲委派模型。2. 执行引擎JVM的“CPU”负责解析字节码指令并执行包含解释器即时解释执行、JIT编译器热点代码即时编译优化提升运行效率、垃圾回收器负责内存垃圾回收。3. 本地方法接口JNI对接本地Native方法C/C编写的方法弥补Java底层操作短板比如系统调用、硬件操作等。4. 运行时数据区核心重点也就是我们常说的JVM内存模型程序运行过程中所有数据、变量、对象、指令信息均存储在此区域是本文核心讲解内容。二、JVM 运行时内存模型五大区域根据《Java虚拟机规范》JVM运行时内存划分为五大核心区域按照线程归属可分为两类线程私有区域随线程创建而生、线程销毁而回收、线程共享区域全局共享JVM启动创建、关闭销毁。1. 程序计数器Program Counter Register归属线程私有核心作用记录当前线程正在执行的字节码指令地址用于线程切换后恢复执行位置。Java是多线程语言CPU通过时间片轮转切换线程当线程暂停执行时程序计数器会记录当前执行进度线程重新获取CPU时间片后精准接续执行。核心特点JVM中唯一不会发生OutOfMemoryError内存溢出的区域内存占用极小、结构简单、线程隔离、无垃圾回收生命周期与当前线程完全一致2. Java虚拟机栈JVM栈/栈内存归属线程私有核心作用存储方法执行的临时数据每调用一个方法就会创建一个栈帧方法执行完毕栈帧自动出栈销毁。栈帧中包含局部变量表、操作数栈、动态链接、方法返回地址等信息我们日常定义的局部变量、基本数据类型变量均存储在栈中。核心特点先进后出的栈结构执行效率极高生命周期跟随线程线程结束栈内存立即释放栈空间固定可通过JVM参数-Xss设置大小栈溢出会抛出StackOverflowError常见于递归死循环、方法嵌套过深3. 本地方法栈归属线程私有核心作用作用与虚拟机栈类似专门为JVM调用的Native本地方法服务存储本地方法执行的栈帧信息。核心特点同样会抛出StackOverflowError和OutOfMemoryError底层由C/C实现Java开发者基本无需手动操作4. 堆内存Heap—— GC主战场归属线程共享核心作用JVM中最大的内存区域所有通过new关键字创建的对象、数组均存储在堆中是垃圾回收的核心区域。为了提升GC效率堆内存被细分为新生代Young、老年代Old。其中新生代又分为Eden区、From Survivor、To Survivor默认比例8:1:1。核心特点全局线程共享所有线程均可访问堆中的对象内存空间大、生命周期长随JVM启动和关闭频繁发生GC用于回收失效对象堆内存不足会抛出OutOfMemoryError堆内存溢出5. 方法区Method Area归属线程共享核心作用存储JVM加载的类元数据包含类名、方法名、字段信息、常量池、静态变量、即时编译代码等全局静态资源。JDK1.8是重要分水岭JDK1.8之前方法区存在永久代JDK1.8之后移除永久代使用元空间Metaspace替代元空间使用本地内存不再占用JVM堆内存大幅降低了方法区溢出概率。核心特点全局共享存储静态、全局、类级别数据极少触发GC仅回收部分废弃常量和无用类元空间溢出会抛出OutOfMemoryError三、垃圾回收GC核心原理Java语言最大的优势之一就是自动垃圾回收无需开发者手动申请和释放内存由JVM的垃圾回收器自动识别、回收内存中无效对象避免内存浪费。1. GC核心定义GCGarbage Collection即垃圾回收指JVM自动扫描堆内存识别没有任何有效引用指向的对象清空其占用的内存空间实现内存复用防止内存堆积。2. 垃圾判定依据GC RootsJVM通过可达性分析算法判定对象是否存活以GC Roots为起始节点遍历内存能遍历到的对象为存活对象无法遍历到的对象即为垃圾对象可被回收。常见GC Roots虚拟机栈中引用的对象方法区中静态变量、常量引用的对象本地方法栈Native引用的对象活跃线程的引用对象3. GC的触发条件GC不会随意触发仅在满足特定条件时自动执行分为Minor GC新生代GC和Full GC全局GC。1Minor GC 触发条件新生代Eden区内存空间不足无法为新对象分配内存时触发Minor GC回收新生代无效对象频率高、速度快、耗时短。2Full GC 触发条件老年代内存空间不足Minor GC后存活对象过多无法放入Survivor区直接进入老年代方法区/元空间内存不足手动调用System.gc()仅建议不保证立即执行JVM出现内存预警、并发GC失败等异常场景注意Full GC会回收整个堆内存新生代老年代耗时极长、会造成程序STW暂停所有用户线程线上项目需尽量避免频繁Full GC。四、三大经典GC回收算法详解GC算法是垃圾回收的核心所有垃圾回收器均基于三大基础算法优化迭代而来分别是标记-清除、标记-复制、标记-整理。1. 标记-清除算法Mark-Sweep执行流程分为两步第一步标记遍历内存标记所有存活对象第二步清除清空所有未被标记的垃圾对象。优点算法简单、实现容易无需移动对象位置。缺点产生大量内存碎片回收后的内存分散不连续内存碎片过多时大对象无法分配连续内存容易触发OOM两次遍历内存效率偏低适用场景老年代低频GC场景2. 标记-复制算法Mark-Copy执行流程将内存划分为两个大小相等的区域平时只使用其中一块。GC时标记所有存活对象将存活对象完整复制到另一块空白内存中复制完成后直接清空原内存区域。新生代8:1:1的分区设计就是为了适配标记复制算法大幅提升回收效率。优点无内存碎片内存空间连续规整只需标记存活对象回收效率极高缺点内存利用率低永久浪费一半内存空间存活对象较多时复制成本极高、效率下降适用场景新生代高频GC存活对象少、垃圾对象多3. 标记-整理算法Mark-Compact执行流程第一步标记所有存活对象第二步将所有存活对象向内存一端移动、紧凑排列最后直接清空边界外的全部垃圾内存。优点无内存碎片内存连续规整内存利用率100%无空间浪费缺点需要移动所有存活对象开销大、速度慢STW时间更长对程序性能影响较大适用场景老年代GC存活对象多、垃圾少不适合复制算法五、内存泄漏 vs 内存溢出成因与解决方案日常开发中最常见的两个内存问题内存泄漏Memory Leak和内存溢出OOM二者因果关联内存泄漏长期堆积最终必然导致内存溢出。1. 内存泄漏Memory Leak定义程序中存在无效对象被持续引用GC无法回收这些本该销毁的对象导致内存被无效占用、无法释放内存使用率持续升高。常见成因静态集合类static List/Map长期持有对象引用未关闭资源IO流、数据库连接、Redis连接、线程池未关闭匿名内部类、非静态内部类持有外部类引用全局变量滥用对象使用完毕未置空自定义缓存无过期策略数据无限堆积解决思路及时关闭所有IO、连接、线程等资源使用try-with-resources自动关闭流缓存设置过期时间、淘汰策略使用弱引用存储缓存对象避免静态集合无限存储数据定期清理无效数据使用内存分析工具MAT、JProfiler定位泄漏对象2. 内存溢出OOMOutOfMemoryError定义JVM内存空间全部被占满没有多余内存分配给新对象程序无法继续运行直接抛出异常崩溃。常见成因长期内存泄漏堆积耗尽可用内存一次性创建超大对象、超大集合批量读取超大文件死循环不断创建新对象无销毁逻辑JVM内存参数配置过小无法支撑业务并发量解决思路优先排查内存泄漏问题修复代码漏洞优化代码逻辑避免循环创建对象、一次性加载海量数据调整JVM参数合理扩容堆内存、元空间大小分批次、分页处理大数据避免一次性加载入内存六、总结与后续预告本文核心总结JVM内存分为线程私有程序计数器、虚拟机栈、本地方法栈和线程共享堆、方法区两大类型各区域各司其职堆是GC主战场分为新生代、老年代不同区域适配不同回收算法三大GC算法各有优劣复制算法适配新生代标记整理适配老年代标记清除易产生内存碎片内存泄漏是缓慢堆积的隐患内存溢出是最终爆发的结果调优核心是杜绝泄漏、优化GC