一、JVM基本概念
JVM(Java Virtual Machine,Java 虚拟机)是 Java 程序运行的核心组件。它负责将 Java 字节码转换为特定平台的机器指令,并提供内存管理、垃圾回收、安全性等功能。JVM 的主要功能包括以下:
- 加载和执行字节码:
- 将 Java 源代码编译后的字节码(.class 文件)加载到内存中,并解释执行或即时编译(JIT)为机器指令。
- 内存管理:
- 管理 Java 程序的内存分配和回收,包括堆、栈、方法区等内存区域。
- 垃圾回收:
- 自动回收不再使用的对象,释放内存。
- 安全性:
- 提供沙箱机制,限制 Java 程序的权限,防止恶意代码的执行。
- 多线程支持:
- 提供线程管理和同步机制,支持多线程编程。
二、JVM的主要组成部分
JVM的主要组成部分包括类加载器、运行时数据区、执行引擎、本地方法接口、本地方法库等。
- 类加载器(Class Loader):
- 作用:
- 负责加载Java类文件,并将其转换成JVM内部的运行时数据结构;
- 分类:
- 启动类加载器(Bootstrap Class Loader):加载 JVM 核心类库(如 java.lang.*)。
- 扩展类加载器(Extension Class Loader):加载扩展类库(如 javax.*)。
- 应用程序类加载器(Application Class Loader):加载用户类路径(Classpath)下的类。
- 双亲委派模型:
- 类加载器在加载类时,首先委托父类加载器加载,只有在父类加载器无法加载时,才由自己加载。
- 作用:
- 运行时数据区(Runtime Data Areas):
- JVM 的内存区域分为以下几个部分:
- 堆(Heap):
- 存储 Java 对象实例和数组。
- 是垃圾回收的主要区域。
- 分为新生代(Young Generation)和老年代(Old Generation)。
- 栈(Stack):
- 存储方法的局部变量、操作数栈、动态链接、方法出口等信息。
- 每个线程都有一个独立的栈。
- 方法区(Method Area):
- 存储类的元数据信息,如类名、方法名、字段名、常量池等。
- 在 JDK 8 之前,方法区的实现是永久代(PermGen);在 JDK 8 及之后,方法区的实现被元空间(Metaspace)取代。
- 本地方法栈(Native Method Stack):
- 为本地方法(Native Method)服务。
- 程序计数器(Program Counter Register):
- 记录当前线程执行的字节码指令地址。
- 堆(Heap):
- JVM 的内存区域分为以下几个部分:
- 执行引擎(Execution Engine):
- 作用:
- 负责解释执行Java字节码,并对程序进行优化;
- 组成部分:
- 解释器(Interpreter):逐行解释执行字节码。
- 即时编译器(JIT Compiler):将热点代码(频繁执行的代码)编译为机器指令,提高执行效率。
- 垃圾回收器(Garbage Collector):自动回收不再使用的对象。
- 作用:
- 本地方法接口(Native Method Interface, JNI):
- 作用:
- 提供 Java 代码调用本地方法(如 C/C++ 代码)的接口。
- 使用场景:
- 调用操作系统底层功能。
- 调用已有的本地库。
- 作用:
- 本地方法库(Native Method Library):
- 作用:
- 包含本地方法的具体实现(如 C/C++ 代码)。
- 作用:
三、JVM 工作流程
3.1 JVM工作流程图:
3.2 JVM具体执行步骤:
- 类加载:
- 类加载器将 .class 文件加载到内存中,并生成对应的 Class 对象。
- 字节码验证:
- 验证字节码的合法性,确保其符合 JVM 规范。
- 内存分配:
- 在堆中为对象分配内存。
- 字节码执行:
- 执行引擎解释执行字节码,或通过 JIT 编译器将字节码编译为机器指令执行。
- 垃圾回收:
- 垃圾回收器自动回收不再使用的对象,释放内存。
- 程序结束:
- 当程序执行完毕或调用 System.exit() 时,JVM 退出。
3.3 类加载过程
当 JVM 加载一个类时,会解析类文件中的常量池表,并将其内容加载到 运行时常量池(Runtime Constant Pool) 中。以下是类加载的详细过程:
- 加载(Loading)
- JVM 读取类文件,并将其二进制数据加载到内存中。
- 在内存中生成一个代表该类的 Class 对象。
- 链接(Linking)
链接过程分为三个步骤:- 验证(Verification):
- 确保类文件的字节码是合法的,符合 JVM 规范。
- 准备(Preparation):
- 为类的静态变量分配内存,并设置默认初始值。
- 解析(Resolution):
- 将常量池表中的符号引用解析为直接引用。
- 对于字符串字面量,JVM 会将其加载到字符串常量池中。
- 验证(Verification):
- 初始化(Initialization)
- 执行类的静态初始化代码(如静态变量赋值、静态代码块)。
3.4 常量池的解析机制
在类加载的 解析(Resolution) 阶段,JVM 会处理常量池表中的字符串字面量,并将其加载到字符串常量池中。具体过程如下:
- 解析字符串字面量
- 当 JVM 解析到一个字符串字面量(如 “hello”)时,会检查字符串常量池中是否已经存在该字符串。
- 如果存在,则直接使用字符串常量池中的引用。
- 如果不存在,则在字符串常量池中创建一个新的字符串对象,并将其引用添加到运行时常量池中。
- 当 JVM 解析到一个字符串字面量(如 “hello”)时,会检查字符串常量池中是否已经存在该字符串。
- 运行时常量池的引用
- 字符串常量池中的字符串对象会被运行时常量池引用。
- 运行时常量池中的字符串字面量实际上是字符串常量池中字符串对象的引用。
四、JVM内存管理模型
JVM(Java Virtual Machine)的内存管理模型是 Java 程序运行的核心基础。它负责管理 Java 程序的内存分配、垃圾回收以及运行时数据的存储。JVM 的内存管理模型可以分为以下几个主要区域:
JDK1.7版本主要分为以下几个区域:
JDK1.8版本相对JDK1.7版本做了如下调整:
- 方法区的变化
- JDK 1.7:
- 方法区的实现是永久代(PermGen),是 JVM 内存的一部分。
- 永久代的大小固定,容易导致 OutOfMemoryError: PermGen space 错误。
- JDK 1.8:
- 方法区的实现改为元空间(Metaspace),永久代被移除。
- 元空间不属于 JVM 内存中,而是在本地内存中。
- 元空间的大小默认不受限制(受限于系统内存)。
- JDK 1.7:
- 字符串常量池的变化
- JDK 1.7:
- 字符串常量池位于 永久代(PermGen) 中。
- 永久代的大小固定,容易导致 OutOfMemoryError: PermGen space 错误。
- 字符串常量池中存储的是字符串对象的引用。
- JDK 1.8:
- 字符串常量池被移动到堆内存(Heap) 中。
- 字符串常量池中存储的是字符串对象的实例(String 对象)。
- 当字符串常量池中的对象不再被引用时,垃圾回收器会将其回收。
- JDK 1.7:
- 静态变量的变化
- JDK 1.7:
- 静态变量(包括静态基本类型和静态对象引用)存储在 永久代(PermGen) 中。
- 永久代的大小固定,容易导致 OutOfMemoryError: PermGen space 错误。
- JDK 1.8:
- 静态变量被移动到 堆内存(Heap) 中。
- 静态变量与普通对象一样,由垃圾回收器管理。
- JDK 1.7:
- 运行时常量池的变化
- JDK 1.7:
- 运行时常量池位于 永久代(PermGen) 中。
- 运行时常量池存储类文件中的常量(如字符串常量、类和接口的全限定名、字段和方法的名称和描述符等)。
- 永久代的大小固定,容易导致 OutOfMemoryError: PermGen space 错误。
- JDK 1.8:
- 运行时常量池被移动到 元空间(Metaspace) 中。
- 元空间的大小不受JVM内存限制(仅受限于系统内存),避免了永久代的内存溢出问题。
- JDK 1.7:
4.1 方法区(Method Area)
- 作用:
- 存储类的元数据信息,如类名、方法名、字段名、常量池等。
- 在 JDK 8 之前,方法区的实现是永久代(PermGen);在 JDK 8 及之后,方法区的实现被元空间(Metaspace)取代。
- 特点:
- 线程共享。
- 存储的数据包括:
- 运行时常量池(Runtime Constant Pool)。
- 类信息(Class Metadata)。
- 静态变量(Static Variables)。
- JIT 编译后的代码(Just-In-Time Compiled Code)。
4.2 堆(Heap)
- 作用:
- 存储 Java 对象实例和数组。
- 是垃圾回收的主要区域。
- 特点:
- 线程共享。
- 分为新生代(Young Generation)和老年代(Old Generation):
- 新生代:存放新创建的对象,分为 Eden 区、Survivor 区(From 和 To)。
- 老年代:存放长期存活的对象。
- 可以通过 JVM 参数调整堆的大小:
- -Xms:初始堆大小。
- -Xmx:最大堆大小。
4.3 栈(Stack)
- 作用:
- 存储方法的局部变量、操作数栈、动态链接、方法出口等信息。
- 每个线程都有一个独立的栈。
- 特点:
- 线程私有。
- 栈的大小可以通过 JVM 参数调整:
- -Xss:设置每个线程的栈大小。
- 栈帧(Stack Frame)是栈的基本单位,每个方法调用会创建一个栈帧。
4.4 本地方法栈(Native Method Stack)
- 作用:
- 为本地方法(Native Method)服务。
- 本地方法是用其他语言(如 C/C++)编写的方法。
- 特点:
- 线程私有。
- 与 Java 栈类似,但服务于本地方法。
4.5 程序计数器(Program Counter Register)
- 作用:
- 记录当前线程执行的字节码指令地址。
- 用于线程切换后恢复执行位置。
- 特点:
- 线程私有。
- 是唯一一个不会发生 OutOfMemoryError 的区域。