JVM垃圾收集器 Java虚拟机(JVM)的垃圾收集(GC)是自动内存管理的核心机制,理解不同的垃圾收集器及其工作原理对于编写高性能Java应用至关重要。
常见垃圾收集器
Serial收集器 特点 :
单线程收集器
新生代使用标记-复制算法
老年代使用标记-整理算法
适合客户端应用或小内存环境
启用参数 :
代码示例:
1 2 3 4 5 6 7 8 9 10 11 public class SerialGCExample { public static void main (String [] args ) { for (int i = 0 ; i < 1000000 ; i++) { String temp = new String ("Object-" + i); if (i % 10000 == 0 ) { System .gc (); } } } }
Parallel/Throughput收集器 特点 :
多线程收集器(并行收集)
新生代使用标记-复制算法
老年代使用标记-整理算法
注重吞吐量(Throughput)
启用参数 :
1 2 -XX :+UseParallelGC -XX :+UseParallelOldGC
代码示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class ParallelGCExample { private static final List<byte []> LIST = new ArrayList<>(); public static void main (String[] args ) throws InterruptedException { while (true ) { byte [] data = new byte [1024 * 1024 ]; LIST.add (data); if (LIST.size() > 100 ) { LIST.clear(); } Thread.sleep(10 ); } } }
CMS(Concurrent Mark-Sweep)收集器 特点 :
以获取最短回收停顿时间为目标
老年代使用并发标记-清除算法
与用户线程并发执行大部分工作
Java 14中被移除
工作阶段 :
初始标记(Initial Mark)
并发标记(Concurrent Mark)
重新标记(Remark)
并发清除(Concurrent Sweep)
启用参数 :
代码示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class CMSGCExample { private static final Map <Integer , String > CACHE = new HashMap <>(); public static void main (String [] args ) { for (int i = 0 ; i < 1000000 ; i++) { CACHE .put (i, "Value-" + i); if (i % 10000 == 0 ) { System .out .println ("Cache size: " + CACHE .size ()); CACHE .entrySet ().removeIf (entry -> entry.getKey () < i - 50000 ); } } } }
G1(Garbage-First)收集器特点
Java 9+的默认收集器
将堆划分为多个大小相等的Region
可预测的停顿时间模型
同时管理新生代和老年代
采用标记-整理和复制算法
启用参数:
配置参数:
1 2 3 -XX :MaxGCPauseMillis =200 -XX :G1HeapRegionSize=N -XX :InitiatingHeapOccupancyPercent =45
代码示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 public class G1GCExample { private static final List<byte []> BIG_OBJECTS = new ArrayList<>(); private static final List<byte []> SMALL_OBJECTS = new ArrayList<>(); public static void main (String[] args ) throws InterruptedException { new Thread(() -> { while (true ) { byte [] big = new byte [1024 * 1024 * 10 ]; BIG_OBJECTS.add (big); if (BIG_OBJECTS.size() > 10 ) { BIG_OBJECTS.clear(); } } }).start(); new Thread(() -> { while (true ) { byte [] small = new byte [1024 ]; SMALL_OBJECTS.add (small); if (SMALL_OBJECTS.size() > 10000 ) { SMALL_OBJECTS.clear(); } } }).start(); Thread.sleep(Long.MAX_VALUE); } }
ZGC和Shenandoah ZGC特点 :
低延迟(目标<10ms)
可扩展(支持TB级堆)
并发处理(大部分工作与应用线程并发)
Java 15开始生产可用
启用参数 :
Shenandoah特点:
启用参数 :
代码示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class ZGCShenandoahExample { private static final int SIZE = 1000000 ; private static final Object[] HOLDER = new Object [SIZE]; public static void main (String[] args) throws InterruptedException { for (int i = 0 ; i < SIZE; i++) { HOLDER[i] = new byte [1024 ]; if (i % 1000 == 0 ) { Thread.sleep(1 ); } } Random random = new Random (); while (true ) { int index = random.nextInt(SIZE); HOLDER[index] = new byte [1024 ]; } } }
如何选择合适的垃圾收集器
吞吐量优先 :Parallel GC
低延迟优先 :G1 GC(堆<6GB)、ZGC/Shenandoah(堆>6GB)
小内存/客户端应用 :Serial GC
CMS :已过时,不推荐使用
监控和分析GC 启用GC日志 1 2 3 4 -XX :+PrintGCDetails -XX :+PrintGCDateStamps -XX :+PrintGCTimeStamps -Xloggc :<file-path>
Java 9+更推荐使用 1 -Xlog :gc* :file=<file-path> :time :filecount= 5 ,filesize=10M
使用JVM工具 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class GCMonitoringExample { public static void main (String[] args ) { System.out .println("Max Memory: " + Runtime.getRuntime().maxMemory() / 1024 / 1024 + "MB" ); System.out .println("Total Memory: " + Runtime.getRuntime().totalMemory() / 1024 / 1024 + "MB" ); System.out .println("Free Memory: " + Runtime.getRuntime().freeMemory() / 1024 / 1024 + "MB" ); List<byte []> list = new ArrayList<>(); for (int i = 0 ; i < 100 ; i++) { byte [] data = new byte [1024 * 1024 ]; list.add (data); if (i % 10 == 0 ) { System.out .println("After allocation " + (i + 1 ) + "MB" ); System.out .println("Free Memory: " + Runtime.getRuntime().freeMemory() / 1024 / 1024 + "MB" ); } } } }
GC调优建议
避免频繁Full GC
合理设置新生代大小(-Xmn)
避免大对象直接进入老年代
合理设置堆大小
初始堆(-Xms)和最大堆(-Xmx)设为相同值
避免堆自动扩展带来的性能波动
注意对象分配模式
选择合适的GC算法
监控和观察
使用VisualVM、JConsole、GC日志等工具持续监控