JDK8 JVM结构详细介绍 (一)(二)(三) (四)(五)

(一) JDK8 JVM结构详细介绍

一、JVM整体架构

JDK8的JVM采用分层设计,主要分为以下几个部分:

  1. 类加载子系统(Class Loader Subsystem)
  2. 运行时数据区(Runtime Data Area)
  3. 执行引擎(Execution Engine)
  4. 本地方法接口(Native Method Interface)
  5. 垃圾回收器(Garbage Collector)

二、详细结构介绍

1. 类加载子系统(Class Loader Subsystem)

作用:负责将.class文件加载到JVM中,并转换成运行时数据结构。

组成

  • 启动类加载器(Bootstrap ClassLoader)
    • 加载JRE核心类库(如rt.jar)
    • 由C++实现,是JVM的一部分
  • 扩展类加载器(Extension ClassLoader)
    • 加载JRE扩展目录(jre/lib/ext)中的类
    • 由Java实现
  • 应用程序类加载器(Application ClassLoader)
    • 加载用户类路径(ClassPath)上的类
    • 由Java实现

类加载过程

  1. 加载(Loading):查找并加载类的二进制数据
  2. 连接(Linking)
    • 验证(Verification):确保类文件符合JVM规范
    • 准备(Preparation):为静态变量分配内存并设置默认值
    • 解析(Resolution):将符号引用转换为直接引用
  3. 初始化(Initialization):执行类构造器()方法

2. 运行时数据区(Runtime Data Area)

作用:JVM运行时用于存储数据的区域,分为线程共享和线程私有两部分。

线程共享区域:
  • 方法区(Method Area)
    • 存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据
    • 在JDK8中,方法区被**元空间(Metaspace)**取代
  • 堆(Heap)
    • 所有对象实例和数组都在堆上分配内存
    • 是垃圾收集器管理的主要区域
    • 分为新生代(Young Generation)和老年代(Old Generation)
      • 新生代又分为Eden区、Survivor0区、Survivor1区
线程私有区域:
  • 程序计数器(Program Counter Register)
    • 当前线程所执行的字节码的行号指示器
    • 线程私有,唯一不会发生OutOfMemoryError的区域
  • Java虚拟机栈(Java Virtual Machine Stacks)
    • 存储栈帧(Stack Frame),每个方法执行时都会创建一个栈帧
    • 包含局部变量表、操作数栈、动态链接、方法出口等信息
    • 可能抛出StackOverflowError和OutOfMemoryError
  • 本地方法栈(Native Method Stack)
    • 为Native方法服务
    • 与虚拟机栈类似,但服务于Native方法

3. 执行引擎(Execution Engine)

作用:执行字节码,将字节码解释或编译后执行。

组成

  • 解释器(Interpreter)
    • 逐条解释执行字节码
    • 启动速度快,执行速度慢
  • 即时编译器(JIT Compiler)
    • 将热点代码编译成机器码执行
    • 执行速度快,启动速度慢
    • 包括:
      • C1编译器(Client模式)
      • C2编译器(Server模式)
      • Graal编译器(JDK9+可选)
  • 垃圾回收器(Garbage Collector)
    • 负责回收堆内存中的无用对象
    • JDK8默认使用:
      • Serial收集器(单线程)
      • Parallel Scavenge收集器(多线程,吞吐量优先)
      • CMS收集器(并发标记清除,低延迟)
      • Serial Old收集器(老年代单线程)
      • Parallel Old收集器(老年代多线程)

4. 本地方法接口(Native Method Interface)

作用:与本地方法库交互的接口,允许Java代码调用本地(Native)方法(如C/C++代码)。

5. 垃圾回收器(Garbage Collector)

作用:自动管理内存,回收不再使用的对象。

JDK8常用垃圾回收器

  • Serial收集器:单线程,适合客户端应用
  • Parallel Scavenge收集器:多线程,吞吐量优先
  • CMS收集器:并发标记清除,低延迟
  • G1收集器:JDK9默认,但JDK8也可使用,面向服务端应用

三、JDK8关键变化

  1. 元空间(Metaspace)取代永久代(PermGen)

    • 方法区实现从永久代改为元空间
    • 元空间使用本地内存(Native Memory),默认无大小限制
    • 解决了永久代的内存溢出问题
  2. 字符串常量池位置变化

    • 字符串常量池从永久代移到堆中
  3. 垃圾回收器优化

    • G1收集器在JDK8中可用(但默认不是)
    • CMS收集器仍然是低延迟场景的首选

四、JVM内存模型图示

+---------------------+
|      类加载子系统    |
+---------------------+
           |
           v
+---------------------+
|   运行时数据区       |
| +----------------+  |
| |    方法区        |  |
| +----------------+  |
| +----------------+  |
| |      堆          |  |
| |  +-----------+   |  |
| |  | 新生代     |   |  |
| |  |  Eden     |   |  |
| |  | Survivor0 |   |  |
| |  | Survivor1 |   |  |
| |  +-----------+   |  |
| |  +-----------+   |  |
| |  | 老年代     |   |  |
| |  +-----------+   |  |
| +----------------+  |
| +----------------+  |
| |    程序计数器    |  |
| +----------------+  |
| +----------------+  |
| |    Java栈        |  |
| +----------------+  |
| +----------------+  |
| |    本地方法栈    |  |
| +----------------+  |
+---------------------+
           |
           v
+---------------------+
|     执行引擎         |
+---------------------+
           |
           v
+---------------------+
|   本地方法接口       |
+---------------------+

五、总结

JDK8的JVM结构在保持JVM基本架构不变的基础上,主要变化在于:

  1. 方法区实现从永久代改为元空间
  2. 字符串常量池位置调整到堆中
  3. 垃圾回收器优化(G1可用但非默认)

这些变化提高了JVM的内存管理效率和稳定性,特别是解决了永久代常见的内存溢出问题。

(二) JDK8元空间相比永久代的具体优势

1. 内存管理方式

  • 永久代:使用JVM堆内存,大小固定(通过-XX:PermSize-XX:MaxPermSize参数设置)
  • 元空间:使用本地内存(Native Memory),默认无大小限制(除非设置-XX:MaxMetaspaceSize

2. 解决内存溢出问题

  • 永久代:容易因加载过多类或常量导致OutOfMemoryError: PermGen space错误
  • 元空间:由于使用本地内存且默认无限制,基本解决了永久代的内存溢出问题

3. 动态扩展能力

  • 永久代:大小固定,无法动态调整(除非重启JVM)
  • 元空间:可以根据需要动态扩展,更适应现代应用的需求

4. 内存使用效率

  • 永久代:内存分配和回收效率较低
  • 元空间:使用本地内存管理机制,内存分配和回收效率更高

5. 类加载器卸载

  • 永久代:类加载器卸载时,相关类的元数据可能无法完全释放
  • 元空间:类加载器卸载时,相关类的元数据可以更彻底地释放

6. 参数配置简化

  • 永久代:需要配置多个参数(-XX:PermSize-XX:MaxPermSize等)
  • 元空间:只需配置-XX:MaxMetaspaceSize(可选),参数更简单

7. 内存泄漏风险降低

  • 永久代:更容易因类加载器泄漏导致内存泄漏
  • 元空间:由于使用本地内存管理,类加载器泄漏导致的内存泄漏风险更低

8. 与JVM整体内存管理集成更好

  • 永久代:作为JVM堆的一部分,与堆内存管理相对独立
  • 元空间:作为本地内存的一部分,与JVM整体内存管理集成更好

9. 适应现代应用需求

  • 永久代:设计较早,难以适应现代应用大量动态加载类的需求
  • 元空间:设计更现代,能更好地适应动态类加载和卸载的需求

10. 性能优化空间更大

  • 永久代:内存管理机制相对简单,优化空间有限
  • 元空间:使用本地内存管理机制,有更大的性能优化空间

这些优势使得JDK8的元空间相比永久代在内存管理、稳定性和性能方面都有显著提升,特别是解决了永久代常见的内存溢出问题。

(三) JDK8中元空间和堆内存的区别

1. 存储内容不同

  • 堆内存:存储Java对象实例和数组,是所有线程共享的内存区域
  • 元空间:存储类的元数据信息(如类结构、方法代码、常量池等),也是线程共享的区域

2. 内存管理方式不同

  • 堆内存:由JVM的垃圾回收器(GC)管理,会定期进行垃圾回收
  • 元空间:使用本地内存(Native Memory),默认情况下不进行垃圾回收(除非类加载器被卸载)

3. 大小限制不同

  • 堆内存:大小可通过-Xms(初始堆大小)和-Xmx(最大堆大小)参数设置
  • 元空间:默认无大小限制(使用本地内存),但可通过-XX:MaxMetaspaceSize设置最大值

4. 内存分配位置不同

  • 堆内存:位于JVM进程的堆区域
  • 元空间:位于JVM进程的本地内存(Native Memory)区域

5. 内存回收机制不同

  • 堆内存:有完整的垃圾回收机制(如新生代、老年代GC)
  • 元空间
    • 默认情况下不进行垃圾回收
    • 当类加载器被卸载时,相关元数据会被回收
    • 可通过-XX:MetaspaceGCInterval等参数控制元空间GC行为(但通常不需要)

6. 内存溢出错误不同

  • 堆内存:内存不足时会抛出OutOfMemoryError: Java heap space
  • 元空间:内存不足时会抛出OutOfMemoryError: Metaspace

7. 参数配置不同

  • 堆内存:使用-Xms-Xmx等参数配置
  • 元空间:使用-XX:MetaspaceSize(初始大小)、-XX:MaxMetaspaceSize(最大大小)等参数配置

8. 内存使用特点不同

  • 堆内存:内存使用量随应用程序运行动态变化
  • 元空间:内存使用量主要取决于加载的类数量和大小,相对稳定(除非动态加载大量类)

9. 内存泄漏风险不同

  • 堆内存:容易因对象引用未释放导致内存泄漏
  • 元空间:内存泄漏风险较低,主要可能因类加载器泄漏导致

10. JVM实现方式不同

  • 堆内存:是JVM规范明确要求的内存区域
  • 元空间:是JDK8对方法区(永久代)的实现替代,属于JVM实现细节

总结:堆内存用于存储对象实例,由GC管理;元空间用于存储类元数据,使用本地内存,默认不GC,主要解决永久代的内存溢出问题。

(四) JDK8中元空间内存泄漏的常见原因

1. 类加载器泄漏

  • 最常见原因:类加载器未被正确卸载,导致其加载的所有类元数据无法释放
  • 典型场景
    • Web应用服务器中,Servlet容器未正确销毁Web应用对应的类加载器
    • 动态生成类的框架(如Groovy、JRuby)中,动态生成的类加载器未被释放
    • 自定义类加载器未实现正确的卸载逻辑

2. 动态生成大量类

  • 原因:应用程序动态生成大量类(如使用ASM、CGLIB、Javassist等字节码操作库)
  • 典型场景
    • AOP框架(如Spring AOP)动态生成代理类
    • ORM框架(如Hibernate)动态生成实体类
    • 动态语言运行时(如Groovy、JRuby)动态生成类

3. 字符串常量池泄漏

  • 原因:大量字符串被放入字符串常量池且未被回收
  • 典型场景
    • 大量使用String.intern()方法且未释放
    • 应用程序缓存大量字符串常量

4. 元数据缓存未清理

  • 原因:应用程序缓存了大量的类元数据信息
  • 典型场景
    • 反射频繁使用的类信息被缓存
    • 动态代理生成的类信息被长期缓存
    • 框架或库缓存了大量的类元数据信息

5. 类加载器层次结构问题

  • 原因:复杂的类加载器层次结构导致类加载器无法被卸载
  • 典型场景
    • 自定义类加载器设计不当,形成循环引用
    • 类加载器之间存在强引用关系,无法被垃圾回收

6. 框架或库的实现问题

  • 原因:某些框架或库存在类加载器管理问题
  • 典型场景
    • 某些第三方库未正确释放类加载器
    • 框架在重新加载配置或模块时未正确清理旧的类加载器

7. 热部署或热加载实现不当

  • 原因:热部署或热加载机制未正确清理旧的类加载器和类元数据
  • 典型场景
    • 开发环境中的热部署工具未正确清理旧的类加载器
    • 生产环境中的热加载机制实现不当

8. 内存泄漏检测工具不足

  • 原因:缺乏有效的元空间内存泄漏检测工具
  • 典型场景
    • 开发人员未使用专门的工具检测元空间内存泄漏
    • 内存泄漏问题在运行时才被发现

9. JVM参数配置不当

  • 原因:元空间大小设置不合理
  • 典型场景
    • -XX:MaxMetaspaceSize设置过小,导致频繁触发元空间GC
    • 未设置-XX:MaxMetaspaceSize,导致元空间无限增长

10. 应用程序设计问题

  • 原因:应用程序设计不当导致类加载器无法释放
  • 典型场景
    • 应用程序长期持有类加载器引用
    • 应用程序未正确实现类加载器的生命周期管理

总结:元空间内存泄漏的主要原因是类加载器未被正确卸载,导致其加载的类元数据无法释放。常见场景包括Web应用服务器、动态生成类的框架、字符串常量池滥用等。预防措施包括正确实现类加载器生命周期管理、合理使用动态类生成、监控元空间使用情况等。

(五) JDK8垃圾回收器对比

1. Serial收集器(单线程)

  • 特点
    • 单线程收集,简单高效
    • 采用复制算法
    • 进行垃圾收集时,必须暂停所有工作线程(Stop The World)
  • 适用场景
    • 客户端应用
    • 单CPU环境
    • 小型应用
  • 参数-XX:+UseSerialGC

2. Parallel Scavenge收集器(多线程,吞吐量优先)

  • 特点
    • 多线程收集,使用复制算法
    • 关注吞吐量(Throughput),即CPU用于运行用户代码的时间与CPU总消耗时间的比值
    • 自适应调节策略(可自动调整新生代大小、晋升老年代对象年龄等参数)
  • 适用场景
    • 后台运算为主的应用
    • 多CPU环境
    • 对吞吐量要求较高的应用
  • 参数-XX:+UseParallelGC(新生代)、-XX:+UseParallelOldGC(老年代)

3. CMS收集器(并发标记清除,低延迟)

  • 特点
    • 以获取最短回收停顿时间为目标
    • 并发收集、低停顿
    • 采用"标记-清除"算法
    • 分为四个阶段:初始标记、并发标记、重新标记、并发清除
    • 会产生内存碎片
  • 适用场景
    • 互联网站或B/S系统服务端
    • 对响应时间有较高要求的应用
  • 参数-XX:+UseConcMarkSweepGC
  • 缺点
    • 并发阶段会占用CPU资源
    • 无法处理浮动垃圾(Floating Garbage)
    • 会产生内存碎片

4. Serial Old收集器(单线程,老年代)

  • 特点
    • 单线程收集,使用标记-整理算法
    • 是Serial收集器的老年代版本
  • 适用场景
    • 客户端应用
    • 与Serial收集器搭配使用
  • 参数-XX:+UseSerialGC(自动使用)

5. Parallel Old收集器(多线程,老年代)

  • 特点
    • 多线程收集,使用标记-整理算法
    • 是Parallel Scavenge收集器的老年代版本
    • 关注吞吐量
  • 适用场景
    • 后台运算为主的应用
    • 多CPU环境
    • 与Parallel Scavenge收集器搭配使用
  • 参数-XX:+UseParallelOldGC

6. G1收集器(面向服务端应用)

  • 特点
    • 并行与并发:使用多个CPU来缩短Stop-The-World停顿时间
    • 分代收集:仍然保留分代概念
    • 空间整合:基于标记-整理算法,不会产生内存碎片
    • 可预测的停顿:可以指定最大停顿时间(-XX:MaxGCPauseMillis
    • 将堆划分为多个大小相等的Region
    • 优先回收价值最大的Region
  • 适用场景
    • 服务端应用
    • 大内存应用
    • 对停顿时间有要求的应用
  • 参数-XX:+UseG1GC
  • 优点
    • 并行与并发
    • 分代收集
    • 空间整合
    • 可预测的停顿
  • 缺点
    • 实现复杂
    • 内存占用相对较高

对比总结

收集器线程数算法目标适用场景是否产生碎片备注
Serial单线程复制吞吐量客户端简单高效
Parallel Scavenge多线程复制吞吐量后台运算自适应调节
CMS多线程标记-清除低延迟互联网服务会产生浮动垃圾
Serial Old单线程标记-整理吞吐量客户端与Serial搭配
Parallel Old多线程标记-整理吞吐量后台运算与Parallel搭配
G1多线程标记-整理低延迟+可预测服务端面向大内存

选择建议

  • 客户端应用:Serial或Parallel Scavenge
  • 后台运算应用:Parallel Scavenge + Parallel Old
  • 互联网服务:CMS(JDK8)或G1(JDK9+推荐)
  • JDK8中G1可用但非默认,CMS是低延迟场景的首选
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值