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的父加载器:null

3️⃣ 双亲委派机制与打破实践

双亲委派机制是类加载的核心安全策略:从下至上检查类是否已加载,从上至下尝试加载类。即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

[AFFILIATE_SLOT_1]

5️⃣ 运行时内存区域详解

运行时数据区分为线程私有线程共享两部分。线程私有区域包括:

  • 程序计数器:记录当前线程执行的字节码地址,唯一不会OOM的区域。
  • 虚拟机栈:每个方法对应一个栈帧,包含局部变量表、操作数栈等。默认大小1MB,可能抛出StackOverflowErrorOutOfMemoryError
  • 本地方法栈:执行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=256m

6️⃣ 对象实例与内存分配

对象在内存中由对象头(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 container = new ArrayList<>();
        while (true) {
            container.add(new TestOOMObject());
        }
    }
    private static class TestOOMObject {
        private byte[] data = new byte[512];
    }
}

# 设置初始堆内存10M,最大堆内存10M
-Xms10m -Xmx10m

package oom;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
public class MetaSpaceOOM {
    public static void main(String[] args) {
        while (true) {
            // 不断创建字节码信息
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(MetaSpaceOOMObject.class);
            enhancer.setUseCache(false);
            enhancer.setCallback((MethodInterceptor)
                    (o, method, objects, methodProxy)
                            -> methodProxy.invokeSuper(o, args));
            enhancer.create();
        }
    }
    public static class MetaSpaceOOMObject {
    }
}

# 设置元空间初始容量和最大容量都为10M
-XX:MetaspaceSize=10m -XX:MaxMetaspaceSize=10m

# 开启指针压缩(默认开启)
-XX:+UseCompressedOops

package oom;
public class ObjectInstance {
    // 4个字节
    private int a;
    // 8个字节
    private long b;
    // 4个字节(开启指针压缩)
    private Object obj;
}


    org.openjdk.jol
    jol-core
    0.9

[AFFILIATE_SLOT_2]

7️⃣ 垃圾回收:判定与算法

垃圾回收(GC)自动管理内存,核心是判定哪些对象是“垃圾”。常用算法包括:

  • 引用计数法:简单但无法解决循环引用。
  • 可达性分析:从GC Roots(如虚拟机栈引用、静态变量)向下搜索,不可达对象即为垃圾。需STW(Stop-The-World)保证准确性。
  • 三色标记算法:解决并发标记中的对象漏标问题。

垃圾回收算法:

  • 标记-清除:产生内存碎片。
  • 复制算法:用于新生代,将存活对象复制到另一块区域。
  • 标记-整理:用于老年代,整理存活对象消除碎片。

GC类型包括Young GC、Old GC、Mixed GC和Full GC。常见收集器有Serial、ParNew、Parallel Scavenge、CMS、G1等。CMS以低延迟为目标,分为初始标记、并发标记、重新标记、并发清除四个阶段。

package oom;
import org.openjdk.jol.info.ClassLayout;
public class WatchObj {
    public static void main(String[] args) {
        Object o = new Object();
        System.out.println(ClassLayout.parseInstance(o).toPrintable());
    }
}

package oom;
import org.openjdk.jol.info.ClassLayout;
public class WatchObj {
    public static void main(String[] args) {
        // 4*9 + 8 + 4 + 4 = 52 + 4 = 56
        int[] arrayObj = new int[9];
        System.out.println(ClassLayout.parseInstance(arrayObj).toPrintable());
    }
}

package oom;
import org.openjdk.jol.info.ClassLayout;
public class WatchObj {
    public static void main(String[] args) {
        // 8 + 4 + 4 + 8 + 4 + 4 + 4 + 4 = 40
        CustomerObject customerObject = new CustomerObject();
        System.out.println(ClassLayout.parseInstance(customerObject).toPrintable());
    }
    private static class CustomerObject {
        private int a;
        private long b;
        private int[] c;
        private long[] d = new long[7];
        private Object e;
    }
}

# 开启TLAB分配(默认开启)
-XX:+UseTLAB
# 设置TLAB区和Eden区的占比(默认N=1,即TLAB区占用Eden区的1%)
-XX:TLABWasteTargetPercent=N

# 设置大对象的阈值(适用于ParNew垃圾收集器)
-XX:PretenureSizeThreshold=1m

# 启用Serial垃圾收集器(开启该参数后,老年代默认使用Serial Old收集器)
-XX:+UseSerialGC

# 启用ParNew收集器
-XX:+UseParNewGC
# 需要多核CPU的支持,默认会根据CPU的核数开启不同的GC线程数,以达到最优的垃圾收集效果
# 指定GC线程数
-XX:ParallelGCThreads=N

# 启用Parallel Scavenge收集器
-XX:+UseParallelGC
# 开启自适应调节策略(默认开启)
-XX:+UseAdaptiveSizePolicy
# 期望的最大GC停顿时间(这里设置的是50ms)
-XX:MaxGCPauseMillis=50
# 期望的吞吐量,设置GC时间占用总执行时间的占比(这里设置的是期望GC时间不超过2%)
-XX:GCTimeRatio=49

# 开启Serial Old收集器(和Serial相同)
-XX:+UseSerialGC

# 启用Parallel Old收集器
-XX:+UseParallelOldGC

# 启用CMS收集器
-XX:+UseConcMarkSweepGC

总结

JVM是一个精密的运行平台,涵盖类加载、执行引擎、内存管理和垃圾回收。理解双亲委派机制能保障类安全,掌握JIT编译和GC算法能优化性能。无论是JavaPython还是Go语言,底层原理的融会贯通都能让你写出更高效的代码。希望本文能帮助你构建JVM知识体系,从容应对开发挑战!✅