Java虚拟机(JVM)是Java生态的基石,它屏蔽了操作系统差异,让Java代码实现“一次编写,到处运行”。本文将深入剖析JVM的五大核心系统:类加载、执行引擎、运行时数据区、垃圾回收及本地接口,帮助你掌握其底层原理。
1️⃣ JVM整体架构与核心组件
JVM本质上是运行Java字节码的平台。操作系统无法直接识别.class文件,JVM通过加载字节码并翻译为机器指令来执行程序。其架构由以下五部分组成:
- 类加载系统:负责将字节码文件加载到内存,涉及类加载器、加载过程和双亲委派机制。
- 执行引擎系统:将字节码指令解释或编译为本地机器指令,包含解释器和JIT即时编译器。
- 运行时数据区:代码执行的内存区域,包括程序计数器、虚拟机栈、堆、元空间等。
- 垃圾回收系统:管理内存,自动回收无用对象,核心算法有标记-清除、复制、标记-整理。
- 本地接口和本地方法库:用于调用C/C++编写的本地方法,通过
native关键字标记。
这些组件相互配合,构成了完整的Java执行平台。了解它们的关系,是优化Java应用性能的前提。
2️⃣ 类加载系统深度剖析
类加载是将字节码文件转换为运行时数据结构的过程。它分为加载、连接(验证、准备、解析)和初始化三个阶段。加载阶段通过全限定名查找字节流,并在堆中创建Class对象;连接阶段验证字节码安全性、分配静态变量内存并解析符号引用;初始化阶段执行静态变量赋值和静态代码块。
JVM内置三种类加载器:
- BootstrapClassLoader:C++实现,加载
JAVA_HOME/lib核心类库。 - ExtClassLoader:加载
JAVA_HOME/lib/ext扩展库。 - AppClassLoader:加载classpath下的应用类。
用户可通过继承ClassLoader实现自定义加载器。四者之间的层级关系由源码决定:Bootstrap加载Ext,Ext加载App,App加载自定义加载器。
package sun.misc;
// ...
public class Launcher {
// ...
// 扩展类加载器
static class ExtClassLoader extends URLClassLoader {
// ...
}
// ...
}package sun.misc;
// ...
public class Launcher {
// ...
// 系统类加载器
static class AppClassLoader extends URLClassLoader {
// ...
}
// ...
}package sun.misc;
// ...
public class Launcher {
// ...
public Launcher() {
ExtClassLoader var1;
try {
// 先初始化并创建ExtClassLoader,将其父加载器设置为null,因为BootstrapClassLoader是C++编写的
var1 = Launcher.ExtClassLoader.getExtClassLoader();
} catch (IOException var10) {
throw new InternalError("Could not create extension class loader", var10);
}
try {
// 再创建AppClassLoader并将ExtClassLoader传递给AppClassLoader作为它的父加载器
this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
} catch (IOException var9) {
throw new InternalError("Could not create application class loader", var9);
}
// 将AppClassLoader设置为线程上下文类加载器
Thread.currentThread().setContextClassLoader(this.loader);
// ...
}
// ...
}package classload;
/**
* CustomizeClassLoader继承了ClassLoader,属于自定义类加载器
*/
public class CustomizeClassLoader extends ClassLoader {
public static void main(String[] args) {
CustomizeClassLoader customizeClassLoader = new CustomizeClassLoader();
printDetail("自定义类加载器", customizeClassLoader);
ClassLoader appClassLoader = customizeClassLoader.getParent();
printDetail("自定义类加载器的父加载器", appClassLoader);
printDetail("程序默认类加载器", ClassLoader.getSystemClassLoader());
printDetail("线程上下文类加载器", Thread.currentThread().getContextClassLoader());
ClassLoader extClassLoader = appClassLoader.getParent();
printDetail("AppClassLoader的父加载器", extClassLoader);
ClassLoader bootstrapClassLoader = extClassLoader.getParent();
printDetail("ExtClassLoader的父加载器", bootstrapClassLoader);
}
public static void printDetail(String log, Object classLoader) {
String result = String.format("%s:%s", log, classLoader);
System.out.println(result);
}
}自定义类加载器:classload.CustomizeClassLoader@17550481
自定义类加载器的父加载器:sun.misc.Launcher$AppClassLoader@3eb07fd3
程序默认类加载器:sun.misc.Launcher$AppClassLoader@3eb07fd3
线程上下文类加载器:sun.misc.Launcher$AppClassLoader@3eb07fd3
AppClassLoader的父加载器:sun.misc.Launcher$ExtClassLoader@edf4efb
ExtClassLoader的父加载器:null3️⃣ 双亲委派机制与打破实践
双亲委派机制是类加载的核心安全策略:从下至上检查类是否已加载,从上至下尝试加载类。即AppClassLoader收到加载请求后,先委派给ExtClassLoader,Ext再委派给Bootstrap。只有当父加载器无法加载时,子加载器才尝试自行加载。这避免了核心类重复加载,也防止了恶意篡改。
其实现逻辑在ClassLoader.loadClass()方法中:
package java.lang;
// ...
public abstract class ClassLoader {
// ...
protected Class loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
// 用全限定名先尝试从自己的命名空间中查找Class对象
Class c = findLoadedClass(name);
// 如果找到就不需要加载了,如果没找到就开始类加载
if (c == null) {
long t0 = System.nanoTime();
try {
// 先将类加载任务委托给自己的父加载器
if (parent != null) {
c = parent.loadClass(name, false);
} else {
// 如果父加载器为null,代表当前为ExtClassLoader
// 直接委托给BootstrapClassLoader
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
// 执行findClass方法
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
// 是否需要在加载时进行解析
if (resolve) {
resolveClass(c);
}
// 返回加载后生成的Class对象
return c;
}
}
// ...
}然而,打破双亲委派在某些场景下是必要的,例如Java SPI(服务提供者接口)。以DriverManager为例,它由Bootstrap加载,但需要加载classpath下的MySQL驱动实现类。此时,它通过线程上下文类加载器(通常是AppClassLoader)来加载,绕过了双亲委派。
package java.sql;
// ...
public class DriverManager {
// ...
/**
* Load the initial JDBC drivers by checking the System property
* jdbc.properties and then use the {@code ServiceLoader} mechanism
*/
static {
loadInitialDrivers();
println("JDBC DriverManager initialized");
}
private static void loadInitialDrivers() {
String drivers;
try {
drivers = AccessController.doPrivileged(new PrivilegedAction() {
public String run() {
return System.getProperty("jdbc.drivers");
}
});
} catch (Exception ex) {
drivers = null;
}
AccessController.doPrivileged(new PrivilegedAction() {
public Void run() {
// 通过ServiceLoader加载Driver的实现类(Java的SPI机制)
ServiceLoader loadedDrivers = ServiceLoader.load(Driver.class);
// ...
}
});
}
// ...
} package java.util;
// ...
public final class ServiceLoader
implements Iterable
{
private static final String PREFIX = "META-INF/services/";
// ...
// 接上文中的例子,这里传入的参数为Driver的Class对象
public static ServiceLoader load(Class service) {
// 获取线程上下文类加载器。默认为AppClassLoader
ClassLoader cl = Thread.currentThread().getContextClassLoader();
// 通过AppClassLoader加载Driver接口的实现类
return ServiceLoader.load(service, cl);
}
// ...
}

自定义类加载器示例:
package classloader; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.nio.file.Paths; /** * 自定义类加载器 */ public class DiyClassLoader extends ClassLoader { // .class文件存放的根目录 private final String rootPath; // 字节码文件后缀名 public static final String CLASS_SUFFIX = ".class"; // 指定根路径 public DiyClassLoader(String rootPath) { this.rootPath = rootPath; } /** * 重写ClassLoader的findClass方法 */ @Override protected Class findClass(String name) throws ClassNotFoundException { try { // 获取字节码文件的字节流数据 byte[] classData = getClassFileByteData(name); if (classData.length == 0) { throw new ClassNotFoundException("Failed to load class " + name); } // 这里调用defineClass方法,defineClass方法执行类的加载、验证、准备、解析等操作 // name已是全限定名 return defineClass(name, classData, 0, classData.length); } catch (Exception e) { throw new ClassNotFoundException("Failed to load class " + name, e); } } /** * @param fullClassName:类的完全限定名 * @return:class文件字节流数据 */ private byte[] getClassFileByteData(String fullClassName) throws Exception { // 转换为全路径 File targetFile = Paths.get(rootPath, (fullClassName.replace(".", "/") + CLASS_SUFFIX)).toFile(); // 返回字节流数据 try (FileInputStream fis = new FileInputStream(targetFile); ByteArrayOutputStream baos = new ByteArrayOutputStream()) { byte[] buffer = new byte[1024]; int len; while ((len = fis.read(buffer)) != -1) { baos.write(buffer, 0, len); } return baos.toByteArray(); } } }
package classloader; import java.io.IOException; import java.io.InputStream; import java.util.Properties; /** * 自定义类加载器工具类 */ public class DiyClassLoaderUtils { private DiyClassLoaderUtils() { } // 自定义的类加载器 public static final ClassLoader DIY_CLASS_LOADER; // 指定存放由自定义类加载器加载的类的根路径 public static final String ROOT_PATH; public static final String ROOT_PATH_KEY = "rootPath"; static { // 加载配置文件中指定的根路径 try (InputStream stream = ClassLoader.getSystemResourceAsStream("diyClassLoader.propertiies")) { Properties properties = new Properties(); properties.load(stream); // 根路径赋值 ROOT_PATH = properties.getProperty(ROOT_PATH_KEY); // 通过根路径来实例化自定义类加载器对象 DIY_CLASS_LOADER = new DiyClassLoader(ROOT_PATH); } catch (IOException e) { throw new RuntimeException(e); } } /** * @param fullClassName:全限定名 * @return:该类的Class对象 */ public static Class getClassForDiyClassLoader(String fullClassName) throws ClassNotFoundException { return DIY_CLASS_LOADER.loadClass(fullClassName); } }
rootPath=/Users/wangxinjie/IdeaProjects
package classloader; public class DiyClassLoaderTest { public static void main(String[] args) { printSuccess(); } private static void printSuccess(){ System.out.println("diy classLoader test success..."); } }

package classloader; import java.lang.reflect.Constructor; import java.lang.reflect.Method; public class MainTest { public static void main(String[] args) throws Exception { // 自定义类加载器 -> Class对象 Class cls = DiyClassLoaderUtils.getClassForDiyClassLoader("classloader.DiyClassLoaderTest"); System.out.println(cls.getClassLoader()); // 执行main方法 Constructor constructor = cls.getConstructor(); Object instance = constructor.newInstance(); Method main = cls.getDeclaredMethod("main", String[].class); main.invoke(instance, (Object) new String[]{}); } }
classloader.DiyClassLoader@b684286 diy classLoader test success...
4️⃣ 执行引擎:解释与编译的平衡艺术
执行引擎将字节码转换为本地机器指令。现代JVM(如HotSpot)采用解释器+JIT即时编译器混合模式。解释器快速启动,逐行翻译字节码;JIT则识别热点代码(如频繁调用的方法),将其编译为本地机器码,提升执行效率。
热点探测基于两种计数器:
方法调用计数器:统计方法调用次数(Server模式默认阈值10000次)。回边计数器:统计循环体执行次数。
达到阈值后,JIT后台编译,机器码存入CodeCache(元空间中的热点代码缓冲区)。对于小项目,可关闭热度衰减,让长时间运行的应用性能更优。
# 完全采用解释器模式执行程序 -Xint # 完全采用JIT即时编译器模式执行程序 -Xcomp # 采用解释器+JIT即时编译器混合模式共同执行程序(默认工作模式) -Xmixed
# 设置CodeCache的最大大小 -XX:ReservedCodeCacheSize=128m
# 设置计数器的阈值 -XX:CompileThreshold=5000
# 关闭热度衰减 -XX:-UseCounterDecay
5️⃣ 运行时内存区域详解
运行时数据区分为线程私有和线程共享两部分。线程私有区域包括:
- 程序计数器:记录当前线程执行的字节码地址,唯一不会OOM的区域。
- 虚拟机栈:每个方法对应一个栈帧,包含局部变量表、操作数栈等。默认大小1MB,可能抛出
StackOverflowError或OutOfMemoryError。 - 本地方法栈:执行native方法,使用操作系统计数器。
线程共享区域包括:
- 堆:存储对象实例和数组。JDK8默认垃圾收集器为Parallel Scavenge+Parallel Old,新生代(Eden+两个Survivor)与老年代比例1:2。JDK9起G1成为默认,堆被划分为多个Region区(Eden、Survivor、Old、Humongous)。
- 元空间:存放类元数据和运行时常量池,受本地内存限制,建议设置上限。
# 设置线程栈的大小
-Xss1m# 堆的起始内存大小
-Xms1g
# 堆的最大内存大小
-Xmx1g# 设置新生代和老年代空间占比,新生代占用堆空间的 1/(1+N)
-XX:NewRatio=N
# 设置Survivor区和Eden区空间占比(Eden:S0:S1 = N : 1 : 1)
-XX:SurvivorRatio=N# 设置每个区的空间大小(建议使用默认值,不去设置)
-XX:G1HeapRegionSize=4m
# 设置新生代初始占比(默认占堆内存空间的5%,这里设置的就是5%)
-XX:G1NewSizePercent=5
# 设置新生代最多占用堆空间的60%(默认就是60%)
-XX:G1MaxNewSizePercent=60# 设置元空间初始容量
-XX:MetaspaceSize=256m
# 设置元空间最大容量
-XX:MaxMetaspaceSize=256m6️⃣ 对象实例与内存分配
对象在内存中由对象头(MarkWord、KlassWord、数组长度)、实例数据和对齐填充组成。分配过程包括:类加载检测、尝试栈上分配(逃逸分析)、TLAB(线程本地分配缓冲区)分配、新生代或老年代分配。
堆内存溢出(OOM)常见原因:
- 堆空间OOM:对象过多或内存泄漏,例如无限创建对象。
- 元空间OOM:类加载过多或CGLib动态生成类。
package oom;
import java.util.ArrayList;
import java.util.List;
public class HeapOOM {
public static void main(String[] args) {
List
浙公网安备 33010602011771号