1.什么是空闲列表?
空闲列表(Free List) 是一种内存管理技术,主要用于跟踪和管理堆内存中可用的空闲块。它广泛应用于各种内存分配算法中,特别是在 分段分配(Segmented Allocation) 和 链式分配(Linked Allocation) 策略中。下面详细介绍空闲列表的工作原理及其特点。
空闲列表的基本概念
空闲列表是一种数据结构,用于维护堆内存中所有未被使用的内存块的信息。每个空闲块通常包含以下信息:
- 起始地址:该空闲块在内存中的起始位置。
- 大小:该空闲块的字节数。
- 下一个空闲块指针:指向列表中的下一个空闲块。
通过维护这样一个列表,内存分配器可以快速找到合适的空闲块来满足对象的分配请求,并在释放对象时将相应的内存块重新加入到空闲列表中。
工作原理
- 初始化:
- 当 JVM 启动时,整个堆内存被视为一个大的空闲块,并将其添加到空闲列表中。
- 对象分配:
- 当需要创建新对象时,内存分配器会遍历空闲列表,查找足够大的空闲块。
- 如果找到合适的空闲块,则从该块中分配所需的空间,并更新空闲列表。
- 如果找不到合适的大块,则可能需要触发垃圾回收或调整堆大小。
- 空闲块分割:
- 为了提高内存利用率,分配器可能会将较大的空闲块分割成两部分:一部分用于分配对象,另一部分保留在空闲列表中作为新的空闲块。
- 合并相邻空闲块:
- 当释放对象时,相应的内存块会被标记为空闲,并尝试与相邻的空闲块合并,以减少碎片化。
- 更新空闲列表:
- 分配和释放操作都会导致空闲列表的变化。分配操作会移除或缩小某个空闲块,而释放操作会添加新的空闲块或合并现有块。
示例图解
假设有一个简单的堆内存结构如下:
+-------------------+
| Free | Size: 100 bytes
| |
| |
+-------------------+
| Allocated | Size: 50 bytes
| |
+-------------------+
| Free | Size: 75 bytes
| |
+-------------------+
对应的空闲列表可能如下:
+-------------------+
| Start Address: 0x1000 |
| Size: 100 bytes |
| Next: 0x10080 |
+-------------------+
| Start Address: 0x100D0 |
| Size: 75 bytes |
| Next: null |
+-------------------+
分配对象 A (Size: 30 bytes)
- 查找空闲块:
- 查找第一个空闲块(Start Address: 0x1000, Size: 100 bytes),其大小足够分配对象 A。
- 分配空间:
- 从该空闲块中分配 30 字节的空间给对象 A。
- 更新空闲块的起始地址和大小。
- 更新空闲列表:
- 修改第一个空闲块的起始地址为 0x1001E,大小变为 70 bytes。
结果如下:
+-------------------+
| Free | Size: 70 bytes
| |
| |
+-------------------+
| Allocated | Size: 30 bytes
| [A] |
+-------------------+
| Allocated | Size: 50 bytes
| |
+-------------------+
| Free | Size: 75 bytes
| |
+-------------------+
对应的空闲列表如下:
+-------------------+
| Start Address: 0x1001E |
| Size: 70 bytes |
| Next: 0x10080 |
+-------------------+
| Start Address: 0x10080 |
| Size: 75 bytes |
| Next: null |
+-------------------+
释放对象 A
- 标记为空闲:
- 将对象 A 所占的空间标记为空闲。
- 合并相邻空闲块:
- 检查相邻的空闲块,发现前一个空闲块(Start Address: 0x1001E, Size: 70 bytes)和当前释放的块可以合并。
- 合并这两个空闲块,形成一个新的空闲块。
- 更新空闲列表:
- 移除旧的空闲块,并更新合并后的空闲块信息。
结果如下:
+-------------------+
| Free | Size: 100 bytes
| |
| |
+-------------------+
| Allocated | Size: 50 bytes
| |
+-------------------+
| Free | Size: 75 bytes
| |
+-------------------+
对应的空闲列表如下:
+-------------------+
| Start Address: 0x1000 |
| Size: 100 bytes |
| Next: 0x10080 |
+-------------------+
| Start Address: 0x10080 |
| Size: 75 bytes |
| Next: null |
+-------------------+
特点和优势
- 高效性:
- 空闲列表允许快速查找和分配空闲块,适合动态内存分配场景。
- 灵活性:
- 可以灵活地处理不同大小的对象分配请求。
- 简单实现:
- 相比于其他复杂的内存管理策略,空闲列表的实现相对简单。
- 降低碎片化:
- 通过合并相邻的空闲块,可以有效减少内存碎片化。
注意事项
- 碎片化问题:
- 即使使用空闲列表,长时间运行的应用程序也可能出现内存碎片化的问题。
- 定期进行垃圾回收可以帮助减少碎片化。
- 搜索时间:
- 在大型堆中,查找合适的空闲块可能会消耗较多的时间。
- 可以采用不同的搜索策略(如最佳适应、首次适应等)来优化性能。
- 锁竞争:
- 在多线程环境中,访问空闲列表可能导致锁竞争。
- 可以使用细粒度锁或多线程友好的分配策略来缓解这个问题。
总结
空闲列表是一种有效的内存管理技术,广泛应用于各种内存分配算法中。通过维护一个记录所有空闲块的数据结构,它可以快速地分配和释放内存,从而提高内存利用率和系统性能。
2.什么是TLAB ?
TLAB(Thread Local Allocation Buffer) 是 Java 虚拟机(JVM)中的一种优化技术,主要用于提高多线程环境下对象分配的性能。通过将每个线程的对象分配空间独立化,TLAB 减少了线程间竞争和锁的开销,从而提升了整体吞吐量。下面详细介绍 TLAB 的工作原理及其优势。
TLAB 的基本概念
TLAB 是为每个线程预先分配的一小块内存区域,专门用于该线程创建的对象。当一个线程需要创建新对象时,它首先尝试从自己的 TLAB 中分配内存。只有在 TLAB 不足时,才会请求更多的内存或从共享的堆内存中分配。
工作原理
- 初始化:
- 当 JVM 启动并创建线程时,会为每个线程分配一个 TLAB。
- TLAB 的大小可以根据 JVM 参数进行配置,默认情况下由 JVM 自动调整。
- 对象分配:
- 线程在创建新对象时,首先检查其 TLAB 是否有足够的空间。
- 如果有足够空间,则直接在 TLAB 中分配对象,并移动 TLAB 的顶部指针。
- 如果没有足够空间,则向全局堆请求新的 TLAB 或者直接从堆中分配对象。
- TLAB 回收:
- 当 TLAB 被耗尽时,线程会请求一个新的 TLAB。
- 旧的 TLAB 中的对象会被标记为存活,后续会被垃圾回收器处理。
- 动态调整:
- JVM 可以根据实际使用情况动态调整 TLAB 的大小,以平衡分配效率和内存利用率。
示例图解
假设有一个简单的多线程环境,有两个线程 Thread A 和 Thread B,各自拥有一个 TLAB:
+-------------------+
| Thread A |
| +-------------+ |
| | TLAB | |
| | [Object 1] | |
| | [Object 2] | |
| +-------------+ |
+-------------------+
+-------------------+
| Thread B |
| +-------------+ |
| | TLAB | |
| | [Object 3] | |
| | | |
| +-------------+ |
+-------------------+
分配对象 C (Size: 10 bytes) 到 Thread A
- 检查 TLAB 空间:
- Thread A 检查其 TLAB 是否有足够的空间来分配对象 C(10 bytes)。
- 假设 TLAB 还有足够的空间。
- 分配对象 C:
- 在 TLAB 中分配 10 字节的空间给对象 C。
- 移动 TLAB 的顶部指针。
结果如下:
+-------------------+
| Thread A |
| +-------------+ |
| | TLAB | |
| | [Object 1] | |
| | [Object 2] | |
| | [Object C] | |
| +-------------+ |
+-------------------+
+-------------------+
| Thread B |
| +-------------+ |
| | TLAB | |
| | [Object 3] | |
| | | |
| +-------------+ |
+-------------------+
分配对象 D (Size: 50 bytes) 到 Thread B
- 检查 TLAB 空间:
- Thread B 检查其 TLAB 是否有足够的空间来分配对象 D(50 bytes)。
- 假设 TLAB 不足以容纳对象 D。
- 请求新的 TLAB 或直接分配:
- Thread B 请求一个新的 TLAB 或者直接从全局堆中分配对象 D。
结果如下:
+-------------------+
| Thread A |
| +-------------+ |
| | TLAB | |
| | [Object 1] | |
| | [Object 2] | |
| | [Object C] | |
| +-------------+ |
+-------------------+
+-------------------+
| Thread B |
| +-------------+ |
| | TLAB | |
| | [Object D] | |
| | | |
| +-------------+ |
+-------------------+
特点和优势
- 减少锁竞争:
- 每个线程都有自己独立的 TLAB,减少了对全局堆锁的竞争。
- 大多数对象分配都在本地完成,降低了锁的开销。
- 提高分配速度:
- 使用指针碰撞(Pointer Bumping)技术,在 TLAB 中分配对象非常高效。
- 避免了频繁的锁获取和释放操作,提高了对象分配的速度。
- 降低碎片化:
- TLAB 中的对象通常是连续分配的,减少了内存碎片化的问题。
- 有利于提高缓存命中率,提升整体性能。
- 动态调整:
- JVM 可以根据实际应用的需求动态调整 TLAB 的大小。
- 适应不同的应用场景,优化内存分配策略。
配置参数
JVM 提供了一些参数来控制 TLAB 的行为:
- -XX:+UseTLAB:启用 TLAB(默认启用)。
- -XX:-UseTLAB:禁用 TLAB。
- -XX:TLABSize=:设置 TLAB 的初始大小。
- -XX:TLABRefillWasteFraction=:设置 TLAB 再填充浪费分数。
- -XX:MinTLABSize=:设置 TLAB 的最小大小。
- -XX:MaxTLABSize=:设置 TLAB 的最大大小。
注意事项
- 线程数量:
- 如果线程数量过多,可能会导致大量的 TLAB 分配,增加管理开销。
- 需要根据实际情况调整线程池大小和 TLAB 参数。
- 对象大小:
- 对于非常大的对象,通常不会分配到 TLAB,而是直接从全局堆中分配。
- 需要注意大对象的分配策略,避免不必要的性能开销。
- 垃圾回收:
- TLAB 中的对象在 TLAB 被耗尽后会被标记为存活,参与垃圾回收过程。
- 适当的 TLAB 大小可以帮助减少垃圾回收的压力。
总结
TLAB 是一种重要的内存分配优化技术,通过为每个线程分配独立的内存区域,显著提高了多线程环境下对象分配的性能。通过减少锁竞争、提高分配速度和降低碎片化,TLAB 成为现代 JVM 中不可或缺的一部分。
3.对象头具体都包含哪些内容?
在 Java 虚拟机(JVM)中,对象头(Object Header) 是每个对象都必须包含的一个部分,用于存储与对象相关的重要元数据信息。对象头的结构和内容因 JVM 的具体实现而异,但通常包含以下几个关键部分:
对象头的基本结构
Java 对象头主要由两部分组成:
- Mark Word:用于存储对象的运行时数据。
- Klass Pointer:指向对象所属类的元数据(在某些情况下可以省略)。
1. Mark Word
Mark Word 是对象头中最重要的一部分,用于存储对象的状态信息、哈希码、GC 状态、锁状态等。具体的结构取决于 JVM 的实现,以下是一些常见的字段:
- Hashcode:对象的哈希码值,用于 hashCode() 方法。
- Age:对象的年龄,用于判断对象是否需要晋升到老年代。
- Biased Locking:偏向锁的信息,用于轻量级锁优化。
- Lock Status:对象的锁定状态,包括无锁、偏向锁、轻量级锁、重量级锁等。
- GC Metadata:垃圾回收相关的标志位,如可达性标记等。
- Thread ID:持有偏向锁的线程 ID。
- Epoch:偏向锁的时间戳或版本号。
2. Klass Pointer
Klass Pointer 指向对象所属类的元数据(klass 结构),包含了类的详细信息,如父类、方法表、字段信息等。在某些情况下,特别是压缩指针启用的情况下,Klass Pointer 可能被省略或合并到 Mark Word 中。
具体示例
以下是一个典型的 64 位 JVM 中对象头的结构示意图:
+--------------------------------------------------+
| Mark Word |
|--------------------------------------------------|
| Hashcode | Age | Biased Locking | Lock Status |
+--------------------------------------------------+
| Klass Pointer |
+--------------------------------------------------+
Mark Word 的详细结构
在不同的 JVM 实现中,Mark Word 的具体结构可能会有所不同。以下是 HotSpot JVM 中的一种常见结构:
+--------------------------------------------------+
| hashcode:25 | age:4 | biased_lock:1 | lock:2 |
+--------------------------------------------------+
| thread_id:54 | epoch:2 |
+--------------------------------------------------+
- hashcode:25:对象的哈希码值。
- age:4:对象的年龄,用于分代垃圾回收。
- biased_lock:1:表示是否启用了偏向锁。
- lock:2:表示对象的锁定状态(0b00 表示无锁,0b01 表示偏向锁,0b10 表示轻量级锁,0b11 表示重量级锁)。
- thread_id:54:持有偏向锁的线程 ID。
- epoch:2:偏向锁的时间戳或版本号。
压缩指针
为了节省内存,现代 JVM 通常会启用压缩指针(Compressed Pointers)。压缩指针技术通过使用较小的指针大小来减少内存占用。例如,在 64 位 JVM 上,Klass Pointer 和普通对象引用可以被压缩为 32 位。
启用压缩指针
可以通过以下 JVM 参数启用压缩指针:
- -XX:+UseCompressedOops:启用压缩对象指针。
- -XX:+UseCompressedClassPointers:启用压缩类指针。
示例代码
虽然对象头的内容不能直接通过 Java 代码访问,但我们可以通过一些工具和技术来观察和分析对象头的信息。例如,使用 jol-core 库可以查看对象头的布局和大小。
[<title="使用 jol-core 查看对象头信息">]
import org.openjdk.jol.info.ClassLayout;
public class ObjectHeaderExample {
public static void main(String[] args) {
MyClass obj = new MyClass();
System.out.println(ClassLayout.parseInstance(obj).toPrintable());
}
}
class MyClass {
private int value;
}
运行上述代码,输出可能如下所示:
com.example.ObjectHeaderExample$MyClass object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 12 (object header) N/A
12 4 int MyClass.value N/A
16 4 (loss due to the next object alignment)
Instance size: 20 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
在这个输出中,(object header) 部分代表了对象头的大小和内容。
总结
Java 对象头是每个对象不可或缺的一部分,包含了对象的运行时元数据信息。主要包括 Mark Word 和 Klass Pointer 两个部分。Mark Word 存储了对象的状态信息、哈希码、GC 状态、锁状态等,而 Klass Pointer 指向对象所属类的元数据。通过理解对象头的结构和内容,可以更好地掌握 JVM 内存管理和对象生命周期的相关知识。
4.你知道哪些JVM调优参数?
JVM 调优参数是用于调整 Java 虚拟机的行为和性能的关键设置。通过合理配置这些参数,可以显著提升应用程序的性能、稳定性和资源利用率。以下是一些常用的 JVM 调优参数及其作用:
堆内存调优参数
-
- 初始堆大小 (-Xms)
- 作用:设置 JVM 启动时分配的初始堆内存大小。
示例:
-Xms512m
-
- 最大堆大小 (-Xmx)
- 作用:设置 JVM 允许的最大堆内存大小。
示例:
-Xmx2g
- 3. 新生代大小 (-Xmn)
- 作用:设置新生代(Young Generation)的大小。
示例:
-Xmn768m
- 4. 年轻代 Eden 区大小 (-XX:NewRatio)
- 作用:设置年轻代中 Eden 区与 Survivor 区的比例。默认值为 2,表示 Eden 区占 2/3,每个 Survivor 区占 1/6。
示例:
-XX:NewRatio=3
-
- Survivor 区比例 (-XX:SurvivorRatio)
- 作用:设置 Eden 区与 Survivor 区的比例。默认值为 8,表示 Eden 区占 8/10,每个 Survivor 区占 1/10。
示例:
-XX:SurvivorRatio=6
- 6. 压缩指针 (-XX:+UseCompressedOops)
- 作用:启用压缩对象指针,减少 64 位 JVM 中的对象引用大小。
示例:
-XX:+UseCompressedOops
- 7. 压缩类指针 (-XX:+UseCompressedClassPointers)
- 作用:启用压缩类指针,减少 64 位 JVM 中的类引用大小。
示例:
-XX:+UseCompressedClassPointers
垃圾回收调优参数
- 1. G1 垃圾收集器 (-XX:+UseG1GC)
- 作用:启用 G1 垃圾收集器。
示例:
-XX:+UseG1GC
- 2. CMS 垃圾收集器 (-XX:+UseConcMarkSweepGC)
- 作用:启用 CMS(Concurrent Mark Sweep)垃圾收集器。
示例:
-XX:+UseConcMarkSweepGC
- 3. Parallel GC (-XX:+UseParallelGC)
- 作用:启用 Parallel GC(并行垃圾收集器)。
示例:
-XX:+UseParallelGC
- 4. Parallel Old GC (-XX:+UseParallelOldGC)
- 作用:启用 Parallel Old GC(并行老年代垃圾收集器)。
示例:
-XX:+UseParallelOldGC
- 5. Young GC 线程数 (-XX:ParallelGCThreads)
- 作用:设置 Young GC 使用的线程数。
示例:
-XX:ParallelGCThreads=4
- 6. Old GC 线程数 (-XX:ConcGCThreads)
- 作用:设置 Concurrent GC 使用的线程数。
示例:
-XX:ConcGCThreads=2
- 7. G1 Region 大小 (-XX:G1HeapRegionSize)
- 作用:设置 G1 垃圾收集器中每个 region 的大小。
示例:
-XX:G1HeapRegionSize=16m
- 8. G1 并发标记线程数 (-XX:ConcGCThreads)
- 作用:设置 G1 并发标记阶段使用的线程数。
示例:
-XX:ConcGCThreads=4
- 9. G1 Initiating Heap Occupancy Percent (-XX:InitiatingHeapOccupancyPercent)
- 作用:设置触发并发标记周期的堆占用百分比。
示例:
-XX:InitiatingHeapOccupancyPercent=45
内存映射文件调优参数
- 1. Metaspace 大小 (-XX:MetaspaceSize)
- 作用:设置 Metaspace 的初始大小。
示例:
-XX:MetaspaceSize=256m
- 2. 最大 Metaspace 大小 (-XX:MaxMetaspaceSize)
- 作用:设置 Metaspace 的最大大小。
示例:
-XX:MaxMetaspaceSize=512m
- 3. PermGen 大小 (-XX:PermSize 和 -XX:MaxPermSize)
- 作用:设置永久代(PermGen)的初始和最大大小(已弃用,推荐使用 Metaspace)。
示例:
-XX:PermSize=128m
-XX:MaxPermSize=256m
其他常用参数
- 1. 禁用偏向锁 (-XX:-UseBiasedLocking)
- 作用:禁用偏向锁。
示例:
-XX:-UseBiasedLocking
- 2. 启用逃逸分析 (-XX:+DoEscapeAnalysis)
- 作用:启用逃逸分析优化。
示例:
-XX:+DoEscapeAnalysis
- 3. 禁用逃逸分析 (-XX:-DoEscapeAnalysis)
- 作用:禁用逃逸分析优化。
示例:
-XX:-DoEscapeAnalysis
- 4. 启用 JIT 编译器 (-client 或 -server)
- 作用:选择客户端或服务器模式的 JIT 编译器。
示例:
-client
-server
- 5. 日志输出 (-Xloggc:)
- 作用:将垃圾回收日志输出到指定文件。
示例:
-Xloggc:/path/to/gc.log
- 6. 输出详细 GC 日志 (-verbose:gc)
- 作用:输出详细的垃圾回收日志信息。
示例:
-verbose:gc
- 7. 设置 GC 日志格式 (-XX:+PrintGCDetails)
- 作用:打印详细的 GC 日志信息。
示例:
-XX:+PrintGCDetails
- 8. 设置 GC 日志时间戳 (-XX:+PrintGCTimeStamps)
- 作用:在 GC 日志中添加时间戳。
示例:
-XX:+PrintGCTimeStamps
- 9. 设置 GC 日志日期戳 (-XX:+PrintGCDateStamps)
- 作用:在 GC 日志中添加日期时间戳。
示例:
-XX:+PrintGCDateStamps
- 10. 设置 GC 日志轮转 (-XX:+UseGCLogFileRotation)
- 作用:启用 GC 日志文件轮转。
示例:
-XX:+UseGCLogFileRotation
- 11. 设置 GC 日志文件数量 (-XX:NumberOfGCLogFiles)
- 作用:设置 GC 日志文件的数量。
示例:
-XX:NumberOfGCLogFiles=5
- 12. 设置 GC 日志文件大小 (-XX:GCLogFileSize)
- 作用:设置每个 GC 日志文件的大小。
示例:
-XX:GCLogFileSize=10M
- 13. 设置线程堆栈大小 (-Xss)
- 作用:设置每个线程的堆栈大小。
示例:
-Xss1m
- 14. 设置最大直接内存大小 (-XX:MaxDirectMemorySize)
- 作用:设置 JVM 可以使用的最大直接内存大小。
示例:
-XX:MaxDirectMemorySize=256m
- 15. 设置最大内联方法大小 (-XX:MaxInlineSize)
- 作用:设置方法内联的最大字节码大小。
示例:
-XX:MaxInlineSize=35
- 16. 设置最大内联层次深度 (-XX:MaxRecursiveInlineLevel)
- 作用:设置递归内联的最大层次深度。
示例:
-XX:MaxRecursiveInlineLevel=1
- 17. 设置最大内联方法大小 (-XX:LargeMethodThreshold)
- 作用:设置认为是“大”方法的阈值字节数。
示例:
-XX:LargeMethodThreshold=32768
- 18. 启用 TLAB (-XX:+UseTLAB)
- 作用:启用 Thread Local Allocation Buffers (TLAB)。
示例:
-XX:+UseTLAB
- 19. 设置 TLAB 大小 (-XX:TLABSize)
- 作用:设置 TLAB 的初始大小。
示例:
-XX:TLABSize=1m
- 20. 设置 TLAB 最大浪费比例 (-XX:TLABRefillWasteFraction)
- 作用:设置 TLAB 再填充时允许的最大浪费比例。
示例:
-XX:TLABRefillWasteFraction=64
示例
以下是一个综合示例,展示了如何配置一些常用的 JVM 调优参数:
java -Xms512m -Xmx2g -Xmn768m -XX:NewRatio=3 -XX:SurvivorRatio=6 \
-XX:+UseG1GC -XX:G1HeapRegionSize=16m -XX:ConcGCThreads=4 \
-XX:InitiatingHeapOccupancyPercent=45 -XX:+UseCompressedOops \
-XX:+UseCompressedClassPointers -XX:MetaspaceSize=256m \
-XX:MaxMetaspaceSize=512m -XX:+DoEscapeAnalysis -XX:+UseTLAB \
-Xss1m -Xloggc:/path/to/gc.log -verbose:gc -XX:+PrintGCDetails \
-XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -XX:+UseGCLogFileRotation \
-XX:NumberOfGCLogFiles=5 -XX:GCLogFileSize=10M MyApplication
5.说一下 JVM 有哪些垃圾回收器?
Java 虚拟机(JVM)提供了多种垃圾回收器(Garbage Collectors),每种垃圾回收器都有其独特的特性和适用场景。以下是 JVM 中常见的几种垃圾回收器及其详细说明:
1. Serial Garbage Collector
适用场景:
- 单线程环境。
- 简单的应用程序或小型系统。
特点:
- 单线程:使用单个线程进行垃圾回收,会导致应用程序在垃圾回收期间暂停(Stop-The-World, STW)。
- 简单高效:适用于内存较小且不需要高并发的应用程序。
- 低开销:由于只有一个线程参与垃圾回收,开销较低。
启用参数:
-XX:+UseSerialGC
示例
java -XX:+UseSerialGC MyApplication
2. Parallel Garbage Collector (Throughput Collector)
适用场景:
- 多核处理器。
- 需要高吞吐量的应用程序。
- 对延迟要求不高。
特点:
- 多线程:使用多个线程并行执行垃圾回收,减少 STW 时间。
- 高吞吐量:适合需要最大化 CPU 使用率的批处理作业。
- 可配置性:可以通过参数调整线程数和堆大小。
启用参数:
-XX:+UseParallelGC
示例
java -XX:+UseParallelGC -XX:ParallelGCThreads=4 -Xmx2g MyApplication
3. Concurrent Mark-Sweep (CMS) Garbage Collector
适用场景:
- 需要低延迟的应用程序。
- 实时系统或交互式应用。
- 内存较大且对停顿时间敏感。
特点:
- 并发收集:大部分工作在应用程序运行时并发执行,减少 STW 时间。
- 低停顿:适合需要快速响应的应用程序。
- 碎片化问题:长时间运行可能导致严重的内存碎片化。
- 已弃用:从 JDK 9 开始,CMS 收集器已被标记为过时,并将在未来的版本中移除。
启用参数:
-XX:+UseConcMarkSweepGC
示例
java -XX:+UseConcMarkSweepGC -Xmx2g MyApplication
4. G1 Garbage Collector
适用场景:
- 大型堆内存。
- 需要平衡吞吐量和延迟的应用程序。
- 对停顿时间有一定要求。
特点:
- 分区收集:将堆内存划分为多个区域(regions),按需进行垃圾回收。
- 并发与并行:结合了 CMS 和 Parallel GC 的优点,支持并发标记和并行清除。
- 预测性:能够预测未来的停顿时间,动态调整回收行为。
- 低停顿:适合需要低延迟的应用程序。
- 自动调优:内置了许多自适应机制,减少了手动调优的需求。
启用参数:
-XX:+UseG1GC
示例
java -XX:+UseG1GC -Xmx4g -XX:MaxGCPauseMillis=200 MyApplication
5. Z Garbage Collector (ZGC)
适用场景:
- 极大堆内存(数百 GB 到 TB)。
- 对停顿时间有严格要求的实时系统。
- 高吞吐量需求。
特点:
- 低停顿:承诺不超过 10 毫秒的停顿时间,即使在大规模堆内存下也是如此。
- 并发收集:几乎所有的垃圾回收工作都是并发执行的。
- 压缩堆:消除内存碎片,提高内存利用率。
- 低延迟:非常适合需要极低延迟的应用程序。
- 实验性:从 JDK 11 开始引入,仍在不断发展和完善。
启用参数:
-XX:+UseZGC
示例
java -XX:+UseZGC -Xmx1t MyApplication
6. Shenandoah Garbage Collector
适用场景:
- 大型堆内存。
- 对停顿时间有严格要求的实时系统。
- 高吞吐量需求。
特点:
- 低停顿:承诺不超过 10 毫秒的停顿时间。
- 并发收集:大多数垃圾回收工作是并发执行的。
- 压缩堆:通过压缩技术减少内存碎片。
- 无分代:采用不分代的设计,简化了垃圾回收过程。
- 成熟稳定:经过多年的开发和优化,已在生产环境中广泛应用。
启用参数:
-XX:+UseShenandoahGC
示例
java -XX:+UseShenandoahGC -Xmx4g MyApplication
7. Epsilon Garbage Collector (NoOp GC)
适用场景:
- 测试和基准测试。
- 不需要垃圾回收的应用程序(如嵌入式系统)。
特点:
- 无操作:不进行任何垃圾回收,适用于不需要垃圾回收的特殊场景。
- 内存泄漏:如果不小心使用,可能会导致内存泄漏。
- 简单高效:适用于简单的测试环境或特定应用场景。
启用参数:
-XX:+UseEpsilonGC
示例
java -XX:+UseEpsilonGC -Xmx512m MyApplication
总结
选择合适的垃圾回收器取决于具体的应用需求和硬件资源。以下是一些常见的选择建议:
- 单线程或小型应用:使用 Serial GC。
- 多核处理器、高吞吐量需求:使用 Parallel GC。
- 低延迟、实时系统:考虑 G1 GC 或 Shenandoah GC。
- 极大堆内存、超低延迟:使用 ZGC 或 Shenandoah GC。
- 测试和基准测试:使用 Epsilon GC。