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)。
- 局部变量表的大小在编译期确定,运行时不会改变。
long
和double
类型占 两个 存储单元,其它数据类型占 一个 存储单元。
-
操作数栈(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 需要扩展堆的大小,通常向高地址扩展。
- JVM 通过
-
操作系统内存管理
- 在 C 语言的
malloc()
和JVM
的new
语义中,分配的堆空间通常从低地址向高地址增长。 - 堆的增长方向使得堆的可扩展性更好,能动态调整大小。
- 在 C 语言的
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 栈 | 线程私有 | 存储局部变量表、操作数栈 | StackOverflowError 、OutOfMemoryError |
本地方法栈 | 线程私有 | 执行 Native 方法 | StackOverflowError 、OutOfMemoryError |
堆 | 线程共享 | 存储对象实例 | OutOfMemoryError: Java heap space |
方法区(元空间) | 线程共享 | 存储类信息、静态变量 | OutOfMemoryError: Metaspace |
直接内存 | 线程共享 | 用于高效 I/O(如 NIO) | OutOfMemoryError: Direct Buffer Memory |
2. 类加载机制
2.1 类加载器
(1)启动类加载器(Bootstrap ClassLoader)
- 负责加载JDK 核心类库(
rt.jar
,charsets.jar
等)。 - 由 C++ 代码实现,无法直接获取其实例(即
null
)。 - 只能加载JDK 自带的核心类库,无法加载用户定义的
.class
文件。
主要加载的类包括:
java.lang.*
(如String
、Integer
、System
)java.util.*
(如ArrayList
、HashMap
)java.io.*
(如File
、InputStream
)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. 双亲委派机制的工作流程
- 当一个
ClassLoader
需要加载一个类时,它不会自己直接加载,而是先委托给父类加载器。 - 父类加载器递归向上委托,最终到
Bootstrap ClassLoader
(顶层)。 - 如果
Bootstrap ClassLoader
无法加载该类(即不是核心类库),那么父类加载器会逐层返回,直到应用类加载器(AppClassLoader
)。 - 如果所有的父类加载器都无法加载该类,那么当前类加载器才会自己尝试加载。
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
文件,并完成 类加载、验证、准备、解析、初始化 过程(即 类的五个生命周期阶段)。 - 如果 类已加载,跳过此步骤。
- 如果 类未加载,JVM 先通过 类加载器(ClassLoader) 加载
步骤 2:为对象分配内存
JVM 在堆(Heap)中为新对象分配内存,分配策略取决于 内存是否连续:
-
指针碰撞(Bump the Pointer)(内存连续时):
- 堆内存按顺序分配,JVM 仅需将指针移动到新的可用地址。
- 适用于 使用 GC 压缩后 的堆。
-
空闲列表(Free List)(内存不连续时):
- 维护空闲内存块列表,找到合适的内存块进行分配。
- 适用于 堆内存碎片较多 的情况。
-
线程私有分配缓冲区(TLAB, Thread Local Allocation Buffer):
- JVM 允许每个线程在堆中新建私有缓存区域,提高对象分配效率,减少同步锁竞争。
步骤 3:初始化对象的默认值
- JVM 将对象字段初始化为默认值(不调用构造方法)。
class Example {int x; // 默认值 0boolean y; // 默认值 falseString 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)的方式进行垃圾对象检测:
- GC Roots(垃圾回收根对象) 作为起点(根节点)。
- 从 GC Roots 开始遍历所有可以访问到的对象,标记为存活。
- 未被遍历到的对象 被认为是不可达(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 GC | Region 化管理 | 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 时间。
这里的并发执行指的是和应用线程并发执行,不用暂停应用线程也能进行垃圾回收。
垃圾回收流程如下:
-
初始标记(Initial Mark,STW):标记 GC Roots 直接关联的对象,STW 时间短。
-
并发标记(Concurrent Marking):在应用程序运行的同时,遍历对象图,标记可达对象。
-
重新标记(Remark,STW):由于并发标记时,可能有对象状态发生变化,因此需要再次 STW,重新标记存活对象。
-
并发清除(Concurrent Sweep):在应用程序运行的同时,并发清除垃圾对象,释放内存。
-
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 的垃圾回收流程
-
年轻代 GC(Minor GC,STW):复制存活对象到 Survivor 或老年代,清空 Eden。
-
并发标记(Concurrent Marking,避免 STW):识别老年代中垃圾最多的 Region,准备回收。
-
混合回收(Mixed GC,减少 Full GC):同时回收年轻代和部分老年代,减少老年代空间不足问题。
-
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 GC | G1 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) 发生在以下条件都满足时:
- 该类的所有实例都被 GC 回收(即堆中不再存在该类的对象)。
- 加载该类的 ClassLoader 本身已经被回收。
- 该类没有被静态变量(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]; // 1MBmemoryLeakList.add(largeObject);}}
}
问题:memoryLeakList
是 static
,导致所有对象即使不再需要,仍然不会被 GC 回收。
解决方案
- 避免静态集合存储大量对象
- 使用
WeakReference
或SoftReference
- 手动调用集合的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]; // 10MBpublic void createAnonymousClass() {Runnable task = new Runnable() {@Overridepublic 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)。这意味着:
- 在 匿名内部类
task
的run()
方法内部,访问了largeArray
(InnerClassLeak
的成员变量)。 - 由于
largeArray
是InnerClassLeak
的实例变量,所以 匿名类task
需要持有InnerClassLeak
的引用,才能访问largeArray
。 - 这种 外部类的隐式引用 可能会导致
InnerClassLeak
对象无法被 GC 回收,从而导致内存泄漏。
解决方案:
- 使用静态内部类,避免隐式引用:
public class InnerClassLeak {private byte[] largeArray = new byte[10 * 1024 * 1024]; // 10MBpublic 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;}@Overridepublic 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>
- 使用
VisualVM
或MAT
(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)
原因
- 大量动态生成的类(如:大量使用
CGLIB
、Javassist
动态代理)。 - 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_OPTS
或 JAVA_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_OPTSvalue: "-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=n | Eden:Survivor 比例(默认 8:1:1 ) | 保持默认 SurvivorRatio=8 |
-Xss<size> | 每个线程的栈大小(默认 1MB) | 适用于高并发应用,如 -Xss512k 减少栈内存占用 |
-XX:MetaspaceSize=256m | JDK 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:+HeapDumpOnOutOfMemoryError | OOM 生成 Heap Dump | 强烈建议启用 |
-XX:HeapDumpPath=<path> | Heap Dump 存储路径 | 推荐 /var/logs/heapdump.hprof |
-XX:+PrintGCDetails | 打印 GC 详情 | 生产环境推荐 |
-Xloggc:/var/logs/gc.log | GC 日志存储路径 | 用于 GC 监控分析 |
相关文章:
JVM(Java 虚拟机)
Java语言的解释性和编译性(通过JVM 的执行引擎) Java 代码(.java 文件)要先使用 javac 编译器编译为 .class 文件(字节码),紧接着再通过JVM 的执行引擎(Execution Engine)…...
组件库选择:ElementUI 还是 Ant Design
🤍 前端开发工程师、技术日更博主、已过CET6 🍨 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 🕠 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 🍚 蓝桥云课签约作者、上架课程《Vue.js 和 E…...
【论文翻译】DeepSeek-V3论文翻译——DeepSeek-V3 Technical Report——第一部分:引言与模型架构
论文原文链接:DeepSeek-V3/DeepSeek_V3.pdf at main deepseek-ai/DeepSeek-V3 GitHub 特别声明,本文不做任何商业用途,仅作为个人学习相关论文的翻译记录。本文对原文内容直译,一切以论文原文内容为准,对原文作者表示…...
渗透利器:Burp Suite 联动 XRAY 图形化工具.(主动扫描+被动扫描)
Burp Suite 联动 XRAY 图形化工具.(主动扫描被动扫描) Burp Suite 和 Xray 联合使用,能够将 Burp 的强大流量拦截与修改功能,与 Xray 的高效漏洞检测能力相结合,实现更全面、高效的网络安全测试,同时提升漏…...
【Redis】redis 存储的列表如何分页和检索
博主介绍:✌全网粉丝22W,CSDN博客专家、Java领域优质创作者,掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域✌ 技术范围:SpringBoot、SpringCloud、Vue、SSM、HTML、Nodejs、Python、MySQL、PostgreSQL、大数据、物…...
医疗影响分割 | 使用 Swin UNETR 训练自己的数据集(3D医疗影像分割教程)
<Swin UNETR: Swin Transformers for Semantic Segmentation of Brain Tumors in MRI Images> 代码地址:unetr 论文地址:https://arxiv.org/pdf/2201.01266 一、下载代码 在Github上下载代码,然后进入SWINUNETR,前两个是针对两个数据集(BRATS21、BTCV)的操作,这里…...
前端学习路径
🚀 前言 本文不仅包含学习路径,还整合了 300小时精选资源 和 开发者必备工具链,助你构建完整知识体系! 🔧 开发工具全家桶 工具类型推荐工具使用场景代码编辑器VS Code 插件市场主力开发工具,必装插件&am…...
Day84:数据可视化
数据可视化是数据分析的重要组成部分,它能直观地展现数据规律,使复杂数据变得易懂。Python 提供了多个数据可视化库,其中最常用的是 Matplotlib 和 Seaborn。今天,我们将学习如何使用这些工具绘制折线图、柱状图、散点图等。 1. 安装和导入库 如果你的 Python 没有安装 Ma…...
iOS主要知识点梳理回顾-3-运行时消息机制
运行时(runtime) 运行时是OC的重要特性,也是OC动态性的根本支撑。动态,如果利用好了,扩展性就很强。当然了,OC的动态性只能算是一个一般水平。与swift、java这种强类型校验的语言相比,OC动态性很…...
JSON是什么
JSON 简介 JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,易于人阅读和编写,同时也易于机器解析和生成。它基于 JavaScript 的一个子集,但独立于编程语言,广泛用于 Web 应用中的数据交换。…...
three-tile-vue 开发测试
three-tile 在 github 上开源半年多,已有140余颗星,目前性能和功能已能满足生产环境使用,是替代cesium的一种轻量级解决方案。让我没想到的是第一个商用的竟然是老外。 最近,测试了three-tile在vue中的使用,效果还可以…...
Acwing-基础算法课笔记之基础算法(差分)
Acwing-基础算法课笔记之基础算法(差分) 一、一维差分1、差分的概念2、差分思想 二、二维差分操作流程 一、一维差分 1、差分的概念 对于一个给定的序列a,它的差分序列b定义为: b [ 1 ] a [ 1 ] b[1]a[1] b[1]a[1],…...
c语言判断一个文件的文件格式
在 Linux 下使用 C 语言判断一个文件的文件格式,通常需要检查文件的头信息(也称为“幻数”或“魔数”)。不同的文件格式在文件头有特定的字节序列,这些字节序列可以用来确定文件的类型。以下是一个基本的示例,展示了如…...
e2studio开发RA2E1(12)----打印函数(printf、 sprintf)的实现
e2studio开发RA2E1.12--打印函数printf、 sprintf的实现 概述视频教学样品申请硬件准备参考程序源码下载新建工程工程模板保存工程路径芯片配置工程模板选择时钟设置UART配置UART属性配置设置e2studio堆栈e2studio的重定向printf设置R_SCI_UART_Open()函数原型回调函数user_uar…...
C++编译期优化 ---分支预计__builtin_expect
在编写程序逻辑的时候,使用了大量的if-else 分支,为了程序尽可能的高效运行,会考虑把可能性更高的条件写在前面,这样程序的平均运行时间会更短。 gcc提供了一种方式,可以让编译器自行优化 __builtin_expect(expr,bool)…...
ASP.NET Core SignalR的协议协商
SignalR支持多种服务器推送方式:Websocket、Server-Sent Events、长轮询。默认按顺序尝试。F12查看协商过程。websocket和HTTP是不同的协议,为什么能用同一个端口。在【开发人员工具】的【网络】页签中看WebSocket通信过程。 协议协商问题 集群中协议协…...
问卷数据分析|SPSS实操之独立样本T检验
适用条件: 检验分类变量和定量变量之间的差异 分类变量只能为二分类变量,如性别 1.选择分析--比较平均值--独立样本检验 2. 在下方选择性别(分类变量) 3. 点击定义组,组1输入1,组2输入2 4.在上方填入定量…...
Vue事件处理 - 按键修饰符
Vue 渐进式JavaScript 框架 基于Vue2的学习笔记 - Vue事件处理 - 按键修饰符 目录 按键修饰符 常见修饰符 绑定按键事件 绑定事件 优化回车修饰符 多个按键 直接绑定数字 总结 按键修饰符 常见修饰符 .esc .up .down .left .right . space .ctrl .shift .delete 绑定…...
PostgreSQL错误: 编码“UTF8“的字符0x0xe9 0x94 0x99在编码“WIN1252“没有相对应值
错误介绍 今天遇到一个错误,记录一下 2025-02-10 17:04:35.264 HKT [28816] 错误: 编码"WIN1252"的字符0x0x81在编码"UTF8"没有相对应值 2025-02-10 17:04:35.264 HKT [28816] 错误: 编码"UTF8"的字符0x0xe9 0x94 0x99在编码&quo…...
使用OBS推流,大华摄像头 srs服务器播放
说明: ffmpeg可以推流,但是是命令行方式不太友好,还可以使用主流的OBS开源推流软件,可从官网Open Broadcaster Software | OBS 下载最新版本,目前很多网络主播都是用它做直播。该软件支持本地视频文件以及摄像头推流。…...
Ollama部署DeepSeek(windows or ubuntu)
Ollama(官网是https://ollama.com/)是一个专为在本地机器上便捷部署和运行大型语言模型(LLM)而设计的开源框架。它简化了大型语言模型的部署过程,提供了轻量级与可扩展的架构,使得研究人员、开发人员和爱好者能够更加方便地在本地…...
2、k8s 二进制安装(详细)
k8s 二进制安装 IP规划初始化部署 etcd 集群在 etcd01 节点上操作准备cfssl证书生成工具,加权生成etcd证书上传etcd软件包启动 etcd 服务 部署 Master 组件部署 Worker Node 组件node 节点安装 docker部署组件 部署 CNI 网络组件部署 flannel简介部署 部署 Calico简…...
02、QLExpress从入门到放弃,相关API和文档
QLExpress从入门到放弃,相关API和文档 一、属性开关 public class ExpressRunner {private boolean isTrace;private boolean isShortCircuit;private boolean isPrecise; }/*** 是否需要高精度计算*/ private boolean isPrecise false;高精度计算在会计财务中非常重要&…...
在 Visual Studio Code 与微信开发者工具中调试使用 emscripten 基于 C 生成的 WASM 代码
最近在尝试将一些 C/C、Lua 项目挪到 Web 上跑, 接触到了 emscripten. 这里会介绍下在 Visual Studio Code 与微信开发者工具中调试使用 emscripten 基于 C 生成的 WASM 代码 (WebAssembly) 的一些方法. Emscripten 与 WebAssebmly WebAssembly 是一种新的编码方式, 可以在现代…...
计算机毕业设计——Springboot的社区维修平台旅游管理
📘 博主小档案: 花花,一名来自世界500强的资深程序猿,毕业于国内知名985高校。 🔧 技术专长: 花花在深度学习任务中展现出卓越的能力,包括但不限于java、python等技术。近年来,花花更…...
Oracle常见语法
一、求交集 SELECT column1, column2 FROM table1 INTERSECT SELECT column1, column2 FROM table2;INTERSECT 操作符是 Oracle 和一些其他数据库(如 PostgreSQL 和 SQL Server)特有的集合操作符,在 MySQL 中并不直接支持。MYSQL同效果代码&a…...
[2025年最新]2024.3版本idea无法安装插件问题解决
背景 随着大模型的持续发展,特别年前年后deepseek的优异表现,编程过程中,需要解决ai来辅助编程,因此需要安装一些大模型插件 问题描述 在线安装插件的时候会遇到以下问题: 1.数据一直在加载,加载的很满 2.点…...
说一下 jvm 有哪些垃圾回收器?
JVM 垃圾回收器对比表 垃圾回收器类型工作方式回收方式停顿时间适用场景优点缺点常见问题常见配置Serial GC串行单线程,STW年轻代:复制算法 老年代:标记-整理长小内存、单核CPU,如桌面应用或嵌入式设备简单高效,适用于…...
【Linux】--- 进程间的通信
【Linux】--- 进程间的通信 一、进程间通信的介绍1、进程间通信的概念2、进程间通信的目的3、 进程间通信的本质/前提4、进程间通信的分类 二、管道1、什么是管道2、匿名管道(1)匿名管道的原理(2)pipe函数(3࿰…...
GlusterFS 深度洞察:从架构原理到案例实践的全面解读(上)
文章目录 一.GlusterFS简介二.GlusterFS原理架构三.适用场景四.Glusterfs与其他存储产品对比五.部署GlusterFS集群六. 使用heketi将glusterfs接入k8s作为后端存储 一.GlusterFS简介 GlusterFS是一个免费的开源分布式文件系统,具有无中心节点、堆栈式设计、全局统一…...
实现限制同一个账号最多只能在3个客户端(有电脑、手机等)登录(附关键源码)
如上图,我的百度网盘已登录设备列表,有一个手机,2个windows客户端。手机设备有型号、最后登录时间、IP等。windows客户端信息有最后登录时间、操作系统类型、IP地址等。这些具体是如何实现的?下面分别给出android APP中采集手机信…...
C#综合知识点面试集锦
在.NET Core 框架的面试中,可能会涉及基础概念、核心组件、依赖注入、性能优化等多方面的知识点,以下为你详细介绍: 基础概念 .NET Core 概述 定义与特点:解释 .NET Core 是一个跨平台、开源、模块化且高性能的通用开发框架,能在 Windows、Linux、macOS 等操作系统上运行…...
宝珀(Blancpain):机械制表的三项重大创新(中英双语)
宝珀(Blancpain):机械制表的创新先驱 本文灵感来源: 瑞士钟表业决定逆流而上,杀出一条生路,宝珀更是坚定地宣称“我们永远只做机械表”,它拒绝了石英技术。制表师们在提高腕表的技艺和品质、实…...
稠密架构和稀疏架构
稠密架构和稀疏架构 flyfish 稠密架构 参数使用方面:稠密架构中的大部分参数在每次计算时都会被使用。也就是说,对于输入的每一个样本,模型的所有或大部分参数都会参与到计算过程中。计算特点:计算密集,需要对大量的…...
SpringCloud - Gateway 网关
前言 该博客为Sentinel学习笔记,主要目的是为了帮助后期快速复习使用 学习视频:7小快速通关SpringCloud 辅助文档:SpringCloud快速通关 源码地址:cloud-demo 一、简介 官网:https://spring.io/projects/spring-clou…...
【如何掌握CSP-J 信奥赛中的排序算法】
要掌握CSP-J信奥赛中的排序算法,需要系统学习基础排序算法的原理、实现和应用场景。以下是分阶段的学习路径和建议: 一、必掌握的排序算法清单 CSP-J阶段需重点掌握以下算法(按考察频率排序): 冒泡排序(B…...
3. CSS中@scope
说说你对 CSS 中scope 的了解 <style>/* scope规则 */scope (#app) {.box {width: 100px;height: 100px;background-color: red;}} </style> <div id"app"><div class"box"></div> </div>CSS 中的scope 是一个相对较新…...
基于雷达和摄像头的无人机轨迹识别与激光照射控制研究
标题:基于雷达和摄像头的无人机轨迹识别与激光照射控制研究 内容:1.摘要 摘要:本文研究了基于雷达和摄像头的无人机轨迹识别与激光照射控制。通过对雷达和摄像头数据的融合处理,实现了对无人机轨迹的精确识别。同时,利用激光照射技术对无人机…...
Response 和 Request 介绍
怀旧网个人博客网站地址:怀旧网,博客详情:Response 和 Request 介绍 1、HttpServletResponse 1、简单分类 2、文件下载 通过Response下载文件数据 放一个文件到resources目录 编写下载文件Servlet文件 public class FileDownServlet exten…...
读 DeepSeek-R1 论文笔记
DeepSeek-R1:通过强化学习激发大语言模型的推理能力 DeepSeek-AI 摘要 我们推出第一代推理模型DeepSeek-R1-Zero和DeepSeek-R1。DeepSeek-R1-Zero作为无需监督微调(SFT)预训练阶段、直接通过大规模强化学习(RL)训练的基础模型,展现出卓越的推理能力。…...
【算法-动态规划】、魔法卷轴: 两次清零机会整个数组最大累加和
【算法-动态规划】、魔法卷轴: 两次清零机会整个数组最大累加和 文章目录 一、dp1.1 题意理解1.2 整体思路1.3 具体思路1.4 代码 二、多语言解法 一、dp 1.1 题意理解 nums 数组, 有正负0, 使用最多两次魔法卷轴, 希望使数组整体的累加和尽可能大. 求尽可能大的累加和 其实就…...
蓝桥杯C语言组:分治问题研究
蓝桥杯C语言组分治问题研究 摘要 本文针对蓝桥杯C语言组中的分治问题展开深入研究,详细介绍了分治算法的原理、实现方法及其在解决复杂问题中的应用。通过对经典例题的分析与代码实现,展示了分治算法在提高编程效率和解决实际问题中的重要作用ÿ…...
npm介绍(Node Package Manager)(JavaScript生态中最流行的包管理工具,主要用于Node.js项目的依赖管理)
文章目录 **核心功能****常用命令****关键文件****npm vs 其他工具****最佳实践**官方资源 npm(Node Package Manager)是 JavaScript 生态中最流行的包管理工具,主要用于 Node.js 项目的依赖管理。以下是核心要点: 核心功能 依赖管…...
小白零基础如何搭建CNN
1.卷积层 在PyTorch中针对卷积操作的对象和使用的场景不同,如有1维卷积、2维卷积、 3维卷积与转置卷积(可以简单理解为卷积操作的逆操作),但它们的使用方法比较相似,都可以从torch.nn模块中调用,需要调用的…...
【分布式架构理论3】分布式调用(1):负载均衡
文章目录 零、三种不同的负载均衡一、常见行业负载均衡方案1. 电商与互联网服务2. 金融与支付系统3. 云计算与分布式存储 二、负载均衡策略概述1. 无状态负载均衡(强调公平性)2. 有状态的负载均衡(强调正确性) 三、 总结 零、三种…...
QT 5.15.2 开发地图ArcGIS 100.15.6(ArcGIS Runtime SDK for Qt)
QT 5.15.2ArcGIS下载 Downloads | ArcGIS Runtime API for Qt | Esri Developer ArcGIS安装(略)参考 Display a map | ArcGIS Maps SDK for Qt | Esri Developer QT新建工程 步骤1 步骤2 步骤3 步骤4(选择Topographic不需要KEY) 步骤5&a…...
细读 React | React Router 路由切换原理
2022 北京冬奥会开幕式 此前一直在疑惑,明明 pushState()、replaceState() 不触发 popstate 事件,可为什么 React Router 还能挂载对应路由的组件呢? 翻了一下 history.js 源码,终于知道原因了。 源码 假设项目路由设计如下&#…...
kubernetes学习-Helm 包管理器(十二)
一、Helm解释 Helm:Kubernetes 的软件包管理器 Helm 被誉为查找、分享及使用 Kubernetes 软件组件的最佳途径。作为 Kubernetes 包的管理工具,Helm 专注于管理名为 chart 的软件包。以下是 Helm 所具备的核心功能: 创建新 chart࿱…...
PbootCMS最新代码注入漏洞(CNVD-2025-01710、CVE-2024-12789)
PbootCMS是一套高效、简洁、 强悍的可免费商用的CMS源码,使用PHPMySQL开发,能够满足各类企业网站开发建设的需要。 国家信息安全漏洞共享平台于2025-01-14公布该程序存在代码注入漏洞。 漏洞编号:CNVD-2025-01710、CVE-2024-12789 影响产品…...
网络安全与AI:数字经济发展双引擎
在2025年年初,一场科技攻防战引发了全球关注。国产人工智能DeepSeek的爆火,伴随着大规模的网络攻击事件,将网络安全的重要性推上了风口浪尖。 在此背景下,我们计划探讨网络安全与人工智能如何为数字经济发展提供强大动力。网络安…...