1. JVM 概述
Java 虚拟机(JVM)是 Java 程序的运行环境,负责将 Java 字节码转换为机器码并执行。JVM 是 Java 跨平台特性的核心,它使得 Java 程序可以在不同的操作系统上运行,而无需修改代码。
2. JVM 架构
JVM 主要由以下几个部分组成:
- - 类加载器(Class Loader):负责加载类文件到内存中。
- - 运行时数据区(Runtime Data Areas):包括方法区、堆、栈、程序计数器、本地方法栈等。
- - 执行引擎(Execution Engine):负责执行字节码。
- - 本地方法接口(Native Method Interface):提供调用本地方法的能力。
- - 本地方法库(Native Libraries):包含本地方法的实现。
3. 类加载器
类加载器负责将 `.class` 文件加载到 JVM 中。JVM 中有三种类加载器:
3.1 类加载过程
类加载的过程分为以下三个阶段:
-
加载(Loading):
-
通过类的全限定名获取类的二进制字节流。
-
将字节流转化为方法区的运行时数据结构。
-
在堆中生成一个
java.lang.Class
对象,作为方法区数据的访问入口。
-
-
链接(Linking):
-
验证(Verification):确保字节码符合 JVM 规范,防止恶意代码破坏 JVM。
-
准备(Preparation):为类的静态变量分配内存并设置默认初始值(如
0
、null
等)。 -
解析(Resolution):将常量池中的符号引用替换为直接引用。
-
-
初始化(Initialization):
-
执行类的静态代码块(
static {}
)和静态变量的赋值操作。 -
初始化是类加载的最后一步,只有在类被主动使用时才会触发。
-
3.2 双亲委派模型
双亲委派模型是 JVM 类加载的核心机制,其工作流程如下:
-
当一个类加载器收到加载请求时,首先会委派给其父类加载器。
-
如果父类加载器无法加载,子类加载器才会尝试加载。
优点:
-
避免类的重复加载。
-
防止核心类库被篡改(如自定义的
java.lang.String
类)。
4. JVM 内存模型
4.1 运行时数据区
JVM 的运行时数据区包括以下几个部分:
-
方法区(Method Area):
-
存储类信息、常量、静态变量、即时编译器编译后的代码等。
-
JDK 8 之前称为永久代(PermGen),JDK 8 及之后改为元空间(Metaspace),使用本地内存。
-
-
堆(Heap):
-
存储对象实例和数组。
-
分为新生代(Young Generation)和老年代(Old Generation)。
-
新生代进一步分为 Eden 区、Survivor 区(From 和 To)。
-
-
栈(Stack):
-
每个线程有一个私有的栈,存储局部变量、操作数栈、动态链接、方法出口等。
-
栈帧(Stack Frame)是栈的基本单位,每个方法调用对应一个栈帧。
-
-
程序计数器(Program Counter Register):
-
记录当前线程执行的字节码指令地址。
-
线程私有,不会发生内存溢出。
-
-
本地方法栈(Native Method Stack):
-
为本地方法(Native Method)服务。
-
4.2 内存溢出与内存泄漏
-
内存溢出(OutOfMemoryError):
-
堆内存溢出:对象过多,无法分配内存。
-
方法区溢出:加载的类过多或常量池过大。
-
栈溢出:递归调用过深或栈帧过大。
-
-
内存泄漏(Memory Leak):
-
对象不再使用,但仍被引用,导致无法被垃圾回收。
-
常见原因:静态集合类、未关闭的资源(如数据库连接、文件流)、监听器未移除等。
-
5. 垃圾回收机制深入
5.1 垃圾回收算法
-
标记-清除(Mark-Sweep):
-
标记所有活动对象,清除未标记的对象。
-
缺点:产生内存碎片。
-
-
复制(Copying):
-
将内存分为两块,每次只使用一块,存活的对象复制到另一块。
-
缺点:内存利用率低。
-
-
标记-整理(Mark-Compact):
-
标记所有活动对象,整理到内存的一端,清除剩余内存。
-
优点:避免内存碎片。
-
-
分代收集(Generational Collection):
-
新生代使用复制算法,老年代使用标记-清除或标记-整理算法。
-
5.2 垃圾回收器
-
Serial GC:
-
单线程垃圾回收器,适用于小型应用。
-
-
Parallel GC:
-
多线程垃圾回收器,注重吞吐量。
-
-
CMS(Concurrent Mark-Sweep):
-
并发标记清除,减少停顿时间,但会产生内存碎片。
-
-
G1(Garbage-First):
-
将堆内存划分为多个区域,优先回收垃圾最多的区域。
-
适用于大内存、低延迟的应用。
-
-
ZGC 和 Shenandoah:
-
低延迟垃圾回收器,停顿时间不超过 10ms。
-
6. 执行引擎
执行引擎负责执行字节码指令。JVM 的执行引擎主要有两种实现方式:
- - 解释执行:逐条解释字节码并执行。
- - 即时编译(Just-In-Time Compilation, JIT):将字节码编译为本地机器码,然后执行。JIT 可以提高程序的执行效率。
8. 常见问题与解决方案
- - OutOfMemoryError:通常是由于堆内存不足导致的,可以通过增加堆内存大小或优化代码来解决。
- - StackOverflowError:通常是由于递归调用过深或栈帧过大导致的,可以通过优化递归算法或增加栈大小来解决。
- - 类加载冲突:通常是由于类路径中存在多个版本的类文件导致的,可以通过清理类路径或使用自定义类加载器来解决。