JVM核心知识点部分总结(快速入门)

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

第01章 JVM基础认知

1.1 核心面试题(入门必掌握)

  1. JVM是什么?JVM的内存区域分为哪些?

  2. 什么是OOM?什么是StackOverflowError?有哪些方法分析?

  3. JVM的常用参数调优你知道哪些?

  4. GC是什么?为什么需要GC?

  5. 什么是类加载器?

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)

执行引擎负责将字节码指令解释、编译为机器码指令,提交给操作系统执行,核心由解释器和即时编译器组成。

  1. 解释器:当Java字节码被加载到内存中时,解释器逐条解析和执行字节码指令,将每条指令转换为对应平台的本地机器指令。优点是可立即执行,无需等待编译;缺点是执行速度较慢。

  2. 即时编译器(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对象,作为方法区这个类的各种数据的访问入口。

结论:类加载为懒加载(按需加载),并非程序启动时一次性加载所有类。

阶段二:链接

  1. 验证:确保Class文件的字节流中包含的信息符合虚拟机要求,不会危害虚拟机安全(如校验字节码格式、语义合法性等)。

  2. 准备:

    1. 为类的静态变量分配内存,并设置该类变量的默认初始值(如int默认初始值为0,String默认初始值为null);

    2. 实例变量是在创建对象时完成赋值,且实例变量随着对象一起分配到Java堆中;

    3. final修饰的常量在编译时就分配值,准备阶段直接完成赋值,无需设置默认初始值,被所有线程、所有对象共享。

  3. 解析:将符号引用替换为直接引用。

    1. 符号引用:以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可;

    2. 直接引用:可以直接指向目标的指针,要求引用的目标已经在内存中存在。

阶段三:初始化

初始化阶段是执行类构造器<clinit>()的过程,核心目的是:根据程序员编码制定的逻辑,初始化类变量和其他资源。

2.4 类加载器的分类

类加载器分为四种,前三种为虚拟机自带的加载器,第四种为开发者自定义加载器,Java 9前后有小幅调整。

核心分类

  • 启动类加载器(BootstrapClassLoader):由C++实现,无对应的Java对象,打印时显示为null。

  • 扩展类加载器(ExtClassLoader)/平台类加载器(PlatformClassLoader):由Java实现,派生自ClassLoader类;Java 9后更名为PlatformClassLoader。

  • 应用程序类加载器(AppClassLoader):也叫系统类加载器,由Java实现,派生自ClassLoader类,是普通Java类的默认加载器。

  • 自定义加载器:程序员可定制类的加载方式,派生自ClassLoader类。

Java 9前后加载器差异

  1. Java 9之前:

    1. Bootstrap ClassLoader:加载$JAVA_HOME中jre/lib/rt.jar,即JDK中的核心类库;

    2. ExtClassLoader:加载相对次要、通用的类,主要包括$JAVA_HOME中jre/lib/*.jar或-Djava.ext.dirs指定目录下的jar包;

    3. AppClassLoader:加载-cp指定的类,即用户类路径中指定的jar包及目录中的class文件。

  2. Java 9及之后:

    1. Bootstrap ClassLoader:采用模块化设计,加载lib/modules启动时的基础模块类(如java.base、java.management、java.xml);

    2. ExtClassLoader更名为PlatformClassLoader:采用模块化设计,加载lib/modules中平台相关模块(如java.scripting、java.compiler);

    3. 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 双亲委派模型(核心,沙箱安全机制)

双亲委派模型是类加载器的核心加载规则,核心逻辑:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把请求委托给父加载器去完成,依次向上;若父加载器加载失败,再由自身尝试加载。

加载流程(两步走)

  1. 从下到上(委托阶段):

    1. 当AppClassLoader收到类加载请求时,先委托给父加载器PlatformClassLoader;

    2. 当PlatformClassLoader收到请求时,先委托给父加载器BootStrapClassLoader。

  2. 从上到下(加载阶段):

    1. 若BootStrapClassLoader加载失败,由PlatformClassLoader尝试加载;

    2. 若PlatformClassLoader加载失败,由AppClassLoader尝试加载;

    3. 若AppClassLoader也加载失败,抛出ClassNotFoundException异常。

核心目的

  1. 性能优化:避免同一个类被多个类加载器重复加载,节省内存;

  2. 安全防护(最重要):避免核心类被篡改,例如开发者自定义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实现不同,静态变量的存储位置也不同,核心差异如下:

  1. 早期JDK环境(JDK 6及以前): 方法区的实现为永久代(PermGen),也叫静态存储区,负责存储类元数据、常量、静态变量、方法信息等。其生命周期与JVM一致,大小可通过启动参数配置,静态变量存储在该区域。

  2. 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对虚拟机栈的操作只有两种:栈帧的压栈和出栈,遵循"先进后出"(后进先出)原则,具体流程如下:

  1. main方法被调用,生成栈帧1,压入栈底;

  2. main方法调用method2,生成栈帧2,压入栈中(位于栈帧1上方);

  3. method2调用method3,生成栈帧3,压入栈中;

  4. method3调用method4,生成栈帧4,压入栈顶;

  5. method4执行完毕,栈帧4出栈;

  6. method3执行完毕,栈帧3出栈;

  7. method2执行完毕,栈帧2出栈;

  8. 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。

问题辨析

  1. 垃圾回收是否涉及栈内存? 不涉及。因为栈内存在方法调用结束后,栈帧会自动出栈,内存被释放,无需GC参与。

  2. 方法内的局部变量是线程安全的吗? 当方法内局部变量没有逃离方法的作用范围时,线程安全。因为一个线程对应一个栈,每调用一个方法会生成一个新的栈帧,局部变量为线程私有;若变量是static(静态变量),则线程不安全,因为静态变量是线程共享的。

第05章 常见内存异常与GC基础

5.1 常见内存异常

  1. StackOverflowError(栈溢出):如第04章所述,核心原因是虚拟机栈内存不足,多由无限递归触发。

  2. OutOfMemoryError(OOM,内存溢出): 定义:JVM中某块内存区域被占满,无法为新的对象或操作分配内存,触发的异常。常见场景:堆内存OOM(最常见,如无限创建对象且未释放)、元空间OOM(类加载过多)、虚拟机栈OOM(线程创建过多)。

5.2 内存异常分析方法

  1. 开启堆转储:通过JVM参数 -XX:+HeapDumpOnOutOfMemoryError,设置OOM时自动生成堆转储文件(.hprof),记录堆内存对象分布;

  2. 工具分析:使用JDK自带工具(JVisualVM、jstat、jmap、jstack)或第三方工具(MAT、JProfiler),分析堆转储文件和运行时内存,定位问题;

  3. 日志分析:查看JVM运行日志和应用日志,定位异常触发的代码位置,判断异常原因(如递归无终止、对象未释放等)。

5.3 GC基础认知

  1. GC定义:Garbage Collection,即垃圾回收,是JVM的核心特性,负责自动识别内存中无用的对象(无任何引用指向的对象),并回收其占用的内存空间。

  2. 为什么需要GC? 若没有GC,开发者需手动分配和释放内存,极易出现内存泄漏(无用对象未释放)和内存溢出(内存被占满),GC大幅降低了内存管理成本,提升程序稳定性。

  3. 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基础,应对更复杂的面试和开发场景。

相关推荐
wuqingshun3141591 小时前
产生死锁的四个必要条件
java·jvm
Predestination王瀞潞2 小时前
3. JVM(Java Virtual Machine,Java 虚拟机):从核心架构到运行机制的全方位剖析
java·jvm·架构
2501_945423549 小时前
Django全栈开发入门:构建一个博客系统
jvm·数据库·python
2301_7938046914 小时前
更优雅的测试:Pytest框架入门
jvm·数据库·python
ole ' ola14 小时前
lambda表达式
java·前端·jvm
2301_8154829315 小时前
用Python实现自动化的Web测试(Selenium)
jvm·数据库·python
m0_7432974218 小时前
Python在金融科技(FinTech)中的应用
jvm·数据库·python
2401_8578652318 小时前
Python日志记录(Logging)最佳实践
jvm·数据库·python
星辰_mya19 小时前
Redlock 算法:是分布式锁的“圣杯”还是“鸡肋”
jvm·redis·分布式·面试·redlock