JVM(Java 虚拟机)

Java语言的解释性和编译性(通过JVM 的执行引擎)

 Java 代码(.java 文件)要先使用 javac 编译器编译为 .class 文件(字节码),紧接着再通过JVM 的执行引擎(Execution Engine) 负责处理 Java 字节码并执行,它的主要组成部分包括:

  • 解释器(Interpreter):逐行解释字节码执行,启动快但执行速度较慢。
  • JIT 编译器(Just-In-Time Compiler):将热点字节码编译为本地机器码,提高执行效率。
  • 垃圾回收器(Garbage Collector, GC):管理 Java 堆中的对象回收,提升内存管理效率。

(1)Java 代码先编译成字节码

  • Java 代码(.java 文件)使用 javac 编译器编译为 .class 文件(字节码)。
  • 这种字节码与平台无关,可以在不同的 JVM 上运行。

(2)JVM 先解释执行,再逐步编译为机器码

  • 当 JVM 启动 Java 应用程序时,解释器(Interpreter)逐条解析字节码并执行,优点是启动速度快,但缺点是运行速度慢。
  • JVM 会分析热点代码(执行次数较多的代码),并使用 JIT(即时编译器) 将热点代码直接编译成机器码,提高性能。

(3)JIT 编译器优化热点代码

  • JIT 编译器(如 C1、C2 编译器)会在代码执行时,将热点字节码转换为本地机器码,提升执行速度。
  • 解释执行 + JIT 编译 的混合模式确保 Java 既有较快的启动速度,又能提升长时间运行的性能

总结:
解释器负责启动时快速执行,JIT 编译器负责优化热点代码。 这就是 Java 既有解释语言的灵活性,又有编译语言的高效性的原因。

1. JVM 运行时数据区(Runtime Data Areas)

1.1. 程序计数器(Program Counter Register)

概述

程序计数器(PC Register)是 JVM 中一个小型的内存区域,它的作用是记录当前线程正在执行的字节码指令地址。

特点

  • 线程私有(每个线程都有一个独立的 PC 寄存器)。
  • 存储字节码指令地址,用于指示当前线程下一条要执行的指令。
  • 执行 Java 方法时,PC 计数器存储正在执行的字节码指令地址。
  • 执行本地方法(Native Method)时,PC 计数器值 undefined(未定义)。
  • 该区域是 JVM 唯一不会发生 OutOfMemoryError 的区域

作用

  • 记录当前线程的执行位置(类似于 CPU 的 PC 寄存器)。
  • 线程切换时,保证线程恢复后能继续执行正确的指令。
  • 实现 Java 代码的多线程并发(JVM 通过线程轮流切换,每个线程都有自己的 PC 寄存器)。

1.2. Java 虚拟机栈(JVM Stack)

概述

Java 虚拟机栈(JVM Stack)用于存储 Java 方法执行时的栈帧(Stack Frame),是 Java 方法执行的基础。

特点

  • 线程私有,每个线程有独立的栈。
  • 栈的大小可以通过 -Xss 参数设置(例如 -Xss1M 设置栈大小为 1MB)。
  • 栈的生命周期与线程相同,线程结束时栈也随之销毁。

栈帧(Stack Frame)的组成

每个栈帧对应一个正在执行的方法,包含:

  • 局部变量表(Local Variable Table)

    • 存放方法中的基本数据类型(int、long、float、double 等)对象引用(reference)
    • 局部变量表的大小在编译期确定,运行时不会改变。
    • longdouble 类型占 两个 存储单元,其它数据类型占 一个 存储单元。
  • 操作数栈(Operand Stack)

    • 作为字节码指令的操作数临时存储区,用于方法调用时传递参数、计算临时结果等。
    • JVM 指令基于栈操作(如 iadd 指令会从操作数栈中弹出两个整数相加后再压入栈中)。
  • 动态链接(Dynamic Linking)

    • 指向方法区中的运行时常量池,用于解析方法引用(即方法调用时如何找到方法地址)。
  • 方法返回地址(Return Address)

    • 记录方法执行完毕后,返回到调用者的位置,以便继续执行。

栈的空间大小

  • 栈的大小由 -Xss 参数控制,通常 默认 1MB,可根据需求调整:
    java -Xss512k MyApplication
    
    • 每个线程的栈大小 512KB
    • 栈过大可能导致 StackOverflowError(递归调用过深)。

为什么栈是从高地址向低地址增长?

  • LIFO 机制(后进先出)

    • 栈用于存储方法调用信息,每次调用新方法时,会创建一个栈帧(Stack Frame),压入栈顶
    • 方法执行完后,栈帧被弹出,栈顶回到上一个方法的栈帧。
  • CPU 设计 & 指针运算优化

    • 许多计算机体系结构(如 x86、ARM)都使用“向下增长”的栈
    • 这允许 ESP(栈指针寄存器)直接递减来分配新的栈帧,提高性能。

可能出现的异常

  • StackOverflowError:递归过深导致栈空间耗尽。
  • OutOfMemoryError:JVM 栈的大小动态扩展失败(一般在线程数量过多时)。

1.3. 本地方法栈(Native Method Stack)

概述

本地方法栈(Native Method Stack)与 JVM 栈类似,但它用于存储 Native 方法的执行信息

特点

  • 线程私有,生命周期与线程相同。
  • 主要用于 JNI(Java Native Interface)调用 C/C++ 代码。
  • 可能出现:
    • StackOverflowError
    • OutOfMemoryError

作用

  • 维护 Native 方法调用时的参数、返回地址、Native 方法执行环境等。
  • 例如,调用 System.arraycopy() 方法时,JVM 需要通过本地方法栈进入 C 代码执行内存拷贝。

1.4. 堆(Heap)

概述

堆(Heap)是 JVM 内存中最大的区域,用于存储所有对象实例

特点

  • 线程共享(所有线程都能访问)。
  • GC(垃圾回收器)管理的区域。
  • 堆的大小可通过 -Xms(初始大小)和 -Xmx(最大大小)参数控制。
  • 可能抛出 OutOfMemoryError: Java heap space

堆的分代

  • 新生代(Young Generation)
    • Eden 区(对象最初分配在这里)。
    • Survivor 0(S0)、Survivor 1(S1)(少量存活对象在两者之间交替存储)。
  • 老年代(Old Generation)
    • 长期存活对象存放这里,GC 频率较低。

垃圾回收

  • Minor GC(新生代 GC):采用复制算法(对象生命周期短,适合快速回收)清理 Eden 和 Survivor 区。
  • Major GC / Full GC:清理整个堆,采用标记-整理算法(对象生命周期长)清理老年代。
  •  Major GC主要针对老年代进行清理,Full GC对整个堆内存(包括年轻代、老年代以及元空间)进行回收。

堆的空间大小

  • 堆的大小可以通过 JVM 参数设置

    • -Xms堆的初始大小(默认通常是 1/64 物理内存)
    • -Xmx堆的最大大小(默认通常是 1/4 物理内存)
    • 例如:
      java -Xms256m -Xmx1024m MyApplication
      
      • 最小堆内存 256MB
      • 最大堆内存 1024MB
  • 默认情况下,堆的大小会随着 GC 调整,但不能超过 -Xmx 设定的上限。

为什么堆是从低地址向高地址增长?

  • 堆是动态分配的,大小不固定

    • JVM 通过 -Xms(最小堆)和 -Xmx(最大堆)设置堆的大小。
    • 由于对象的创建是动态的,JVM 需要扩展堆的大小,通常向高地址扩展。
  • 操作系统内存管理

    • 在 C 语言的 malloc()JVMnew 语义中,分配的堆空间通常从低地址向高地址增长
    • 堆的增长方向使得堆的可扩展性更好,能动态调整大小。

1.5. 方法区(Method Area,又称元空间 Metaspace)

概述

存储类的元数据(方法信息、静态变量、运行时常量池)。关于类的信息存储在这里。

JDK 7 及以前

  • 方法区是堆中的永久代(PermGen)
  • -XX:PermSize-XX:MaxPermSize 限制。

JDK 8 及以后

  • 方法区改为单独的元空间(Metaspace)
  • 使用本地内存,不再受堆大小限制。
  • -XX:MetaspaceSize 控制其大小。

1.6 运行时常量池(在方法区中)字符串常量池(在堆中) 

常量池类型存储位置(JDK 6 及以前)存储位置(JDK 7+)存储内容作用
类文件常量池(Class File Constant Pool).class 文件.class 文件字面量(数值、字符串)、符号引用编译时生成,在运行时加载到运行时常量池
运行时常量池(Runtime Constant Pool)方法区(永久代 PermGen)方法区(元空间 Metaspace)从类文件加载的常量(字面量、符号引用)、运行时生成的常量(String.intern()动态解析符号引用、存储运行时常量
字符串常量池(String Pool)方法区(永久代 PermGen)堆(Heap)String 字面量、intern() 方法存入的字符串优化字符串存储,减少内存占用
常量类型说明
字面量编译时生成的常量,如 final 修饰的常量、字符串字面量、数值(int、float、double、long)等。
符号引用类名、字段名、方法名的符号引用(未解析为具体地址),用于支持动态链接。
方法引用方法的符号引用,如方法的名称、描述符等。

1.7 总结

内存区域线程私有/共享主要作用可能抛出的异常
程序计数器线程私有记录当前线程执行的字节码地址
JVM 栈线程私有存储局部变量表、操作数栈StackOverflowErrorOutOfMemoryError
本地方法栈线程私有执行 Native 方法StackOverflowErrorOutOfMemoryError
线程共享存储对象实例OutOfMemoryError: Java heap space
方法区(元空间)线程共享存储类信息、静态变量OutOfMemoryError: Metaspace
直接内存线程共享用于高效 I/O(如 NIO)OutOfMemoryError: Direct Buffer Memory

2. 类加载机制

2.1 类加载器

(1)启动类加载器(Bootstrap ClassLoader)

  • 负责加载JDK 核心类库rt.jarcharsets.jar 等)。
  • C++ 代码实现,无法直接获取其实例(即 null)。
  • 只能加载JDK 自带的核心类库,无法加载用户定义的 .class 文件。

主要加载的类包括:

  • java.lang.*(如 StringIntegerSystem
  • java.util.*(如 ArrayListHashMap
  • java.io.*(如 FileInputStream
  • java.nio.*java.net.*

(2)扩展类加载器(ExtClassLoader)

  • 负责加载 lib/ext/ 目录下的扩展类库(如 javax.crypto.*)。
  • Java 代码实现,可通过 ClassLoader.getSystemClassLoader().getParent() 获取。
  • JDK 9 以后,扩展类加载器被移除,改为平台类加载器(PlatformClassLoader)

主要加载的类包括:

  • javax.crypto.*(加密库)
  • javax.sound.*(声音处理库)
  • javax.imageio.*(图像处理库)

(3)应用类加载器(AppClassLoader/ SystemClassLoader)

  • 默认的类加载器:如果你没有手动指定类加载器,默认由它加载。
  • 可以通过 ClassLoader.getSystemClassLoader() 获取到它
  • 支持动态加载 JAR 包:当你添加 JAR 依赖(如 Spring Boot 依赖的 JAR),它会动态加载这些类。

 主要加载的类包括:

  • com.example.MyClass
  • org.springframework.*
  • 任何放在 classpath 下的 .class 文件

(4)自定义类加载器 

  • 默认类加载器仅加载 classpath 下的类,如果需要从网络、数据库、加密文件中加载 .class 文件,必须使用自定义类加载器。 
  • 默认的 AppClassLoader 共享 classpath,如果多个模块的类存在相同的包名,可能会发生类冲突
  • 防止 Java 反编译:Java .class 文件容易被反编译,我们可以加密 .class 文件,并使用自定义类加载器在运行时解密。

如何自定义类加载器?

  • 方式 1:继承 ClassLoader。Java 提供了 ClassLoader 抽象类,允许我们创建自己的类加载器。

  • 方式 2:继承 URLClassLoader。如果 .class 文件存放在 JAR 或远程服务器上,我们可以继承 URLClassLoader 来动态加载类。

2.2 双亲委派机制(Parent Delegation Model)

1. 什么是双亲委派机制?

双亲委派机制 是指 类加载器在加载一个类时,先委托其父类加载器加载,只有当父类加载器无法加载该类时,子类加载器才会尝试自己加载

2. 双亲委派机制的工作流程

  1. 当一个 ClassLoader 需要加载一个类时,它不会自己直接加载,而是先委托给父类加载器
  2. 父类加载器递归向上委托,最终到 Bootstrap ClassLoader(顶层)。
  3. 如果 Bootstrap ClassLoader 无法加载该类(即不是核心类库),那么父类加载器会逐层返回,直到应用类加载器(AppClassLoader)。
  4. 如果所有的父类加载器都无法加载该类,那么当前类加载器才会自己尝试加载

3. 为什么要使用双亲委派机制?

保证 Java 运行时的安全性

  • 避免核心 API 被篡改java.lang.String 始终由 Bootstrap ClassLoader 加载)。
  • 防止类的重复加载类冲突

提高类加载的效率

  • 先尝试加载已经加载过的类,避免重复加载。

2.3 类加载过程

类加载的五个阶段

阶段说明
加载(Loading)读取 .class 文件,将字节码转换为 Class 对象。
验证(Verification)检查字节码是否合法,防止恶意代码执行。
准备(Preparation)分配静态变量的内存,初始化默认值(不赋具体值)。
解析(Resolution)将符号引用转换为直接引用(方法地址、变量地址)。
初始化(Initialization)执行 static 代码块,赋值静态变量。

2.3.1. 加载(Loading)

步骤:

  • 读取 .class 文件(从硬盘、网络、JAR 包等加载类的字节码)。
  • 转换字节码为 JVM 识别的 Class 对象,存入方法区。
  • 在堆(Heap)中创建 Class 对象,表示该类的运行时信息。

示例

Class<?> clazz = Class.forName("java.lang.String");
  • Class.forName() 方法会触发类加载

2.3.2. 连接(Linking)

(1)验证(Verification)

  • 检查 .class 文件格式是否正确,防止恶意代码(字节码验证)。
  • 例如,检查字节码指令是否合法,是否会破坏 JVM 运行。

(2)准备(Preparation)

  • 为类的静态变量 分配内存,并设置默认值(如 int 变量默认值为 0)。
  • 例如:
public static int a = 10;  // 在准备阶段 a=0,在初始化阶段才变成10

(3)解析(Resolution)

  • 符号引用(如 java.lang.String)转换为直接引用(JVM 内存地址)。

2.3.3. 初始化(Initialization)

  • 执行 <clinit>() 静态代码块,初始化静态变量(准备阶段是为静态变量赋默认值,而这里是要设置你所定义的值)。
  • 只有第一次使用该类时才执行初始化,确保类只初始化一次。

示例

class Example {
    static {
        System.out.println("Static block executed");
    }

    public static int value = 10;
}

public class Test {
    public static void main(String[] args) {
        System.out.println(Example.value);
    }
}

3. Java 对象的创建过程

当 Java 代码执行 new 关键字创建对象时,JVM 需要完成以下步骤:

步骤 1:检查类是否已被加载

  • JVM 先检查目标类的元信息是否已加载到方法区(元空间 Metaspace)
    • 如果 类未加载,JVM 先通过 类加载器(ClassLoader) 加载 .class 文件,并完成 类加载、验证、准备、解析、初始化 过程(即 类的五个生命周期阶段)。
    • 如果 类已加载,跳过此步骤。

步骤 2:为对象分配内存

JVM 在堆(Heap)中为新对象分配内存,分配策略取决于 内存是否连续

  • 指针碰撞(Bump the Pointer)(内存连续时):

    • 堆内存按顺序分配,JVM 仅需将指针移动到新的可用地址
    • 适用于 使用 GC 压缩后 的堆。
  • 空闲列表(Free List)(内存不连续时):

    • 维护空闲内存块列表,找到合适的内存块进行分配。
    • 适用于 堆内存碎片较多 的情况。
  • 线程私有分配缓冲区(TLAB, Thread Local Allocation Buffer)

    • JVM 允许每个线程在堆中新建私有缓存区域,提高对象分配效率,减少同步锁竞争。

步骤 3:初始化对象的默认值

  • JVM 将对象字段初始化为默认值(不调用构造方法)。
class Example {
    int x;    // 默认值 0
    boolean y; // 默认值 false
    String s; // 默认值 null
}

这里需要注意,类加载过程中也会有对变量赋默认值的操作,但二者是不同的,类加载过程中的是为类的静态变量赋默认值,而这里是对对象的属性进行赋默认值。 

步骤 4:设置对象的元数据

  • Mark Word(标记字段)

    • 存储 哈希码、GC 状态、锁信息
    • 在对象加锁、GC 过程中会被修改。
  • Class Pointer(类指针)

    • 指向对象所属的类元信息(方法区中的 Class 对象)。
    • 通过此指针可以找到对象的类型信息

步骤 5:执行构造方法

JVM 调用 构造方法 <init>(),执行初始化逻辑:

class Example {
    int num;
    Example() {
        num = 10;
        System.out.println("Constructor executed!");
    }
}

public class Test {
    public static void main(String[] args) {
        Example obj = new Example();
    }
}
  • JVM 调用构造方法num = 10

这里需要注意,类加载过程中的赋值操作与这里不同,类加载过程中只是单纯为类的静态变量赋值,而这里是调用构造函数对对象的属性进行赋值。


4. Java 对象的内存分配

4.1 对象的内存结构

Java 采用 堆 + 栈 的模式进行对象的内存管理。

存储位置存储内容
堆(Heap)对象本身(实例变量、数组)
栈(Stack)对象引用(局部变量表)
方法区(Method Area)类的元信息(方法、静态变量)
方法区:
A 类的静态变量 b = 20
A 类的方法信息

栈:
obj1 -> 指向堆中的 A 对象
obj2 -> 指向另一个 A 对象

堆:
obj1 的实例变量 a = 10
obj2 的实例变量 a = 10

4.2 具体对象内存分配示例 

class Person {
    static String species = "Human";  // 静态变量(方法区)
    String name;   // 实例变量(堆)
    int age;       // 实例变量(堆)

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public void sayHello() {
        System.out.println("Hello, my name is " + name);
    }
}

public class MemoryDemo {
    public static void main(String[] args) {
        Person p1 = new Person("Alice", 25);  // 在堆中创建对象
        Person p2 = new Person("Bob", 30);    // 在堆中创建另一个对象

        p1.sayHello();
        p2.sayHello();
    }
}

 直观的内存示意图

方法区(存储类信息 + 静态变量)
-------------------------------------------------
| 类名:Person                                   
| 静态变量:species = "Human"                   
| 方法:sayHello()                              
-------------------------------------------------

栈(存储局部变量/对象引用)
---------------------------------
| main() 方法的栈帧                          
| p1 -> 指向 堆中的对象 1                     
| p2 -> 指向 堆中的对象 2                     
---------------------------------

堆(存储对象实例)
-------------------------------------
| Person 对象 1 (p1)                     
| name = "Alice"                          
| age = 25                                
-------------------------------------
| Person 对象 2 (p2)                     
| name = "Bob"                            
| age = 30                                
-------------------------------------

4.3 对象的内存分配策略

JVM 根据对象的生命周期和大小,决定其分配的位置:

  • 新生代(Young Generation)

    • 大部分对象先在 Eden 区分配,如果对象存活过 GC,则进入 Survivor 区。
    • 新生代 GC(Minor GC)频繁执行,但速度快。
  • 老年代(Old Generation)

    • 生命周期长的对象 会晋升到老年代(如缓存对象)。
    • 大对象无法放入新生代,直接进入老年代。
  • 栈上分配(逃逸分析)

    • JVM 可能优化对象分配,将短生命周期对象存放在栈上,减少 GC 压力。
    • 需要开启 -XX:+DoEscapeAnalysis

5. JVM 垃圾回收机制(GC) 

5.1 为什么需要垃圾回收?

Java 采用自动内存管理

  • 在 C 语言中,开发者需要手动申请和释放内存(malloc() / free()),容易导致 内存泄漏(Memory Leak)悬空指针(Dangling Pointer)
  • 在 Java 中JVM 通过 GC 自动回收不再使用的对象,避免手动管理内存的复杂性。

解决对象生命周期管理问题

  • Java 采用 堆(Heap)存储对象,但对象的生命周期不同:
    • 短生命周期对象(局部变量、循环创建的对象)
    • 长生命周期对象(缓存、全局对象)
    • 永久对象(static 变量)
  • GC 需要智能回收短生命周期对象,并优化长期存活对象的管理

5.2 判断对象是否需要垃圾回收的两种方法

5.2.1. 引用计数法(已淘汰)

  • 原理:每个对象有一个引用计数器引用 +1,解除引用 -1,当引用计数变为 0,说明对象可被回收。
  • 缺陷:由于无法处理循环引用(两个对象相互引用,但不再使用)的问题,现已淘汰。

5.2.2 可达性分析法

基本原理

可达性分析法是基于图遍历(Graph Traversal)的方式进行垃圾对象检测:

  1. GC Roots(垃圾回收根对象) 作为起点(根节点)。
  2. 从 GC Roots 开始遍历所有可以访问到的对象,标记为存活。
  3. 未被遍历到的对象 被认为是不可达(Garbage),可以被回收。

什么是 GC Roots(垃圾回收根对象) / GC Roots 的来源

在可达性分析中,JVM 会选择一组特殊的对象作为根对象(GC Roots),从这些根开始查找所有可达对象

GC Roots 类型存储位置示例
栈帧中的局部变量栈(Stack)方法内的局部变量 Object obj = new Object();
静态变量(Static)方法区(Metaspace)static Object obj = new Object();
常量池中的引用方法区(Metaspace)String s = "Hello";
JNI(Native 方法)引用的对象本地方法栈(Native Stack)通过 JNI 访问的 Java 对象
线程对象线程管理区运行中的线程对象 Thread.currentThread()

5.3 垃圾回收算法

JVM 采用不同的 GC 算法来优化垃圾回收,主要包括:

GC 算法原理优缺点
标记-清除(Mark-Sweep)标记存活对象 → 清除未标记对象产生内存碎片,影响分配效率
复制(Copying)复制存活对象到新区域,清空旧区域内存利用率低(50% 内存浪费)
标记-整理(Mark-Compact)标记存活对象 → 移动对象(向一端移动) → 回收未使用空间解决碎片问题,性能较高
分代回收(Generational GC)新生代 采用复制算法老年代 采用标记整理算法适用于大规模应用
  • 新生代(Young Generation)

    • 采用复制算法(对象生命周期短,适合快速回收)。
    • 包括 Eden、Survivor 0、Survivor 1
    • Minor GC 发生在新生代,速度快。
  • 老年代(Old Generation)

    • 采用标记-整理算法(对象生命周期长)。
    • Major GC / Full GC 发生在老年代,通常比 Minor GC 慢。

5.4 常见垃圾回收器

GC新生代算法老年代算法适用场景
Serial GC复制标记整理单线程,适用于小型应用
Parallel GC复制标记整理多线程高吞吐量
CMS GC标记清除标记清除低延迟,适用于 Web 应用
G1 GCRegion 化管理Region 化管理大内存应用,JDK 9+ 推荐
ZGC并发并发超低延迟,JDK 11+

STW(Stop-The-World)的概念

STW(Stop-The-World) 是指 JVM 在执行 GC 时,会暂停所有应用线程,以便垃圾回收器安全地回收对象。这意味着:

  • 所有应用线程停止执行,等待 GC 完成后再继续运行。
  • STW 发生时,Java 代码暂停执行,系统响应变慢,可能导致卡顿。

5.4.1 CMS GC(Concurrent Mark-Sweep)

CMS(Concurrent Mark-Sweep)是 JDK 1.4 引入的 低延迟 GC,适用于Web 服务器、金融系统等低停顿时间应用

(1)CMS GC 的核心特点

最小化 STW(低延迟),适用于交互式应用。
并发执行 GC,不影响应用线程运行。
"标记-清除" 算法,回收时不会整理堆内存(容易产生内存碎片)。
垃圾碎片问题严重,可能导致 Full GC(STW 变长)。
CPU 资源开销大,GC 线程与应用线程竞争 CPU 资源。


(2)CMS GC 的工作原理

1️⃣ CMS GC 的堆内存结构

  • 采用 "分代回收"(Generational GC)策略
    • 新生代(Young Generation):使用 "复制" 算法进行垃圾回收(Minor GC)。
    • 老年代(Old Generation):使用 "标记-清除" 算法进行垃圾回收(Major GC)。
    • 方法区。

2️⃣ CMS GC 的垃圾回收流程

CMS GC 的核心思想是:并发执行垃圾回收,尽可能减少 STW 时间

这里的并发执行指的是和应用线程并发执行,不用暂停应用线程也能进行垃圾回收。

垃圾回收流程如下:

  1. 初始标记(Initial Mark,STW):标记 GC Roots 直接关联的对象,STW 时间短。

  2. 并发标记(Concurrent Marking):应用程序运行的同时,遍历对象图,标记可达对象。

  3. 重新标记(Remark,STW):由于并发标记时,可能有对象状态发生变化,因此需要再次 STW,重新标记存活对象

  4. 并发清除(Concurrent Sweep):应用程序运行的同时,并发清除垃圾对象,释放内存。

  5. Full GC(当 CMS GC 失败时触发,STW 时间长):由于 CMS 不进行内存整理(Compaction),可能导致碎片化问题。当大对象无法分配到连续空间时,触发 Full GC(可能造成严重 STW(通常几百毫秒到几秒))。


(3)CMS GC 的垃圾碎片问题

为什么 CMS GC 会产生垃圾碎片?

  • CMS 采用"标记-清除"算法,不进行内存整理,导致老年代中存在很多不连续的空闲内存(碎片)。
  • 大对象需要分配时,如果没有足够的连续空间,JVM 可能触发 Full GC 进行内存整理(STW 时间长)。

解决方案:

  • 参数优化

    • -XX:+UseCMSCompactAtFullCollection(在 Full GC 后进行整理)。
    • -XX:CMSFullGCsBeforeCompaction=3(每 3 次 Full GC 后执行一次内存整理)。
  • 改用 G1 GC

    • G1 GC 通过 Region 化管理和混合回收,可以避免碎片化问题

5.4.2 G1 GC(Garbage First)

G1 GC(Garbage First GC)是 JDK 7u4 引入,并在 JDK 9 成为 默认 GC
适用于大内存应用(4GB 以上),相比 CMS GC 减少了碎片化问题,提供更可预测的 GC 停顿时间

(1)G1 GC 的核心特点

Region(分区化管理),动态调整新生代和老年代比例。
可预测的 GC 停顿时间(-XX:MaxGCPauseMillis)。
并发执行回收,减少 STW 停顿时间。
自动整理内存(不会产生碎片化问题)。
相比 CMS,CPU 开销更高。
吞吐量略低于 Parallel GC。


(2)G1 GC 的工作原理

1️⃣ G1 GC 的堆内存结构

  • 不同于 CMS GC 的 "分代管理",G1 GC 采用 "Region(分区)管理"
    • Eden(新生代)
    • Survivor(新生代)
    • Old(老年代)
    • Humongous(存放大对象)
    • Free(未使用的 Region)

2️⃣ G1 GC 的垃圾回收流程

  1. 年轻代 GC(Minor GC,STW):复制存活对象到 Survivor 或老年代,清空 Eden。

  2. 并发标记(Concurrent Marking,避免 STW):识别老年代中垃圾最多的 Region,准备回收。

  3. 混合回收(Mixed GC,减少 Full GC):同时回收年轻代和部分老年代,减少老年代空间不足问题。

  4. Full GC(极少发生):只有当 G1 GC 失败时才会触发 Full GC。


(3)G1 GC 避免垃圾碎片
  • 通过 Region 化管理对象,回收垃圾最多的 Region,避免碎片化问题。
  • 当需要整理时,可以逐步迁移存活对象,减少 STW 时间

5.4.3 CMS GC vs G1 GC 对比

JDK 9默认使用G1 GC

对比项CMS GCG1 GC
适用场景低延迟应用(Web 服务器)大内存应用(4GB+)
回收策略标记-清除,不整理内存Region 化管理,减少碎片
STW 时间可能较长(Full GC)可预测(-XX:MaxGCPauseMillis
碎片化问题可能严重,影响 Full GC 频率通过 Region 避免碎片
吞吐量较高,但 Full GC 影响较大较稳定,整体吞吐量较优
Full GC 触发碎片化严重时容易触发极少发生

5.5 GC不仅会对堆进行GC还会对方法区GC

  • 堆(Heap)GC:

    • 主要回收 Java 对象(实例)。
    • 频繁触发 GC(Minor GC、Major GC、Full GC)
  • 方法区(Method Area)GC:

    • 主要回收 类的元数据、常量池、JIT 编译后的代码缓存
    • 较少触发 GC(通常在类卸载时进行)。

方法区 GC 主要回收哪些内容?

(1)废弃的常量

  • 字符串常量池(String Pool)
  • 运行时常量池中的数值、类名、方法名、字段名

(2)无用的类

类的卸载(Class Unloading) 发生在以下条件都满足时:

  1. 该类的所有实例都被 GC 回收(即堆中不再存在该类的对象)。
  2. 加载该类的 ClassLoader 本身已经被回收
  3. 该类没有被静态变量(static)引用

注意:JVM 默认不会主动卸载类,通常只有在动态加载和卸载 ClassLoader 时才会发生(如 Web 服务器动态部署)


(3)JIT 编译缓存

JVM 的 JIT 编译器(Just-In-Time Compiler) 会将热点代码编译成本地机器码并缓存到 代码缓存(Code Cache)。当缓存空间不足时,JVM 可能会触发 GC 清除不常用的编译代码。


方法区 GC 触发时机

  • 动态代理、反射、CGLIB 生成的类较多时(如 Spring 框架)。
  • 大量的字符串常量、方法名、字段名 存入常量池。
  • 频繁卸载 ClassLoader(如 Web 服务器重新加载 WAR 包)。
  • JIT 编译器缓存过多代码(如长时间运行的大型 Java 程序)。

6. JVM 内存泄漏

6.1 什么是 JVM 内存泄漏?

在 JVM 中,内存泄漏(Memory Leak) 指的是程序运行过程中,不再使用的对象仍然被引用,导致 GC 无法回收它们,进而导致堆内存(Heap)不断膨胀,最终可能触发 OutOfMemoryError(OOM)

尽管 Java 有 垃圾回收机制(GC),但如果对象仍然被可达引用(Reachable),即使程序不再使用它们,GC 也不会回收这些对象。这就形成了内存泄漏


6.2 内存泄漏的表现

1. 堆内存持续增长

  • JVM 运行时间越长,内存占用越高,甚至 OOM
  • GC 频率升高,但老年代(Old Generation)对象无法释放

2. 应用性能下降

  • 内存占用增加,导致频繁 GC
  • 应用响应时间变慢,甚至崩溃

3. OutOfMemoryError: Java heap space

  • 堆空间耗尽,程序崩溃
  • 发生在 大量对象未释放大对象占用过多内存 的情况下。

6.3 JVM 内存泄漏的常见原因

Java 内存泄漏的根本原因是 无用对象仍然被引用,GC 无法回收它们。常见的几种情况如下:


6.3.1 静态集合类 / 静态变量导致的内存泄漏

原因

  • 静态变量(static)属于类,生命周期与 JVM 一致,不会被 GC 自动回收。
  • 若静态变量持有大量对象引用,即使对象本身不再使用,也不会被回收,从而造成 堆积

示例

import java.util.*;

public class StaticCollectionLeak {
    private static final List<byte[]> memoryLeakList = new ArrayList<>();

    public static void main(String[] args) {
        for (int i = 0; i < 1000; i++) {
            byte[] largeObject = new byte[1024 * 1024]; // 1MB
            memoryLeakList.add(largeObject);
        }
    }
}

问题memoryLeakListstatic,导致所有对象即使不再需要,仍然不会被 GC 回收。 

解决方案

  • 避免静态集合存储大量对象
  • 使用 WeakReferenceSoftReference
  • 手动调用集合的clear()方法清理

6.3.2 监听器 & 观察者模式导致的泄漏

原因

  • 监听器(Listener)或观察者模式(Observer)会使对象之间形成强引用,即使对象不再使用,监听器仍然会保持对它的引用,导致 GC 无法回收。

示例

import java.util.ArrayList;
import java.util.List;

class EventSource {
    private final List<EventListener> listeners = new ArrayList<>();

    public void addListener(EventListener listener) {
        listeners.add(listener);
    }
}

interface EventListener {
    void onEvent();
}

public class ListenerLeak {
    public static void main(String[] args) {
        EventSource eventSource = new EventSource();
        EventListener listener = () -> System.out.println("Event received!");
        eventSource.addListener(listener);
    }
}

问题

  • listeners 集合会一直持有 EventListener 对象的引用,即使它们不再被使用,导致 GC 不能回收它们。

解决方案

  • 使用 WeakReference 弱引用
private final List<WeakReference<EventListener>> listeners = new ArrayList<>();

6.3.3 线程本地变量(ThreadLocal)泄漏

原因

  • ThreadLocal 绑定的变量存储在线程的 ThreadLocalMap,但如果不手动清理,线程池复用线程时可能会导致数据泄漏。

示例

public class ThreadLocalLeak {
    private static final ThreadLocal<byte[]> threadLocal = new ThreadLocal<>();

    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            threadLocal.set(new byte[10 * 1024 * 1024]); // 10MB
        });
        thread.start();
    }
}

问题:线程执行完后,ThreadLocal 变量没有被清理,导致占用 10MB 内存无法释放。

解决方案

  • finally 语句中手动清理 ThreadLocal 变量
try {
    threadLocal.set(new byte[10 * 1024 * 1024]);
} finally {
    threadLocal.remove();
}

6.3.4 内部类 & 匿名类导致的泄漏

原因

  • 非静态内部类匿名类 持有对外部类的隐式引用,如果外部类仍然存活,内部类不会被 GC 回收。

示例

public class InnerClassLeak {
    private byte[] largeArray = new byte[10 * 1024 * 1024]; // 10MB

    public void createAnonymousClass() {
        Runnable task = new Runnable() {
            @Override
            public void run() {
                System.out.println(largeArray.length);
            }
        };
        new Thread(task).start();
    }

    public static void main(String[] args) {
        new InnerClassLeak().createAnonymousClass();
    }
}

问题

task 作为匿名内部类会持有 InnerClassLeak 的引用?

在 Java 中,非静态内部类和匿名类会隐式地持有外部类实例的引用,这就是 隐式引用(Implicit Reference)。这意味着:

  • 匿名内部类 taskrun() 方法内部,访问了 largeArrayInnerClassLeak 的成员变量)。
  • 由于 largeArrayInnerClassLeak 的实例变量,所以 匿名类 task 需要持有 InnerClassLeak 的引用,才能访问 largeArray
  • 这种 外部类的隐式引用 可能会导致 InnerClassLeak 对象无法被 GC 回收,从而导致内存泄漏

解决方案

  • 使用静态内部类,避免隐式引用
public class InnerClassLeak {
    private byte[] largeArray = new byte[10 * 1024 * 1024]; // 10MB

    public void createStaticInnerClass() {
        new Thread(new StaticTask(largeArray)).start(); // 直接传递 largeArray
    }

    // 变为静态内部类
    private static class StaticTask implements Runnable {
        private final byte[] arrayRef;

        StaticTask(byte[] arrayRef) {
            this.arrayRef = arrayRef;
        }

        @Override
        public void run() {
            System.out.println(arrayRef.length);  // 访问传入的参数,而不是外部类变量
        }
    }

    public static void main(String[] args) {
        new InnerClassLeak().createStaticInnerClass();
    }
}

为什么静态内部类可以避免内存泄漏?

  • 静态内部类不会持有外部类 InnerClassLeak 的隐式引用

    • StaticTask 使用 static 修饰后,就不再与 InnerClassLeak 绑定,它变成了独立的类
    • StaticTask 不会自动持有 InnerClassLeak 的实例引用
  • 显式传递 largeArray,避免隐式引用

    new Thread(new StaticTask(largeArray)).start();
    
    • 我们显式地将 largeArray 传递给 StaticTask 构造方法,这样 StaticTask 只持有 largeArray 的引用,而不是 InnerClassLeak 的整个实例。
    • 即使 InnerClassLeak 被 GC 回收,StaticTask 仍然可以正常运行
方案是否会导致内存泄漏?原因
匿名内部类可能会泄漏持有外部类 InnerClassLeak 的隐式引用,导致 largeArray 无法回收
静态内部类不会泄漏不再持有 InnerClassLeak 的引用,只持有 largeArray,可以安全回收

最佳实践

  • 避免匿名类访问外部类的实例变量,否则可能会无意间创建隐式引用,导致对象不能被 GC
  • 如果必须使用内部类,建议使用 static 内部类,并通过构造方法传递所需数据,避免隐式引用外部类。

6.3.5 数据库连接 / IO 资源未关闭

原因

  • 数据库连接(JDBC)、文件流、Socket 连接未正确关闭,导致资源泄漏,最终耗尽可用内存。
  • GC 无法自动回收这些外部资源

示例

public class ConnectionLeak {
    public static void main(String[] args) throws Exception {
        Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "user", "password");
        Statement stmt = conn.createStatement();
        ResultSet rs = stmt.executeQuery("SELECT * FROM users");

        while (rs.next()) {
            System.out.println(rs.getString("name"));
        }
        // 🚨 资源未关闭,泄漏
    }
}

解决方案

  • 使用 try-with-resources
    try (Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "user", "password");
         Statement stmt = conn.createStatement();
         ResultSet rs = stmt.executeQuery("SELECT * FROM users")) {
        
        while (rs.next()) {
            System.out.println(rs.getString("name"));
        }
    }
    
    try-with-resources 确保所有资源自动关闭!

6.4 如何检测 JVM 内存泄漏?

(1) 使用 jmap 查看堆内存

jmap -histo:live <pid>
  • 分析大对象占用情况,找出无法被回收的对象。

(2)使用 jconsole 监控 JVM 内存

  • 实时观察堆内存使用趋势,发现是否存在不断增长且无法释放的对象

(3)使用 VisualVM 进行 Heap Dump 分析

jmap -dump:format=b,file=heapdump.hprof <pid>
  • 导入 VisualVM,分析对象引用关系,找出无法被 GC 回收的对象

(4)使用 MAT(Memory Analyzer Tool)

  • MAT(Eclipse Memory Analyzer) 可以分析 .hprof 文件,找出GC Root 保持的对象,定位泄漏点。

7. JVM 内存溢出(OutOfMemoryError, OOM) 

7.1. 什么是 JVM 内存溢出?

JVM 内存溢出(OutOfMemoryError,简称 OOM) 是指 JVM 试图分配内存,但由于内存不足或内存无法回收,导致 JVM 运行失败并抛出 java.lang.OutOfMemoryError 异常

JVM 主要的内存区域

  • 堆(Heap):存储对象实例。
  • 栈(Stack):存储方法调用帧、局部变量。
  • 方法区(Method Area)(JDK 8+ 称为 Metaspace):存储类元数据、方法、静态变量等。
  • 直接内存(Direct Memory):NIO 直接分配的操作系统内存。

7.2. 常见的 OOM 类型

JVM 内存溢出 通常发生在以下几种区域

OOM 错误类型发生区域主要原因
java.lang.OutOfMemoryError: Java heap space堆(Heap)- 对象过多,无法回收,导致堆空间耗尽(如集合无限增长,缓存未清理)。
- 单个大对象分配失败(如一次性分配超大数组)。
java.lang.OutOfMemoryError: GC overhead limit exceeded堆(Heap)- GC 频繁运行但每次回收内存极少,导致 CPU 资源被大量消耗。
java.lang.OutOfMemoryError: Metaspace方法区(Metaspace)- 类加载过多(如 Spring 频繁创建代理类,动态类加载)。
- 类无法卸载(如自定义 ClassLoader 造成内存泄漏)。
java.lang.StackOverflowError栈(Stack)- 方法递归过深,导致栈帧溢出(如无限递归)。
- 每个线程栈空间不足,导致溢出。
java.lang.OutOfMemoryError: unable to create new native thread本地内存(OS 线程数)- 线程创建过多,超出 OS 允许的最大线程数(如无限创建 new Thread())。
- 每个线程栈大小过大,导致系统无法分配新线程。
java.lang.OutOfMemoryError: Direct buffer memory直接内存(Direct Memory)- NIO 直接缓冲区 (ByteBuffer.allocateDirect()) 分配过多,超过 MaxDirectMemorySize 限制。
java.lang.OutOfMemoryError: Swap space操作系统 Swap 交换空间- 应用分配内存过多,导致 OS 交换空间耗尽(一般在物理内存不足时发生)。
java.lang.OutOfMemoryError: Requested array size exceeds VM limit堆(Heap)- 试图分配超大数组(如 new int[Integer.MAX_VALUE])。
java.lang.OutOfMemoryError: Compressed class space方法区(Metaspace, Class Space)- JVM 运行时加载类过多,超出 CompressedClassSpaceSize 限制。

7.3 各种 JVM 内存溢出情况

7.3.1. 堆内存溢出(java.lang.OutOfMemoryError: Java heap space)

原因

  • 创建对象过多,堆空间不断增长,无法回收(如:缓存未清理、集合不断增长)。
  • 单个大对象分配失败(如:一次性分配一个超大数组)。
  • 内存泄漏,无用对象仍然被引用,GC 无法清理。

示例

import java.util.ArrayList;
import java.util.List;

public class HeapOOM {
    public static void main(String[] args) {
        List<byte[]> list = new ArrayList<>();
        while (true) {
            list.add(new byte[10 * 1024 * 1024]); // 每次分配 10MB
        }
    }
}

解决方案

增大堆空间(适用于对象确实需要更多内存的情况):

java -Xms2g -Xmx4g HeapOOM

优化 GC 策略

java -XX:+UseG1GC -XX:MaxGCPauseMillis=200 HeapOOM

检测内存泄漏

  • 使用 jmap
jmap -histo:live <pid>
  • 使用 Heap Dump
jmap -dump:format=b,file=heapdump.hprof <pid>
  • 使用 VisualVMMAT(Memory Analyzer Tool)分析 heapdump.hprof

7.3.2. 栈内存溢出(StackOverflowError 或 Stack Space OOM)

原因

  • 递归调用过深,导致栈帧不断压入,最终超过栈空间大小。
  • 创建大量线程,导致 JVM 线程栈空间耗尽。

示例 1:递归调用导致 StackOverflowError

public class StackOverflowDemo {
    public void recursiveMethod() {
        recursiveMethod(); // 无限递归
    }

    public static void main(String[] args) {
        new StackOverflowDemo().recursiveMethod();
    }
}

示例 2:创建大量线程导致 OutOfMemoryError: Unable to create new native thread

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadOOM {
    public static void main(String[] args) {
        ExecutorService executor = Executors.newFixedThreadPool(1000);
        for (int i = 0; i < Integer.MAX_VALUE; i++) {
            executor.execute(() -> {
                while (true) {}  // 每个线程执行无限循环
            });
        }
    }
}

解决方案

减少递归深度增大栈空间

java -Xss1m StackOverflowDemo

控制线程池大小

ExecutorService executor = Executors.newFixedThreadPool(100);

7.3.3. 方法区/元空间溢出(Metaspace OOM)

原因

  • 大量动态生成的类(如:大量使用 CGLIBJavassist 动态代理)。
  • Spring Boot 等框架频繁加载新类,导致 Metaspace 过满。
  • 应用长时间运行,但类卸载不及时,导致 Metaspace 持续增长。

示例

import javassist.ClassPool;

public class MetaspaceOOM {
    public static void main(String[] args) throws Exception {
        ClassPool classPool = ClassPool.getDefault();
        for (int i = 0; i < Integer.MAX_VALUE; i++) {
            classPool.makeClass("com.example.GeneratedClass" + i).toClass();
        }
    }
}

解决方案

增加 Metaspace 大小

java -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m MetaspaceOOM

减少动态生成的类


7.3.4. GC 过载导致 OOM(java.lang.OutOfMemoryError: GC Overhead limit exceeded)

原因

  • GC 运行时间过长,超过 98% CPU,但回收的内存不足 2%,JVM 触发此 OOM 保护机制。
  • 堆内存不足,导致 GC 频繁执行,但对象回收效果不佳。

示例

import java.util.HashMap;
import java.util.Map;

public class GCOverheadOOM {
    public static void main(String[] args) {
        Map<Integer, String> map = new HashMap<>();
        int i = 0;
        while (true) {
            map.put(i, "OOM Test " + i++); // 不断填充 HashMap
        }
    }
}

解决方案

增大堆空间,减少 GC 触发

java -Xmx4g -XX:+UseG1GC -XX:MaxGCPauseMillis=200 GCOverheadOOM

使用 -XX:-UseGCOverheadLimit 关闭 GC 限制

java -Xmx4g -XX:-UseGCOverheadLimit GCOverheadOOM

7.3.5. 直接内存溢出(Direct Buffer Memory OOM)

原因

  • NIO ByteBuffer 分配过多,导致 Direct Memory 耗尽。
  • JVM 直接内存上限太低,无法满足 ByteBuffer.allocateDirect() 分配请求。

示例

import java.nio.ByteBuffer;

public class DirectMemoryOOM {
    public static void main(String[] args) {
        while (true) {
            ByteBuffer.allocateDirect(1024 * 1024); // 每次申请 1MB 直接内存
        }
    }
}

解决方案

增大直接内存

java -XX:MaxDirectMemorySize=512m DirectMemoryOOM

避免无限制分配

ByteBuffer buffer = ByteBuffer.allocateDirect(10 * 1024 * 1024);
buffer.clear(); // 复用 Buffer,避免反复分配

8. JVM 常见参数及其作用

在 JVM 运行 Java 应用时,我们可以使用 JVM 参数 来控制内存分配、垃圾回收(GC)策略、性能优化等。本文将详细介绍 JVM 常见参数的分类、作用、以及如何设置这些参数。


8.1 JVM 参数设置方法

在生产环境中,JVM 参数通常通过以下方式进行设置:

(1)直接通过 java 命令行设置

适用于 独立 Java 应用、测试环境,例如:

java -Xms2g -Xmx4g -XX:+UseG1GC -jar myapp.jar

(2)在 JAVA_OPTSJAVA_TOOL_OPTIONS 环境变量中设置

适用于 Web 服务器(Tomcat、Spring Boot、微服务)

export JAVA_OPTS="-Xms2g -Xmx4g -XX:+UseG1GC"

(3)在 Docker 容器中设置

容器化部署时,一般通过环境变量 JAVA_OPTS 传递:

docker run -e "JAVA_OPTS=-Xmx4g -XX:+UseG1GC" my-java-app

(4)在 Kubernetes(K8s)中设置

对于 K8s 部署的 Java 应用,可以在 Deployment 配置文件中设置:

env:
  - name: JAVA_OPTS
    value: "-Xms2g -Xmx4g -XX:+UseG1GC"

8.2 常用 JVM 参数及生产环境实践

8.2.1 内存管理参数

作用:控制 JVM 的 堆(Heap)、栈(Stack)、方法区(Metaspace) 大小,影响 GC 频率和性能。

参数作用生产环境建议
-Xms<size>初始堆大小(默认 1/64 物理内存)设置与 -Xmx 相同,避免运行时扩展
-Xmx<size>最大堆大小(默认 1/4 物理内存)根据可用内存大小设置,如 -Xmx4g
-XX:NewRatio=n新生代:老年代 比例(默认 2,即 1:2推荐 NewRatio=2,适用于吞吐量型应用
-XX:SurvivorRatio=nEden:Survivor 比例(默认 8:1:1保持默认 SurvivorRatio=8
-Xss<size>每个线程的栈大小(默认 1MB)适用于高并发应用,如 -Xss512k 减少栈内存占用
-XX:MetaspaceSize=256mJDK 8+ 方法区大小推荐 256m
-XX:MaxMetaspaceSize=512m元空间最大值防止 Metaspace OOM,推荐 512m

8.2.2 GC(垃圾回收)策略

作用:选择合适的 GC 机制,降低 STW(Stop-The-World) 停顿时间,提高吞吐量。

参数作用生产环境建议
-XX:+UseSerialGC单线程 GC(适用于小型应用)不推荐用于生产环境
-XX:+UseParallelGC多线程吞吐量 GC适用于批处理任务、Kafka、Spark
-XX:+UseG1GC低延迟 GC(默认)适用于 Web 服务器 / 微服务
-XX:+UseZGC超低延迟 GC(JDK 11+)适用于金融、超大堆(TB 级)应用
-XX:MaxGCPauseMillis=200最大 GC 停顿时间适用于 G1 GC,控制 STW 时长

8.2.3 JIT(Just-In-Time 编译)优化

作用:优化 JIT 编译,提高 Java 代码执行性能。

参数作用生产环境建议
-XX:+TieredCompilation分层 JIT 编译默认启用,适用于高并发应用
-XX:+PrintCompilation打印 JIT 编译方法调试时启用

8.2.4 线程管理

作用:控制并发线程数,提高 CPU 资源利用率。

参数作用生产环境建议
-XX:ParallelGCThreads=<n>GC 并行线程数推荐 CPU 核心数 / 2
-XX:ConcGCThreads=<n>G1 / ZGC 并发线程数适用于低延迟应用

8.2.5 监控与日志

作用:启用 GC 日志,监控应用运行状态。

参数作用生产环境建议
-XX:+HeapDumpOnOutOfMemoryErrorOOM 生成 Heap Dump强烈建议启用
-XX:HeapDumpPath=<path>Heap Dump 存储路径推荐 /var/logs/heapdump.hprof
-XX:+PrintGCDetails打印 GC 详情生产环境推荐
-Xloggc:/var/logs/gc.logGC 日志存储路径用于 GC 监控分析

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/968142.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Unity3D实现显示模型线框(shader)

系列文章目录 unity工具 文章目录 系列文章目录👉前言👉一、效果展示👉二、第一种方式👉二、第二种方式👉壁纸分享👉总结👉前言 在 Unity 中显示物体线框主要基于图形渲染管线和特定的渲染模式。 要显示物体的线框,通常有两种常见的方法:一种是利用内置的渲染…

java项目之直销模式下家具工厂自建网站源码(ssm+mysql)

风定落花生&#xff0c;歌声逐流水&#xff0c;大家好我是风歌&#xff0c;混迹在java圈的辛苦码农。今天要和大家聊的是一款基于ssm的直销模式下家具工厂自建网站源码。项目源码以及部署相关请联系风歌&#xff0c;文末附上联系信息 。 项目简介&#xff1a; 直销模式下家具…

window 安装GitLab服务器笔记

目录 视频&#xff1a; 资源&#xff1a; Linux CeneOS7&#xff1a; VMware&#xff1a; Linux无法安装 yum install vim -y 1.手动创建目录 2.下载repo PS 补充视频不可复制的代码 安装GitLab *修改root用户密码相关&#xff08;我卡在第一步就直接放弃了这个操作&…

笔记:理解借贷相等的公式

强烈推荐非会计人士&#xff0c;快速了解会计看这个系列的视频&#xff0c;其中比较烧脑的“借贷相等”公式&#xff0c;这个视频讲解的不错&#xff1a; 4.小白财务入门-借贷记账法_哔哩哔哩_bilibili 比如这里&#xff0c;钱在银行卡重&#xff0c;所以银行存款就是借方…

Qt - 地图相关 —— 3、Qt调用高德在线地图功能示例(附源码)

效果 作者其他相关文章链接:           Qt - 地图相关 —— 1、加载百度在线地图(附源码)           Qt - 地图相关 —— 2、Qt调用百度在线地图功能示例全集,包含线路规划、地铁线路查询等(附源码)           Qt - 地图相关 —— 3、Qt调用…

使用 POI-TL 和 JFreeChart 动态生成 Word 报告

文章目录 前言一、需求背景二、方案分析三、 POI-TL JFreeChart 实现3.1 Maven 依赖3.3 word模板设置3.2 实现代码 踩坑 前言 在开发过程中&#xff0c;我们经常需要生成包含动态数据和图表的 Word 报告。本文将介绍如何结合 POI-TL 和 JFreeChart&#xff0c;实现动态生成 W…

jenkins备份还原配置文件

下载ThinBackup插件 方式1 从插件市场直接下载 Manage Jenkins->Manage Plugins->可选插件搜索 注意&#xff1a;有时可能因为网络或者版本问题下载不了&#xff0c;好像是默认下载最新版本&#xff0c;可选择手动安装&#xff01; 方式二 手动安装插件 点击查看手…

C++蓝桥杯基础篇(二)

片头 嗨&#xff01;小伙伴们&#xff0c;今天我们将学习C蓝桥杯基础篇&#xff08;二&#xff09;&#xff0c;继续练习相关习题&#xff0c;准备好了吗&#xff1f;咱们开始咯~ 第1题 简单计算器输入两个数&#xff0c;以及一个运算符 &#xff0c;-&#xff0c;*&#xff…

将 AMD Zynq™ RFSoC 扩展到毫米波领域

目录 将 AMD Zynq™ RFSoC 扩展到毫米波领域Avnet XRF RFSoC 系统级模块适用于 MATLAB 的 Avnet RFSoC Explorer 工具箱5G mmWave PAAM 开发平台突破性的宽带毫米波波束成形特征&#xff1a;OTBF103 Mathworks Simulink 模型优化毫米波应用中的射频信号路径 用于宽带毫米波上/下…

1Panel配置java运行环境运行springboot项目

一、实际运行效果 1panel上java容器springboot的简单web项目 二、详细操作 步骤一、完成spring项目的打包&#xff0c;生成jar文件 步骤二、登录1panel&#xff0c;点击系统-》文件菜单&#xff0c;上传jar到一个合适的文件夹目录&#xff0c;/opt/jar 如下图&#xff1a; 步…

Jenkins+gitee 搭建自动化部署

Jenkinsgitee 搭建自动化部署 环境说明&#xff1a; 软件版本备注CentOS8.5.2111JDK1.8.0_211Maven3.8.8git2.27.0Jenkins2.319最好选稳定版本&#xff0c;不然安装插件有点麻烦 一、安装Jenkins程序 1、到官网下载相应的版本war或者直接使用yum安装 Jenkins官网下载 直接…

ubuntu安装VMware报错/dev/vmmon加载失败

ubuntu安装VMware报错/dev/vmmon加载失败&#xff0c;解决步骤如下&#xff1a; step1&#xff1a;为vmmon和vmnet组件生成密钥对 openssl req -new -x509 -newkey rsa:2048 -keyout VMW.priv -outform DER -out VMW.der -nodes -days 36500 -subj "/CNVMware/"ste…

LSTM 学习笔记 之pytorch调包每个参数的解释

0、 LSTM 原理 整理优秀的文章 LSTM入门例子&#xff1a;根据前9年的数据预测后3年的客流&#xff08;PyTorch实现&#xff09; [干货]深入浅出LSTM及其Python代码实现 整理视频 李毅宏手撕LSTM [双语字幕]吴恩达深度学习deeplearning.ai 1 Pytorch 代码 这里直接调用了nn.l…

细读 React | React Router 路由切换原理

2022 北京冬奥会开幕式 此前一直在疑惑&#xff0c;明明 pushState()、replaceState() 不触发 popstate 事件&#xff0c;可为什么 React Router 还能挂载对应路由的组件呢&#xff1f; 翻了一下 history.js 源码&#xff0c;终于知道原因了。 源码 假设项目路由设计如下&#…

Flutter 双屏双引擎通信插件加入 GitCode:解锁双屏开发新潜能

在双屏设备应用场景日益丰富的当下&#xff0c;移动应用开发领域迎来了新的机遇与挑战。如何高效利用双屏设备优势&#xff0c;为用户打造更优质的交互体验&#xff0c;成为开发者们关注的焦点。近日&#xff0c;一款名为 Flutter 双屏双引擎通信插件的创新项目正式入驻 GitCod…

【C++高并发服务器WebServer】-18:事件处理模式与线程池

本文目录 一、事件处理模式1.1 Reactor模式1.2 Proactor模式1.3 同步IO模拟Proactor模式 二、线程池 一、事件处理模式 服务器程序通常需要处理三类事件&#xff1a;I/O事件、信号、定时事件。 对应的有两种高效的事件处理模式&#xff1a;Reactor和Proactor&#xff0c;同步…

人岗匹配为核,打造精确高效招聘 “高速路”

人才的选拔与招聘是企业开展所有工作的前提&#xff0c;通过选聘合适的人才&#xff0c;充分发挥其能力和潜质&#xff0c;帮助企业不断完成发展目标。尤其对于初创企业&#xff0c;在人力资源与财务状况均相对紧张的背景下&#xff0c;聚焦于关键岗位的人才招聘显得尤为重要。…

网络在线考试|基于vue的网络在线考试系统的设计与实现(源码+数据库+文档)

网络在线考试系统 目录 基于SSM&#xff0b;vue的网络在线考试系统的设计与实现 一、前言 二、系统设计 三、系统功能设计 1功能页面实现 2系统功能模块 3管理员功能模块 4学生功能模块 四、数据库设计 五、核心代码 六、论文参考 七、最新计算机毕设选题推荐 八…

vue2 导出Excel文件

1.安装依赖 npm install xlsx file-saver 2.使用 <template><button click"exportToExcel">导出Excel</button> </template><script> import * as XLSX from xlsx; import { saveAs } from file-saver; export default {methods: {ex…

第三届通信网络与机器学习国际学术会议(CNML 2025)

在线投稿&#xff1a; 学术会议-学术交流征稿-学术会议在线-艾思科蓝 通信网络机器学习 通信理论 通信工程 计算机网络和数据通信 信息分析和基础设施 通信建模理论与实践 无线传感器和通信网络 云计算与物联网 网络和数据安全 光电子学和光通信 无线/移动通信和技术 智能通信…