本文旨在梳理JVM(Java Virtual Machine)快速入门核心知识点,涵盖基础定义、核心结构、类加载机制、内存区域、内存异常及调优基础,适用于Java开发入门及面试备考,内容兼顾理论性与实用性,确保知识点连贯、逻辑清晰。
第01章 JVM基础认知

1.1 核心面试题(入门必掌握)
-
JVM是什么?JVM的内存区域分为哪些?
-
什么是OOM?什么是StackOverflowError?有哪些方法分析?
-
JVM的常用参数调优你知道哪些?
-
GC是什么?为什么需要GC?
-
什么是类加载器?
1.2 基础概念铺垫
-
线程:程序执行的过程,是操作系统调度的基本单位。
-
进程:正在运行的程序,是操作系统分配资源的基本单位。
1.3 JVM定义与位置
JVM:Java Virtual Machine,即Java虚拟机。
位置:JVM运行在操作系统之上,与硬件没有直接的交互,是连接Java代码与底层系统的桥梁。
1.4 常见JVM实现
-
JCP组织(Java Community Process 开放的国际组织):Hotspot虚拟机(Open JDK版),Sun于2006年开源,是目前使用最广泛的JVM。
-
Oracle:Hotspot虚拟机(Oracle JDK版),闭源,允许个人免费使用,商用需收费。
-
BEA:JRockit虚拟机,专注于高性能服务端应用。
-
IBM:J9虚拟机,兼顾跨平台性与运行性能。
-
阿里巴巴:Dragonwell JDK(龙井虚拟机),针对电商、物流、金融等高性能需求场景优化。
1.5 JVM的作用与特点
核心作用:加载并执行Java字节码文件(.class),具体包括:加载字节码文件、分配内存(运行时数据区)、运行程序。
核心特点:
-
一次编译,到处运行:Java代码编译为字节码后,可在任意安装JVM的平台运行,无需重新编译。
-
自动内存管理:无需开发者手动分配和释放内存,由JVM统一管理。
-
自动垃圾回收:自动识别并回收无用内存,减少内存泄漏问题。
1.6 JVM核心结构图(核心组件)
JVM由五大核心组件组成,各组件协同工作,完成字节码的加载与执行,具体组件及功能如下:
-
类加载器子系统:将字节码文件(.class)加载到内存中的方法区,仅负责加载,不负责判断类是否可运行。
-
运行时数据区:JVM的内存核心,分为5个区域,具体如下:
-
方法区:存储已被虚拟机加载的类的元数据信息(元空间),即字节码相关信息。
-
堆:存放对象实例,几乎所有的对象实例都在这里分配内存。
-
虚拟机栈(Java栈):描述Java方法执行的内存模型,每个方法被执行时会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口等信息。
-
本地方法栈:记录虚拟机当前使用到的native方法。
-
程序计数器:当前线程所执行的字节码的行号指示器,存储下一条要执行的指令地址。
-
-
本地方法接口:虚拟机使用到的native类型方法,负责调用操作系统类库(例如Thread类中有多个Native方法调用)。
-
执行引擎:包含解释器、即时编译器和垃圾收集器,负责执行加载到JVM中的字节码指令。
注意:
-
多线程共享方法区和堆;
-
Java栈、本地方法栈、程序计数器是每个线程私有的。
1.7 执行引擎(Execution Engine)
执行引擎负责将字节码指令解释、编译为机器码指令,提交给操作系统执行,核心由解释器和即时编译器组成。
-
解释器:当Java字节码被加载到内存中时,解释器逐条解析和执行字节码指令,将每条指令转换为对应平台的本地机器指令。优点是可立即执行,无需等待编译;缺点是执行速度较慢。
-
即时编译器(Just-In-Time Compiler,JIT Compiler):为提升执行速度而生,将字节码动态编译为本地机器码,直接在底层硬件上执行。会对经常执行的热点代码进行优化,并将优化后的代码缓存,供下次执行时直接使用。
补充:执行引擎还包括即时编译器后端、垃圾回收器、线程管理器等组件,共同保障Java程序的跨平台运行和高性能。
1.8 本地方法接口(Native Interface)
核心作用:融合不同编程语言为Java所用,在内存中专门开辟区域处理标记为native的代码,具体做法是在Native Method Stack中登记native方法,在Execution Engine执行时加载native libraries。
示例:hashCode()方法就是native方法;Thread类中也包含多个标记为native的方法。
1.9 本地方法栈(Native Method Stack)
存储从Java代码中调用本地方法时所需的信息,是线程私有的,与虚拟机栈功能类似,区别在于针对native方法。
1.10 PC寄存器(程序计数器)
每个线程都有一个程序计数器,是线程私有的,本质是一个指针,指向方法区中的方法字节码(存储下一条要执行的指令地址),由执行引擎读取下一条指令。其内存空间极小,几乎可以忽略不计。
第02章 类加载器(ClassLoader)
类加载器是JVM的核心组件之一,负责加载.class文件,其核心逻辑、加载流程和双亲委派模型是重点知识点。
2.1 类加载器的核心作用

-
负责加载class文件,class文件开头有特定的文件标识(CA FE BA BE,即"cafe babe")。
-
ClassLoader只负责class文件的加载,至于类是否可以运行,由Execution Engine决定。
-
加载的类信息存放到方法区的内存空间。
2.2 类加载示例代码
java
public class ClassLoaderDemo1 {
public static void main(String[] args) {
Car car = new Car();
Class<? extends Car> aClass = car.getClass(); // 由对象得到类
ClassLoader classLoader = aClass.getClassLoader(); // 由类得到类加载器
System.out.println(classLoader); // Car的类加载器是AppClassLoader
}
}
2.3 类的加载过程(核心)
类的加载过程主要分为三个步骤:加载、链接、初始化,其中链接过程又分为验证、准备、解析;加上卸载、使用两个步骤,统称为类的生命周期。
阶段一:加载
-
通过一个类的全限定名获取定义此类的二进制字节流;
-
将这个字节流代表的静态存储结构转为方法区运行时数据结构;
-
在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。
结论:类加载为懒加载(按需加载),并非程序启动时一次性加载所有类。
阶段二:链接
-
验证:确保Class文件的字节流中包含的信息符合虚拟机要求,不会危害虚拟机安全(如校验字节码格式、语义合法性等)。
-
准备:
-
为类的静态变量分配内存,并设置该类变量的默认初始值(如int默认初始值为0,String默认初始值为null);
-
实例变量是在创建对象时完成赋值,且实例变量随着对象一起分配到Java堆中;
-
final修饰的常量在编译时就分配值,准备阶段直接完成赋值,无需设置默认初始值,被所有线程、所有对象共享。
-
-
解析:将符号引用替换为直接引用。
-
符号引用:以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可;
-
直接引用:可以直接指向目标的指针,要求引用的目标已经在内存中存在。
-
阶段三:初始化
初始化阶段是执行类构造器<clinit>()的过程,核心目的是:根据程序员编码制定的逻辑,初始化类变量和其他资源。
2.4 类加载器的分类
类加载器分为四种,前三种为虚拟机自带的加载器,第四种为开发者自定义加载器,Java 9前后有小幅调整。
核心分类
-
启动类加载器(BootstrapClassLoader):由C++实现,无对应的Java对象,打印时显示为null。
-
扩展类加载器(ExtClassLoader)/平台类加载器(PlatformClassLoader):由Java实现,派生自ClassLoader类;Java 9后更名为PlatformClassLoader。
-
应用程序类加载器(AppClassLoader):也叫系统类加载器,由Java实现,派生自ClassLoader类,是普通Java类的默认加载器。
-
自定义加载器:程序员可定制类的加载方式,派生自ClassLoader类。
Java 9前后加载器差异
-
Java 9之前:
-
Bootstrap ClassLoader:加载$JAVA_HOME中jre/lib/rt.jar,即JDK中的核心类库;
-
ExtClassLoader:加载相对次要、通用的类,主要包括$JAVA_HOME中jre/lib/*.jar或-Djava.ext.dirs指定目录下的jar包;
-
AppClassLoader:加载-cp指定的类,即用户类路径中指定的jar包及目录中的class文件。
-
-
Java 9及之后:
-
Bootstrap ClassLoader:采用模块化设计,加载lib/modules启动时的基础模块类(如java.base、java.management、java.xml);
-
ExtClassLoader更名为PlatformClassLoader:采用模块化设计,加载lib/modules中平台相关模块(如java.scripting、java.compiler);
-
AppClassLoader:加载-cp、-mp指定的类,即用户类路径中指定的jar包及目录中的class文件。
-
类加载器层级关系示例代码
java
public class ClassLoaderDemo2 {
public static void main(String[] args) {
ClassLoaderDemo2 demo2 = new ClassLoaderDemo2();
// AppClassLoader(系统类加载器)
System.out.println(demo2.getClass().getClassLoader());
System.out.println(ClassLoader.getSystemClassLoader());
// PlatformClassLoader(平台类加载器)
System.out.println(demo2.getClass().getClassLoader().getParent());
// null(BootstrapClassLoader,C++实现,无Java对象)
System.out.println(demo2.getClass().getClassLoader().getParent().getParent());
// null(BootstrapClassLoader,String是核心类,由启动类加载器加载)
String s = new String();
System.out.println(s.getClass().getClassLoader());
}
}
注意:此处的父子关系并非代码中的extends继承关系,而是逻辑上的委派关系。
2.5 双亲委派模型(核心,沙箱安全机制)
双亲委派模型是类加载器的核心加载规则,核心逻辑:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把请求委托给父加载器去完成,依次向上;若父加载器加载失败,再由自身尝试加载。

加载流程(两步走)
-
从下到上(委托阶段):
-
当AppClassLoader收到类加载请求时,先委托给父加载器PlatformClassLoader;
-
当PlatformClassLoader收到请求时,先委托给父加载器BootStrapClassLoader。
-
-
从上到下(加载阶段):
-
若BootStrapClassLoader加载失败,由PlatformClassLoader尝试加载;
-
若PlatformClassLoader加载失败,由AppClassLoader尝试加载;
-
若AppClassLoader也加载失败,抛出ClassNotFoundException异常。
-
核心目的
-
性能优化:避免同一个类被多个类加载器重复加载,节省内存;
-
安全防护(最重要):避免核心类被篡改,例如开发者自定义java.lang.String类,由于双亲委派模型,会优先加载BootstrapClassLoader中的核心String类,防止恶意类替代核心类。
第03章 方法区(Method Area)
方法区是JVM规范中定义的内存区域,被所有线程共享,其实现方式在不同JDK版本中有显著演进,是JVM内存知识点的重点。
3.1 方法区存储内容

根据《深入理解Java虚拟机》书中描述,方法区用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码缓存等内容。
3.2 方法区演进细节(Hotspot虚拟机)
方法区的实现方式随JDK版本迭代不断优化,核心演进重点是"永久代"到"元空间"的转变,具体如下:
-
JDK 7开始:静态变量和字符串常量池从方法区迁移到堆中(JDK 7具体存储位置存在少量歧义,但主流观点认为已迁移);
-
JDK 8及以后:彻底移除永久代,用元空间(Metaspace)替代方法区的实现,元空间使用本地内存,而非JVM堆内存。
3.3 关键概念补充:静态变量
静态变量(类变量):在类定义中使用static关键字声明的变量,存储在指定内存区域,生命周期与应用程序一致。所有类实例共享同一个静态变量,而非每个实例拥有独立副本,所有实例均可访问和修改静态变量。
3.4 不同JDK版本静态变量存储位置原理
不同JDK版本的JVM实现不同,静态变量的存储位置也不同,核心差异如下:
-
早期JDK环境(JDK 6及以前): 方法区的实现为永久代(PermGen),也叫静态存储区,负责存储类元数据、常量、静态变量、方法信息等。其生命周期与JVM一致,大小可通过启动参数配置,静态变量存储在该区域。
-
JDK 8及以后版本: 永久代被元空间(Metaspace)替代,元空间存储类元数据和JIT编译后的代码,使用本地内存;静态变量则存储在堆中(因JDK 7已完成静态变量向堆的迁移)。
3.5 核心辨析
-
方法区:JVM规范中定义的内存区域,用于存储类元数据、方法字节码等,是抽象概念;
-
永久代:Hotspot虚拟机在JDK 8之前对方法区的具体实现,属于JVM堆内存的一部分;
-
元空间:Hotspot虚拟机在JDK 8及以后对方法区的具体实现,使用本地内存,避免了永久代易出现的内存溢出问题。
第04章 虚拟机栈(Stack)
虚拟机栈是线程私有的内存区域,主管Java方法的执行,其运行原理和栈溢出问题是日常开发和面试的高频考点。
4.1 虚拟机栈的定义与特点
-
栈(栈内存):主管Java程序的运行,线程创建时随之创建,生命周期与线程一致,线程结束时栈内存释放,是线程私有的。
-
栈帧(Stack Frame):线程上正在执行的每个方法,都对应一个栈帧,是栈内存的基本单位。
4.2 栈运行原理(示例代码)
java
public class StackDemo {
public static void main(String[] args) {
System.out.println("main()开始");
StackDemo test = new StackDemo();
test.method2();
System.out.println("main()结束");
}
public void method2(){
System.out.println("method2()执行...");
this.method3();
System.out.println("method2()结束...");
}
public void method3() {
System.out.println("method3()执行...");
this.method4();
System.out.println("method3()结束...");
}
public void method4() {
System.out.println("method4()执行...");
System.out.println("method4()结束...");
}
}
运行流程解析
JVM对虚拟机栈的操作只有两种:栈帧的压栈和出栈,遵循"先进后出"(后进先出)原则,具体流程如下:
-
main方法被调用,生成栈帧1,压入栈底;
-
main方法调用method2,生成栈帧2,压入栈中(位于栈帧1上方);
-
method2调用method3,生成栈帧3,压入栈中;
-
method3调用method4,生成栈帧4,压入栈顶;
-
method4执行完毕,栈帧4出栈;
-
method3执行完毕,栈帧3出栈;
-
method2执行完毕,栈帧2出栈;
-
main方法执行完毕,栈帧1出栈,线程结束,栈内存释放。
补充:一个线程中只能有一个正在执行的方法(当前方法),对应一个活动的当前栈帧。
4.3 栈存储内容:栈帧的组成
栈中的数据均以栈帧(Stack Frame)的形式存在,栈帧是一个内存区块,包含方法执行过程中的各种数据信息,核心组成部分为局部变量表。
3.1 局部变量表(Local Variables)
又称本地变量表,核心作用:存储方法参数和方法体内的局部变量,包括8种基本类型变量、对象引用(reference)。
示例代码与查看方式
java
public class LocalVariableTableDemo {
public static void main(String[] args) {
int i = 100;
String s = "hello";
char c = 'c';
Date date = new Date();
}
}
查看局部变量表的方式:使用JDK自带的javap命令,在类路径下执行 javap -v 类名.class,可查看字节码中的局部变量表。
注意:通过javap命令看到的是加载到方法区的字节码中的局部变量表;程序运行时,这些局部变量会被动态加载到栈帧的局部变量表中。
4.4 常见问题:栈溢出(StackOverflowError)
示例代码(栈溢出测试)
java
package com.atguigu.demo;
/**
* @author: atguigu
* @create: 2022-11-10 17:33
* 未设置栈大小默认:11416次
* 设置VM参数:-Xss256k 2475次
*/
public class StackOOMDemo {
public static int count = 1;
public static void main(String[] args) {
System.out.println(count);
count++;
main(args); // 无限递归调用,触发栈溢出
}
}
核心要点
-
触发场景:最常见于无限递归调用,方法不断压栈,导致虚拟机栈超出最大内存限制;也可能是-Xss参数设置的栈空间过小。
-
异常信息:Exception in thread "main" java.lang.StackOverflowError。
问题辨析
-
垃圾回收是否涉及栈内存? 不涉及。因为栈内存在方法调用结束后,栈帧会自动出栈,内存被释放,无需GC参与。
-
方法内的局部变量是线程安全的吗? 当方法内局部变量没有逃离方法的作用范围时,线程安全。因为一个线程对应一个栈,每调用一个方法会生成一个新的栈帧,局部变量为线程私有;若变量是static(静态变量),则线程不安全,因为静态变量是线程共享的。
第05章 常见内存异常与GC基础
5.1 常见内存异常
-
StackOverflowError(栈溢出):如第04章所述,核心原因是虚拟机栈内存不足,多由无限递归触发。
-
OutOfMemoryError(OOM,内存溢出): 定义:JVM中某块内存区域被占满,无法为新的对象或操作分配内存,触发的异常。常见场景:堆内存OOM(最常见,如无限创建对象且未释放)、元空间OOM(类加载过多)、虚拟机栈OOM(线程创建过多)。
5.2 内存异常分析方法
-
开启堆转储:通过JVM参数
-XX:+HeapDumpOnOutOfMemoryError,设置OOM时自动生成堆转储文件(.hprof),记录堆内存对象分布; -
工具分析:使用JDK自带工具(JVisualVM、jstat、jmap、jstack)或第三方工具(MAT、JProfiler),分析堆转储文件和运行时内存,定位问题;
-
日志分析:查看JVM运行日志和应用日志,定位异常触发的代码位置,判断异常原因(如递归无终止、对象未释放等)。
5.3 GC基础认知
-
GC定义:Garbage Collection,即垃圾回收,是JVM的核心特性,负责自动识别内存中无用的对象(无任何引用指向的对象),并回收其占用的内存空间。
-
为什么需要GC? 若没有GC,开发者需手动分配和释放内存,极易出现内存泄漏(无用对象未释放)和内存溢出(内存被占满),GC大幅降低了内存管理成本,提升程序稳定性。
-
GC作用范围:主要针对堆内存(对象实例存储区),方法区的元数据也会进行GC(如卸载无用类);虚拟机栈、程序计数器、本地方法栈不参与GC。
第06章 JVM常用参数调优(入门级)
JVM调优的核心是通过参数调整内存区域大小、GC策略等,解决内存异常和性能瓶颈,以下是入门级常用核心参数:
6.1 堆内存配置(核心)
-
-Xms:堆的初始内存大小,建议与-Xmx设置为相同值,避免JVM频繁调整堆内存;
-
-Xmx:堆的最大内存大小,示例:
-Xms2G -Xmx2G(堆初始和最大内存均为2G); -
-Xmn:新生代内存大小,新生代越大,老年代越小,需根据业务场景调整(如高并发短生命周期对象适合增大新生代)。
6.2 虚拟机栈配置
- -Xss:设置每个线程的虚拟机栈大小,示例:
-Xss256k;默认值因JDK版本和系统不同而异,过小易触发StackOverflowError。
6.3 元空间配置(JDK 8+)
-
-XX:MetaspaceSize:元空间初始内存大小,触发元空间GC的阈值;
-
-XX:MaxMetaspaceSize:元空间最大内存大小,默认无上限,建议手动设置(如
-XX:MaxMetaspaceSize=512m),避免占用过多本地内存。
6.4 调试与排查参数
-
-XX:+HeapDumpOnOutOfMemoryError:OOM时自动生成堆转储文件;
-
-XX:HeapDumpPath:指定堆转储文件的存储路径;
-
-XX:+PrintGC:打印GC日志,查看GC触发频率和回收效果;
-
-XX:+DisableExplicitGC:禁止通过System.gc()手动触发Full GC,避免性能波动。
总结
本文档梳理了JVM快速入门的核心知识点,涵盖JVM基础认知、类加载机制、方法区、虚拟机栈、内存异常、GC基础及入门级调优参数,核心重点如下:
-
JVM是Java跨平台的核心,五大组件协同完成字节码的加载与执行;
-
运行时数据区的"共享/私有"特性、方法区的演进的是内存理解的关键;
-
类加载的双亲委派模型是Java安全的核心,类加载三步流程(加载、链接、初始化)是基础;
-
StackOverflowError和OOM是常见内存异常,需掌握触发原因和分析方法;
-
入门级调优主要围绕内存区域大小配置,结合GC监控和日志分析解决问题。
后续可深入学习GC算法、GC收集器、高级调优等内容,进一步夯实JVM基础,应对更复杂的面试和开发场景。