【JVM 深入了解】JVM 到底包含什么?
👉博主介绍: 博主从事应用安全和大数据领域,有8年研发经验,5年面试官经验,Java技术专家,WEB架构师,阿里云专家博主,华为云云享专家,51CTO 专家博主
⛪️ 个人社区:个人社区
💞 个人主页:个人主页
🙉 专栏地址: ✅ Java 中级
🙉八股文专题:剑指大厂,手撕 Java 八股文
文章目录
- 1. JVM 架构
- 2. 垃圾收集机制
- 3. 性能优化
- 4. 内存模型
- 5. 类加载过程
- 6. 字节码执行
- 7. 常见问题与调试
- 8. 最佳实践
Java 虚拟机(JVM, Java Virtual Machine)是 Java 平台的核心组件,它负责执行 Java 字节码。JVM 提供了一个跨平台的运行环境,使得 Java 程序可以在不同的操作系统上运行而无需重新编译。以下是关于 JVM 的一些关键内容:
1. JVM 架构
JVM 主要由以下几个部分组成:
-
类加载器子系统(Class Loader Subsystem):
- 引导类加载器(Bootstrap Class Loader):加载核心 Java 类库(如
java.lang.*
)。 - 扩展类加载器(Extension Class Loader):加载 Java 扩展库(位于
jre/lib/ext
目录下的类库)。 - 应用程序类加载器(Application Class Loader):加载用户自定义的应用程序类。
- 引导类加载器(Bootstrap Class Loader):加载核心 Java 类库(如
-
运行时数据区(Runtime Data Area):
- 方法区(Method Area):存储类的结构信息,如运行时常量池、字段和方法数据、构造函数和普通方法的字节码内容等。
- 堆(Heap):存储对象实例和数组。堆是所有线程共享的内存区域。
- 虚拟机栈(VM Stack):每个线程都有一个私有的虚拟机栈,用于存储局部变量表、操作数栈、动态链接、方法出口等信息。
- 本地方法栈(Native Method Stack):与虚拟机栈类似,但为本地方法服务。
- 程序计数器(Program Counter Register):记录当前线程所执行的字节码指令地址。
-
执行引擎(Execution Engine):
- 解释器(Interpreter):逐条解释并执行字节码。
- 即时编译器(Just-In-Time Compiler, JIT):将热点代码编译成本地机器码,提高执行效率。
- 垃圾收集器(Garbage Collector, GC):自动管理内存,回收不再使用的对象。
- 本地接口(Native Interface):与本地方法库交互,调用本地方法。
-
本地方法库(Native Method Libraries):
- 包含了特定平台相关的本地方法实现,如文件 I/O、网络通信等。
2. 垃圾收集机制
JVM 的垃圾收集机制主要负责自动管理内存,回收不再使用的对象。常见的垃圾收集算法包括:
-
标记-清除(Mark and Sweep):
- 标记阶段:从根集合开始遍历,标记所有可达的对象。
- 清除阶段:回收未被标记的对象。
-
复制(Copying):
- 将存活对象从一个空间复制到另一个空间,然后清空原来的内存空间。
-
标记-整理(Mark and Compact):
- 标记阶段:标记所有可达的对象。
- 整理阶段:将存活对象向一端移动,然后清理边界外的内存。
-
分代收集(Generational Collection):
- 将堆分为新生代(Young Generation)和老年代(Old Generation),针对不同代采用不同的收集策略。
- 新生代通常使用复制算法,老年代通常使用标记-整理或标记-清除算法。
3. 性能优化
-
JIT 编译器:
- 通过将热点代码编译成本地机器码,提高执行效率。
- 可以通过
-XX:CompileThreshold
参数调整触发 JIT 编译的阈值。
-
垃圾收集器选择:
- 不同的垃圾收集器适用于不同的场景,如 Serial GC、Parallel GC、CMS GC 和 G1 GC。
- 可以通过
-XX:+UseSerialGC
、-XX:+UseParallelGC
、-XX:+UseConcMarkSweepGC
和-XX:+UseG1GC
等参数选择合适的垃圾收集器。
-
堆大小设置:
- 通过
-Xms
和-Xmx
参数设置初始堆大小和最大堆大小。 - 合理设置堆大小可以避免频繁的垃圾收集和 OutOfMemoryError。
- 通过
-
其他优化参数:
-XX:NewRatio
:设置新生代和老年代的比例。-XX:SurvivorRatio
:设置 Eden 区和 Survivor 区的比例。-XX:MaxTenuringThreshold
:设置晋升老年代的最大年龄。-XX:ParallelGCThreads
:设置并行垃圾收集的线程数。
4. 内存模型
JVM 的内存模型规定了多线程环境下对共享变量的访问规则,主要包括:
-
主内存与工作内存:
- 每个线程都有自己的工作内存,存储了该线程读写共享变量的副本。
- 主内存中存储了所有的共享变量。
-
原子性、可见性和有序性:
- 原子性:保证基本数据类型的读写操作是原子的。
- 可见性:确保一个线程对共享变量的修改对其他线程是可见的。
- 有序性:禁止编译器和处理器对指令进行重排序。
-
volatile 关键字:
- 保证变量的可见性和有序性。
- 防止指令重排序。
-
synchronized 关键字:
- 保证代码块的原子性和可见性。
- 通过监视器锁(Monitor Lock)实现互斥访问。
5. 类加载过程
类加载过程主要包括以下步骤:
-
加载(Loading):
- 通过类的全限定名获取二进制流。
- 将二进制流转换为方法区内的运行时数据结构。
- 在内存中生成一个代表这个类的
java.lang.Class
对象。
-
验证(Verification):
- 确保被加载的类符合 JVM 规范,防止恶意代码破坏 JVM 安全。
-
准备(Preparation):
- 为类的静态变量分配内存,并设置默认初始值。
-
解析(Resolution):
- 将符号引用替换为直接引用。
-
初始化(Initialization):
- 执行类构造器
<clinit>()
方法,初始化静态变量和静态代码块。
- 执行类构造器
6. 字节码执行
-
字节码:
- Java 源代码经过编译后生成的中间表示形式。
- 字节码是一种平台无关的二进制格式。
-
解释执行:
- 解释器逐条解释并执行字节码。
-
即时编译:
- JIT 编译器将热点代码编译成本地机器码,提高执行效率。
7. 常见问题与调试
-
OutOfMemoryError:
- 堆内存溢出(Heap Space):增加堆内存大小。
- 栈内存溢出(Stack Overflow):检查递归调用深度。
- 方法区内存溢出(PermGen 或 Metaspace):增加方法区大小。
-
性能监控工具:
- JConsole:图形化监控工具,可以查看 JVM 的内存使用情况、线程状态等。
- VisualVM:更强大的图形化监控工具,支持性能分析、内存快照等功能。
- jstat:命令行工具,用于监控 JVM 的性能统计信息。
- jmap:命令行工具,用于生成堆转储快照。
- jstack:命令行工具,用于打印线程堆栈跟踪信息。
8. 最佳实践
-
合理设置堆大小:
- 根据应用的实际需求设置
-Xms
和-Xmx
,避免频繁的垃圾收集。
- 根据应用的实际需求设置
-
选择合适的垃圾收集器:
- 根据应用的特点选择适合的垃圾收集器,如低延迟应用可以选择 G1 GC。
-
减少不必要的对象创建:
- 使用对象池、缓存等技术减少对象的创建和销毁。
-
使用并发编程:
- 利用多线程提高应用的并发处理能力,但要注意线程安全问题。
-
定期进行性能分析:
- 使用性能监控工具定期分析应用的性能瓶颈,进行针对性优化。
JVM 是 Java 应用程序运行的基础,理解 JVM 的架构、垃圾收集机制、性能优化、内存模型、类加载过程等内容对于开发高性能、可靠的 Java 应用至关重要。通过合理的配置和最佳实践,可以显著提升应用的性能和稳定性。
精彩专栏推荐订阅:在下方专栏👇🏻
✅ 2023年华为OD机试真题(A卷&B卷)+ 面试指导
✅ 精选100套 Java 项目案例
✅ 面试需要避开的坑(活动)
✅ 你找不到的核心代码
✅ 带你手撕 Spring
✅ Java 初阶