JVM基础知识都有什么
Java虚拟机(JVM)是Java平台的核心组件之一,它负责执行Java字节码,并提供了内存管理、垃圾回收、线程管理等功能。理解JVM的基础知识对于Java开发者来说至关重要,因为它不仅帮助我们编写高效的代码,还能在出现性能问题时提供诊断和优化的思路。本文将详细介绍JVM的基础知识,包括JVM的架构、内存模型、垃圾回收机制、类加载机制等。
1. JVM架构概述
JVM的架构可以分为三个主要部分:类加载器(Class Loader)、运行时数据区(Runtime Data Areas)和执行引擎(Execution Engine)。
1.1 类加载器(Class Loader)
类加载器负责将Java类文件(.class文件)加载到JVM中。JVM中的类加载器采用双亲委派模型(Parent Delegation Model),即当一个类加载器需要加载一个类时,它首先会委托其父类加载器去加载,只有在父类加载器无法加载时,子类加载器才会尝试加载。
JVM中有三种主要的类加载器:
- 启动类加载器(Bootstrap Class Loader):负责加载JVM核心类库(如
java.lang.*
),通常由C++实现,是JVM的一部分。
- 扩展类加载器(Extension Class Loader):负责加载
<JAVA_HOME>/lib/ext
目录下的类库。
- 应用程序类加载器(Application Class Loader):负责加载用户类路径(Classpath)上的类库。
1.2 运行时数据区(Runtime Data Areas)
运行时数据区是JVM在执行Java程序时所使用的内存区域,主要包括以下几个部分:
- 方法区(Method Area):用于存储类的元数据、常量、静态变量等。在JDK 8之前,方法区被称为永久代(PermGen),但在JDK 8及以后,方法区被替换为元空间(Metaspace),元空间使用本地内存(Native Memory)来存储类的元数据。
- 堆(Heap):堆是JVM中最大的一块内存区域,用于存储对象实例和数组。堆是所有线程共享的内存区域,也是垃圾回收的主要区域。
- 栈(Stack):每个线程在创建时都会分配一个栈,栈用于存储局部变量、方法参数、返回值等。栈是线程私有的内存区域,每个方法在执行时都会创建一个栈帧(Stack Frame),栈帧中包含了方法的局部变量表、操作数栈、动态链接、方法出口等信息。
- 程序计数器(Program Counter Register):程序计数器是线程私有的内存区域,用于记录当前线程执行的字节码指令地址。如果当前线程正在执行Java方法,程序计数器记录的是正在执行的字节码指令地址;如果当前线程正在执行本地方法(Native Method),程序计数器的值为空(Undefined)。
- 本地方法栈(Native Method Stack):本地方法栈与栈类似,但它是为本地方法(Native Method)服务的。本地方法栈也是线程私有的内存区域。
1.3 执行引擎(Execution Engine)
执行引擎负责执行字节码指令。JVM的执行引擎主要有两种实现方式:解释执行和即时编译(Just-In-Time Compilation,JIT)。
- 解释执行:解释器逐条读取字节码指令并执行。解释执行的优点是启动速度快,但执行效率较低。
- 即时编译(JIT):JIT编译器将热点代码(HotSpot Code)编译为本地机器码,以提高执行效率。JIT编译器会根据代码的执行频率动态决定哪些代码需要编译为本地机器码。
2. JVM内存模型
JVM的内存模型是理解JVM内存管理的基础。JVM的内存模型主要包括堆、栈、方法区、程序计数器和本地方法栈。
2.1 堆(Heap)
堆是JVM中最大的一块内存区域,用于存储对象实例和数组。堆是所有线程共享的内存区域,也是垃圾回收的主要区域。堆可以分为新生代(Young Generation)和老年代(Old Generation)。
- 新生代:新生代是对象创建的主要区域,大多数对象在新生代中被创建和销毁。新生代又可以分为Eden区、Survivor区(From区和To区)。新创建的对象首先分配在Eden区,当Eden区满时,会触发一次Minor GC,将存活的对象复制到Survivor区。经过多次Minor GC后仍然存活的对象会被晋升到老年代。
- 老年代:老年代用于存储存活时间较长的对象。当老年代空间不足时,会触发一次Full GC,Full GC会对整个堆进行垃圾回收。
2.2 栈(Stack)
栈是线程私有的内存区域,用于存储局部变量、方法参数、返回值等。每个方法在执行时都会创建一个栈帧,栈帧中包含了方法的局部变量表、操作数栈、动态链接、方法出口等信息。
2.3 方法区(Method Area)
方法区用于存储类的元数据、常量、静态变量等。在JDK 8之前,方法区被称为永久代(PermGen),但在JDK 8及以后,方法区被替换为元空间(Metaspace),元空间使用本地内存(Native Memory)来存储类的元数据。
2.4 程序计数器(Program Counter Register)
程序计数器是线程私有的内存区域,用于记录当前线程执行的字节码指令地址。如果当前线程正在执行Java方法,程序计数器记录的是正在执行的字节码指令地址;如果当前线程正在执行本地方法(Native Method),程序计数器的值为空(Undefined)。
2.5 本地方法栈(Native Method Stack)
本地方法栈与栈类似,但它是为本地方法(Native Method)服务的。本地方法栈也是线程私有的内存区域。
3. 垃圾回收机制
垃圾回收(Garbage Collection,GC)是JVM自动管理内存的机制,它负责回收不再使用的对象,释放内存空间。JVM的垃圾回收机制主要包括以下几个部分:
3.1 垃圾回收算法
JVM中常用的垃圾回收算法包括:
- 标记-清除算法(Mark-Sweep):标记-清除算法分为两个阶段:标记阶段和清除阶段。在标记阶段,垃圾回收器会标记所有存活的对象;在清除阶段,垃圾回收器会清除所有未被标记的对象。标记-清除算法的优点是实现简单,缺点是会产生内存碎片。
- 复制算法(Copying):复制算法将内存分为两个相等的区域,每次只使用其中一个区域。当使用的区域满时,垃圾回收器会将存活的对象复制到另一个区域,并清空当前区域。复制算法的优点是不会产生内存碎片,缺点是内存利用率较低。
- 标记-整理算法(Mark-Compact):标记-整理算法分为三个阶段:标记阶段、整理阶段和清除阶段。在标记阶段,垃圾回收器会标记所有存活的对象;在整理阶段,垃圾回收器会将所有存活的对象移动到内存的一端;在清除阶段,垃圾回收器会清除边界以外的内存。标记-整理算法的优点是不会产生内存碎片,缺点是整理阶段的开销较大。
3.2 垃圾回收器
JVM中常用的垃圾回收器包括:
- Serial收集器:Serial收集器是单线程的垃圾回收器,它在进行垃圾回收时会暂停所有用户线程(Stop-The-World)。Serial收集器适用于单核CPU或小内存的应用场景。
- Parallel收集器:Parallel收集器是多线程的垃圾回收器,它在进行垃圾回收时会使用多个线程并行执行垃圾回收任务。Parallel收集器适用于多核CPU或大内存的应用场景。
- CMS收集器(Concurrent Mark Sweep):CMS收集器是一种以最短停顿时间为目标的垃圾回收器,它在进行垃圾回收时会尽量减少用户线程的停顿时间。CMS收集器适用于对响应时间要求较高的应用场景。
- G1收集器(Garbage-First):G1收集器是一种面向服务端应用的垃圾回收器,它将堆内存划分为多个区域(Region),并在进行垃圾回收时优先回收垃圾最多的区域。G1收集器适用于大内存、多核CPU的应用场景。
4. 类加载机制
类加载机制是JVM将类文件加载到内存并生成对应的Class
对象的过程。类加载机制主要包括以下几个阶段:
4.1 加载(Loading)
加载阶段是类加载机制的第一个阶段,它负责将类的字节码文件加载到内存中,并生成对应的Class
对象。加载阶段由类加载器完成。
4.2 验证(Verification)
验证阶段是类加载机制的第二个阶段,它负责验证类的字节码文件是否符合JVM规范。验证阶段包括文件格式验证、元数据验证、字节码验证和符号引用验证。
4.3 准备(Preparation)
准备阶段是类加载机制的第三个阶段,它负责为类的静态变量分配内存并设置初始值。准备阶段不会执行任何Java代码。
4.4 解析(Resolution)
解析阶段是类加载机制的第四个阶段,它负责将类中的符号引用转换为直接引用。符号引用是指类、方法、字段等的名称和描述符,直接引用是指这些符号引用在内存中的具体地址。
4.5 初始化(Initialization)
初始化阶段是类加载机制的最后一个阶段,它负责执行类的静态初始化代码(如静态变量赋值、静态代码块等)。初始化阶段是类加载过程中唯一一个会执行Java代码的阶段。
5. 总结
JVM是Java平台的核心组件之一,理解JVM的基础知识对于Java开发者来说至关重要。本文详细介绍了JVM的架构、内存模型、垃圾回收机制和类加载机制。通过掌握这些基础知识,开发者可以更好地理解Java程序的运行机制,编写高效的代码,并在出现性能问题时进行诊断和优化。