gh_mirrors/jvm9/jvm全景解析:JVM底层原理学习的终极资源指南
【免费下载链接】jvm 🤗 JVM 底层原理最全知识总结 项目地址: https://gitcode.com/gh_mirrors/jvm9/jvm
引言:为什么JVM底层原理是Java开发者的必修课
你是否曾因频繁的Full GC导致系统响应缓慢而束手无策?是否在排查内存泄漏时面对堆转储文件无从下手?是否想深入理解Java程序的执行机制却苦于缺乏系统资源?本文将带你全面掌握JVM底层原理,从内存结构到垃圾回收,从类加载机制到性能调优,一站式解决你的所有困惑。读完本文,你将能够:
- 清晰描述JVM内存区域的划分及各区域功能
- 掌握对象创建、内存布局和访问方式的底层细节
- 深入理解垃圾收集算法及各类收集器的工作原理
- 熟练运用类加载机制解决实际开发问题
- 制定有效的JVM性能调优策略
JVM内存结构:深入理解JVM的"五脏六腑"
JVM内存结构是理解JVM底层原理的基础,它分为程序计数器、Java虚拟机栈、本地方法栈、堆和方法区五个部分。
程序计数器(PC寄存器)
程序计数器是一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器。它具有以下特点:
- 线程私有,每条线程都有自己的程序计数器
- 生命周期与线程相同
- 是唯一一个不会出现OutOfMemoryError的内存区域
- 如果线程正在执行的是一个Java方法,计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是Native方法,计数器值则为Undefined
Java虚拟机栈(Java栈)
Java虚拟机栈是描述Java方法运行过程的内存模型,它为每个方法创建一个栈帧(Stack Frame),用于存储局部变量表、操作数栈、动态链接、方法出口等信息。
局部变量表是一组变量值存储空间,用于存放方法参数和方法内部定义的局部变量。其容量在编译期确定,最小单位为Slot,32位类型占用1个Slot,64位类型(long和double)占用2个Slot。
Java虚拟机栈可能出现两种异常:
- StackOverflowError:线程请求的栈深度大于虚拟机所允许的深度
- OutOfMemoryError:虚拟机栈动态扩展时无法申请到足够的内存
本地方法栈(C栈)
本地方法栈与Java虚拟机栈作用类似,区别在于它为Native方法服务。虚拟机规范对本地方法栈没有强制规定,虚拟机可以自由实现它。
堆(Heap)
堆是JVM管理的内存中最大的一块,是所有线程共享的区域,在虚拟机启动时创建。其唯一目的是存放对象实例,几乎所有的对象实例都在这里分配内存。
新生代又分为Eden区、From Survivor区和To Survivor区,默认比例为8:1:1。对象优先在Eden区分配,当Eden区没有足够空间时,虚拟机将发起一次Minor GC。
方法区(Method Area)
方法区与堆一样,是所有线程共享的内存区域,用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。JDK 1.8及以后,方法区的实现由永久代改为元空间(Metaspace),元空间使用本地内存。
运行时常量池是方法区的一部分,用于存放编译期生成的各种字面量和符号引用。它具有动态性,运行期间也可以将新的常量放入池中,如String类的intern()方法。
虚拟机对象探秘:从创建到消亡的生命周期
对象的内存布局
在虚拟机中,对象在内存中分为三个部分:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)。
┌─────────────────────────────────────────────────┐
│ Object Header (16B) │
├───────────────┬───────────────┬─────────────────┤
│ Mark Word (8B)│ Klass Pointer (8B) │
├───────────────┴───────────────┴─────────────────┤
│ Instance Data │
├─────────────────────────────────────────────────┤
│ Padding (补齐8B的倍数) │
└─────────────────────────────────────────────────┘
- Mark Word:存储对象自身的运行时数据,如哈希码、GC分代年龄、锁状态标志等
- Klass Pointer:指向对象所属类元数据的指针
- 实例数据:对象真正存储的有效信息,即我们定义的各种字段内容
- 对齐填充:不是必然存在,仅起占位符作用,保证对象大小是8字节的整数倍
对象的创建过程
- 类加载检查:虚拟机遇到new指令时,首先检查常量池中是否有该类的符号引用,并检查类是否已加载、解析和初始化
- 分配内存:根据堆内存是否规整,采用指针碰撞或空闲列表方式分配内存
- 初始化零值:将分配到的内存空间初始化为零值(不包括对象头)
- 设置对象头:存储对象的类元信息、哈希码、GC分代年龄等信息
- 执行init方法:执行构造函数,初始化对象
对象的访问方式
对象的访问方式有句柄访问和直接指针访问两种:
虚拟机采用直接指针访问方式,好处是速度更快,节省一次指针定位的时间开销。
垃圾收集机制:JVM的内存自动管理艺术
对象存活判定算法
引用计数法
给对象添加一个引用计数器,每当有一个地方引用它时,计数器加1;引用失效时,计数器减1。当计数器为0时,对象可被回收。但该算法无法解决循环引用问题,因此Java虚拟机未采用。
可达性分析法
通过一系列称为"GC Roots"的根对象作为起始节点集,从这些节点开始,根据引用关系向下搜索,搜索过程中走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连时,证明此对象是不可用的。
GC Roots包括:
- 虚拟机栈中局部变量表中引用的对象
- 方法区中类静态属性引用的对象
- 方法区中常量引用的对象
- 本地方法栈中JNI引用的对象
引用类型
Java将引用分为强引用、软引用、弱引用和虚引用四种,强度依次减弱:
| 引用类型 | 被回收时机 | 用途 | 生存时间 |
|---|---|---|---|
| 强引用 | 永远不会回收 | 对象的一般状态 | 只要引用存在 |
| 软引用 | 内存不足时回收 | 缓存 | 内存不足时 |
| 弱引用 | GC时回收 | 缓存 | 下次GC前 |
| 虚引用 | 随时可能回收 | 跟踪对象回收 | 最短 |
垃圾收集算法
标记-清除算法(Mark-Sweep)
缺点:效率不高,产生大量不连续的内存碎片。
复制算法(Copying)
将可用内存按容量划分为大小相等的两块,每次只使用其中一块。当这块内存用完了,就将还存活着的对象复制到另一块上面,然后再把已使用过的内存空间一次清理掉。新生代采用该算法。
标记-整理算法(Mark-Compact)
标记过程与标记-清除算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。老年代采用该算法。
分代收集算法
根据对象存活周期的不同将内存划分为几块,一般是把Java堆分为新生代和老年代,根据各个年代的特点采用最适当的收集算法。
垃圾收集器
内存分配与回收策略:JVM如何管理对象的生命周期
对象优先在Eden区分配
大多数情况下,对象在新生代Eden区中分配。当Eden区没有足够空间进行分配时,虚拟机将发起一次Minor GC。
大对象直接进入老年代
大对象是指需要大量连续内存空间的Java对象,如很长的字符串或数组。虚拟机提供了-XX:PretenureSizeThreshold参数,令大于这个设置值的对象直接在老年代分配。
长期存活的对象将进入老年代
虚拟机给每个对象定义了一个对象年龄计数器。如果对象在Eden出生并经过第一次Minor GC后仍然存活,并且能被Survivor容纳的话,将被移动到Survivor空间中,年龄设为1。对象在Survivor区中每"熬过"一次Minor GC,年龄就增加1岁,当年龄达到-XX:MaxTenuringThreshold设置的阈值(默认为15)时,就会被晋升到老年代中。
动态对象年龄判定
如果在Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,无需等到MaxTenuringThreshold中要求的年龄。
空间分配担保
在发生Minor GC之前,虚拟机先检查老年代最大可用的连续空间是否大于新生代所有对象总空间。如果这个条件成立,那么Minor GC可以确保是安全的。如果不成立,则虚拟机会查看HandlePromotionFailure设置值是否允许担保失败。如果允许,那么会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小。如果大于,将尝试进行一次Minor GC;如果小于,或者HandlePromotionFailure设置不允许冒险,那这时也要改为进行一次Full GC。
类加载机制:JVM如何加载.class文件
类加载的过程
加载阶段
加载阶段需要完成以下三件事:
- 通过一个类的全限定名获取定义此类的二进制字节流
- 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
- 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口
验证阶段
验证是连接阶段的第一步,这一阶段的目的是确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。验证阶段大致会完成4个阶段的检验动作:文件格式验证、元数据验证、字节码验证和符号引用验证。
准备阶段
准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些变量所使用的内存都将在方法区中进行分配。这里所说的初始值"通常情况"下是数据类型的零值。
// 准备阶段后,value的值为0,而不是123
public static int value = 123;
// 准备阶段后,value的值为123,因为被final修饰
public static final int value = 123;
解析阶段
解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。符号引用以一组符号来描述所引用的目标,直接引用是可以直接指向目标的指针、相对偏移量或者是一个能间接定位到目标的句柄。
初始化阶段
初始化阶段是执行类构造器 ()方法的过程。 ()方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块(static{}块)中的语句合并产生的。
类加载器
双亲委派模型
双亲委派模型的工作过程是:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载。
类加载器种类
- 启动类加载器(Bootstrap ClassLoader):负责加载<JAVA_HOME>\lib目录中的类库
- 扩展类加载器(Extension ClassLoader):负责加载<JAVA_HOME>\lib\ext目录中的类库
- 应用程序类加载器(Application ClassLoader):负责加载用户类路径上的类库
- 自定义类加载器:用户根据需要自定义的类加载器
JVM性能调优:从理论到实践的最佳实践
性能调优目标
JVM性能调优的主要目标是:
- 低延迟:减少GC停顿时间
- 高吞吐量:提高单位时间内的处理能力
- 低内存占用:减少内存消耗
关键调优参数
| 参数 | 描述 | 默认值 | 建议值 |
|---|---|---|---|
| -Xms | 初始堆大小 | 物理内存的1/64 | 与-Xmx相同 |
| -Xmx | 最大堆大小 | 物理内存的1/4 | 根据应用需求调整 |
| -Xmn | 新生代大小 | 堆大小的1/3~1/4 | |
| -XX:SurvivorRatio | Eden区与Survivor区比例 | 8 | 8~10 |
| -XX:MaxTenuringThreshold | 对象晋升老年代的年龄阈值 | 15 | 5~15 |
| -XX:+UseConcMarkSweepGC | 使用CMS垃圾收集器 | 根据JDK版本选择 | |
| -XX:+UseG1GC | 使用G1垃圾收集器 | JDK 8及以上推荐 | |
| -XX:ParallelGCThreads | GC线程数 | CPU核心数 | CPU核心数 |
| -XX:MetaspaceSize | 元空间初始大小 | 128m | |
| -XX:MaxMetaspaceSize | 元空间最大大小 | 无限制 | 256m |
调优步骤
- 监控JVM运行状态:使用jstat、jstack、jmap等工具
- 分析性能瓶颈:确定是内存问题、GC问题还是CPU问题
- 调整JVM参数:根据瓶颈调整相应参数
- 验证调优效果:比较调优前后的性能指标
常见问题及解决方案
内存泄漏
内存泄漏是指程序中已动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。
解决方法:
- 使用内存分析工具(如MAT)分析堆转储文件
- 检查静态集合类是否持有对象引用
- 检查监听器和回调函数是否正确移除
- 检查数据库连接、文件流等资源是否正确关闭
GC频繁
GC频繁通常是由于新生代空间过小或对象创建速度过快导致的。
解决方法:
- 增加新生代大小(-Xmn)
- 调整Survivor区比例(-XX:SurvivorRatio)
- 使用合适的垃圾收集器(如G1)
- 减少大对象的创建
- 优化对象生命周期
内存溢出
内存溢出是指应用系统中存在无法回收的内存或使用的内存过多,最终使得程序运行要用到的内存大于能提供的最大内存。
解决方法:
- 增加堆内存大小(-Xmx)
- 检查是否存在内存泄漏
- 优化大对象的使用
- 使用内存效率更高的数据结构
总结与展望:JVM技术的发展趋势
JVM作为Java生态的基石,其技术一直在不断发展和完善。从JDK 9引入的Jigsaw模块化系统,到ZGC、Shenandoah等低延迟垃圾收集器的出现,JVM正在向更高性能、更低延迟、更易用的方向发展。
作为Java开发者,深入理解JVM底层原理不仅能帮助我们解决实际开发中的问题,更能提升我们对Java语言的认知高度。希望本文能成为你JVM学习之旅的指南针,带你探索JVM的奥秘,掌握Java技术的核心竞争力。
最后,推荐大家通过以下方式继续深入学习JVM:
- 阅读《深入理解Java虚拟机》等经典书籍
- 参与开源JVM项目的开发和讨论
- 关注JDK官方文档和更新日志
- 实践JVM调优,积累实战经验
掌握JVM,让你的Java程序跑得更快、更稳、更高效!
【免费下载链接】jvm 🤗 JVM 底层原理最全知识总结 项目地址: https://gitcode.com/gh_mirrors/jvm9/jvm
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



