如果说垃圾回收算法是内存回收的方法论,那么垃圾收集器就是具体实现。jvm会结合针对不同的场景及用户的配置使用不同的收集器。
Serial垃圾回收器
现代的商用虚拟机的都是采用分代收集的,不同的区域用不同的收集器。常用的7种收集器,其适用的范围如图所示
年轻代收集器
Serial、ParNew、Parallel Scavenge
老年代收集器
Serial Old、Parallel Old、CMS收集器
特殊收集器
G1收集器[新型,不在年轻、老年代范畴内]
Serial
Serial 收集器是最基础、历史最悠久的收集器,曾经(JDK1.3.1之前) 是HotSpot 新生代收集器的唯一选择,对应的老年代是 Serial Old 收集器。
Serial:基于复制算法,Serial Old:基于标记-整理算法。
两大特点:
1.使用一个处理器或一条收集线程取完成垃圾收集工作;
2.进行垃圾收集时,必须暂停其他所有工作线程,直到它收集结束。
缺点:
多线程环境下,效率低下。
优点:
1.简单而高效,迄今为止仍然时HotSpot 虚拟机在客户端模式下的默认新生代收集器;
2.在所有收集器里额外内存消耗最小。
查看JVM使用的默认的垃圾收集器
cmd执行命令:
java -XX:+PrintCommandLineFlags -version
输出如下(举例):
C:\Users\wang>java -XX:+PrintCommandLineFlags -version
-XX:InitialHeapSize=265625408 -XX:MaxHeapSize=4250006528 -XX:+PrintCommandLineFlags -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC
java version "1.8.0_121"
Java(TM) SE Runtime Environment (build 1.8.0_121-b13)
Java HotSpot(TM) 64-Bit Server VM (build 25.121-b13, mixed mode)
自JDK7u4开始的JDK7u系列与JDK8系列,如果指定了:-XX:+UseParallelGC,则会默认开启:XX:+UseParallelOldGC 。
验证默认垃圾回收器
简单的controller代码
@RestController
@RequestMapping("/jvm")
@Slf4j
public class JvmController {
static List<Object> list = new ArrayList<>();
@RequestMapping("/addlist")
public void lust(@RequestParam("value") Integer value) throws InterruptedException {
byte[] bytes = new byte[value * 1024 * 1024];
list.add(bytes);
}
@RequestMapping("/clearlist")
public void clearlist() throws InterruptedException {
list.clear();
}
}
JVM参数,显式使用-XX:+UseParallelGC
-Xms100M
-Xmx100M
-Xmn70M
-XX:PretenureSizeThreshold=5M
-XX:+UseParallelGC
-XX:+PrintGCDetails
-XX:+PrintGCTimeStamps
-XX:+PrintGCDateStamps
-XX:+PrintGCCause
-Xloggc:gc-old.log
-verbose:gc
启动SpringBoot项目
C:\Users\wang>jps
14864 RemoteJdbcServer
16324
1716 Launcher
21492 Jps
10492 Application
10668 RemoteJdbcServer
10796 RemoteMavenServer36
12668
C:\Users\wang>jmap -heap 10492
Attaching to process ID 10492, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.121-b13
using thread-local object allocation.
Parallel GC with 8 thread(s)
Heap Configuration:
MinHeapFreeRatio = 0
MaxHeapFreeRatio = 100
MaxHeapSize = 104857600 (100.0MB)
NewSize = 73400320 (70.0MB)
MaxNewSize = 73400320 (70.0MB)
OldSize = 31457280 (30.0MB)
NewRatio = 2
SurvivorRatio = 8
MetaspaceSize = 21807104 (20.796875MB)
CompressedClassSpaceSize = 1073741824 (1024.0MB)
MaxMetaspaceSize = 17592186044415 MB
G1HeapRegionSize = 0 (0.0MB)
Heap Usage:
PS Young Generation
Eden Space:
capacity = 31457280 (30.0MB)
used = 14946488 (14.254081726074219MB)
free = 16510792 (15.745918273925781MB)
47.51360575358073% used
From Space:
capacity = 20971520 (20.0MB)
used = 13754640 (13.117446899414062MB)
free = 7216880 (6.8825531005859375MB)
65.58723449707031% used
To Space:
capacity = 20971520 (20.0MB)
used = 0 (0.0MB)
free = 20971520 (20.0MB)
0.0% used
PS Old Generation
capacity = 31457280 (30.0MB)
used = 20363760 (19.420394897460938MB)
free = 11093520 (10.579605102539062MB)
64.73464965820312% used
20519 interned Strings occupying 1861040 bytes.
然后我们不断地调用
http://localhost/jvm/addlist?value=10
很容易就能触发old gc。
此时,查看我们的gc日志,下边红色我标出了ParOldGen字样:
2022-03-01T11:14:24.031+0800: 451.667: [Full GC (Ergonomics) [PSYoungGen: 12566K->3792K(51712K)] [ParOldGen: 30158K->30275K(30720K)] 42725K->34067K(82432K), [Metaspace: 53823K->53480K(1099776K)], 0.1418099 secs] [Times: user=0.70 sys=0.00, real=0.14 secs]
2022-03-01T11:14:25.402+0800: 453.037: [Full GC (Ergonomics) [PSYoungGen: 25764K->23188K(51712K)] [ParOldGen: 30275K->27887K(30720K)] 56039K->51076K(82432K), [Metaspace: 53480K->53480K(1099776K)], 0.1561581 secs] [Times: user=0.69 sys=0.00, real=0.16 secs]
2022-03-01T11:14:25.559+0800: 453.194: [Full GC (Allocation Failure) [PSYoungGen: 23188K->23176K(51712K)] [ParOldGen: 27887K->27720K(30720K)] 51076K->50897K(82432K), [Metaspace: 53480K->53457K(1099776K)], 0.2252083 secs] [Times: user=0.91 sys=0.00, real=0.22 secs]
2022-03-01T11:14:25.986+0800: 453.621: [Full GC (Ergonomics) [PSYoungGen: 30028K->23978K(51712K)] [ParOldGen: 27720K->27661K(30720K)] 57749K->51639K(82432K), [Metaspace: 53928K->53928K(1099776K)], 0.2050496 secs] [Times: user=0.70 sys=0.03, real=0.20 secs]
说明启用了Parallel Old这个老年代收集器。
使用Jconsole连接该程序,切换到VM概要这个tab,注意下图红圈圈出来的地方:
JVM参数 显式使用-XX:+UseSerialGC
-Xms100M
-Xmx100M
-Xmn70M
-XX:PretenureSizeThreshold=5M
-client
-XX:+UseSerialGC
-XX:+PrintGCDetails
-XX:+PrintGCTimeStamps
-XX:+PrintGCDateStamps
-XX:+PrintGCCause
-Xloggc:gc-old.log
-verbose:gc
gc日志如下:
Java HotSpot(TM) 64-Bit Server VM (25.121-b13) for windows-amd64 JRE (1.8.0_121-b13), built on Dec 12 2016 18:21:36 by "java_re" with MS VC++ 10.0 (VS2010)
Memory: 4k page, physical 16601588k(5142084k free), swap 26038772k(4551612k free)
CommandLine flags: -XX:-BytecodeVerificationLocal -XX:-BytecodeVerificationRemote -XX:InitialHeapSize=104857600 -XX:+ManagementServer -XX:MaxHeapSize=104857600 -XX:MaxNewSize=73400320 -XX:NewSize=73400320 -XX:PretenureSizeThreshold=5242880 -XX:+PrintGC -XX:+PrintGCCause -XX:+PrintGCDateStamps -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:TieredStopAtLevel=1 -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseSerialGC
2022-03-01T11:23:14.044+0800: 1.617: [GC (Allocation Failure) 2022-03-01T11:23:14.044+0800: 1.618: [DefNew: 57344K->5487K(64512K), 0.0091428 secs] 57344K->5487K(95232K), 0.0093811 secs] [Times: user=0.02 sys=0.00, real=0.01 secs]
2022-03-01T11:23:14.527+0800: 2.100: [GC (Allocation Failure) 2022-03-01T11:23:14.527+0800: 2.100: [DefNew: 62831K->5807K(64512K), 0.0139393 secs] 62831K->8566K(95232K), 0.0139920 secs] [Times: user=0.01 sys=0.00, real=0.01 secs]
2022-03-01T11:23:14.643+0800: 2.216: [Full GC (Metadata GC Threshold) 2022-03-01T11:23:14.643+0800: 2.216: [Tenured: 2758K->6966K(30720K), 0.0211325 secs] 28297K->6966K(95232K), [Metaspace: 20450K->20450K(1067008K)], 0.0212119 secs] [Times: user=0.01 sys=0.00, real=0.02 secs]
此时,新生代是DefNew,老年代是Tenured,明显和前面使用了 -XX:+UseParallelGC时候不一样。
引用