1.垃圾回收算法
1.1 标记-清除算法
- 标记-清除算法是现代垃圾回收算法的思想基础。
- 标记-清除算法将垃圾回收分为两个阶段:
- 标记阶段:首先通过根节点,标记所有从根节点开始可达的对象,未被标记的对象就是未被引用的垃圾对象
- 清除阶段:清除所有未被标记的对象
- 此种方法的两个问题:
- 效率问题
- 标记和清除两个过程的效率都不高
- 空间问题:
- 标记清除之后会产生大量不连续的内存碎片,空间碎片太多会导致以后在程序运行过程中需要分配较大的对象时,无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。
- 效率问题
1.2 复制s
2.垃圾收集器
2.1 概述
- 问题1:垃圾回收算法和垃圾回收器有什么关系?
- 垃圾回收算法是垃圾回收的方法论,垃圾收集器是垃圾回收算法的具体实现
- 问题2:为什么有这么多种垃圾回收器?
- Java虚拟机规范中对垃圾收集器应该如何实现并没有任何规定,
- 因此不同厂商、不同版本的虚拟机所提供的垃圾收集器都有可能有很大差别
- 目前为止,还没有完美的收集器出现,Java的应用场景很多,没有万能的收集器能解决所有应用场景,只是针对具体应用选择最合适的收集器,进行分代收集
2.2 七大垃圾收集器
代码后结果的参数提前说明:
- DefNew:Defalut New Generation,默认新生代
- Tenured:Old,老年代
- ParNew:Parallel New Generation,在新生代用并行回收
- PSYoungGen:Parallel Scavenge
- ParOldGen:Parallel Old Generation,在老年代用并行回收
JVM中Server/Client分别是什么意思:
- 适用范围:只需要掌握Server模式,Client基本不会用
- 操作系统:
- 32位Window操作系统,不论硬件如何都默认使用Client的JVM模式
- 32位其他操作系统,2G内存同时有2个CPU以上使用的是Server模式,低于该配置还是Client模式
- 64位的操作系统都是Server模式
2.2.1 新生代垃圾收集器
(1)Serial收集器(新生代串行GC/Servial Copying)
- 它是最早使用的一个收集器,在JDK1.3.1之前是唯一的选择
- 一个单线程的收集器:即在垃圾回收的时候只会使用一个CPU或一个收集线程完成垃圾回收工作
- 在进行垃圾收集的时候,必须暂停其他所有的工作线程直到它收集结束
- 如图:
- 一对一:新生代和老年代都为单线程
- 说明:STW即为Stop The World,即暂停所有应用程序线程
优点:
- 简单而高效
- 对于限定单个CPU环境来说,没有线程交互的开销可以获得最高的单线程垃圾收集效率
缺点:
- 收集期间需要暂停所有应用线程,用户体验不好
应用场景:
- Java虚拟机运行在Client模式下默认的新生代垃圾收集器
对应JVM参数是:-XX:+UseSerialGC
-
当我们使用此参数开启Serial,老年代默认会开启Serial Old
- 即开启后会使用:Serial(Young区用)+Serial Old(Old区用)的收集器组合
- 表示新生代和老年代都会使用串行回收收集器
- 新生代使用复制算法,老年代使用标记-整理算法
示例代码演示:
import java.util.Random;
public class GCDemo {
public static void main(String[] args) {
System.out.println("=====GCDemo,Hello====");
try {
String str = "GCDemo";
while (true){
str += str + new Random().nextInt(77777777) + new Random().nextInt(88888888);
str.intern();
}
}catch (Throwable e){
e.printStackTrace();
}
}
}
配置JVM参数:
结论:DefNew+Tenured
(2)ParNew(新生代并行GC)
- 使用多线程进行垃圾回收
- 在进行垃圾收集的时候,必须暂停其他所有的工作线程直到它收集结束
- 如图:
- ParNew收集器其实是Serial收集器新生代的并行多线程版本
应用场景:
- 最常见的应用场景是配合老年代的CMS GC工作,其余的行为和Serial收集器完全一样
- 它是很多Java虚拟机运行在Server模式下新生代的默认垃圾收集器
对应JVM参数是:-XX:+UseParNewGC
- 启用ParNewGC收集器,只影响新生代的收集,不影响老年代
- 开启后会使用:ParNew(Young区用)+Serial Old(Old区用)的收集器组合
- 新生代使用复制算法,老年代使用标记-整理算法
备注:
- -XX:ParallelGCThreads 限制线程数量,默认开启和CPU数目相同的线程数
示例代码演示:
import java.util.Random;
public class GCDemo {
public static void main(String[] args) {
System.out.println("=====GCDemo,Hello====");
try {
String str = "GCDemo";
while (true){
str += str + new Random().nextInt(77777777) + new Random().nextInt(88888888);
str.intern();
}
}catch (Throwable e){
e.printStackTrace();
}
}
}
配置JVM参数:
结论:ParNew+Tenured,但是这种组合已经不再推荐被使用
(3)Parallel(并行回收GC/Parallel Scavenge)
- Parallel Scavenge收集器类似ParNew也是一个新生代垃圾收集器
- 使用复制算法
- 也是一个并行的多线程的垃圾收集器,俗称吞吐量优先收集器
- 多对多:新生代和老年代均为多线程
有了ParNew为什么还要Parallel?
- Parallel重点关注的是:可控制的吞吐量
- 吞吐量=运行用户代码的时间/(运行用户代码时间+垃圾收集时间)
- 即比如程序运行100分钟,垃圾收集时间位1分钟,吞吐量为99%
-
高吞吐量意味着高效利用CPU的时间,它多用于在后台运算而不需要太多交互的任务
- 比如你在前台下个单,它停顿来算,交互性差,而对于科学计算,它自己在后台计算,停顿一会我们也不知道,但是它高效利用了CPU
-
自适应调节策略也是Parallel Scavenge收集器与ParNew收集器的一个重要区别
- 自适应调节策略:虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整这些参数以提供最合适的停顿时间(-XX:MaxGCPauseMillis)或最大的吞吐量
对应JVM参数是:-XX:+UseParallelGC或-XX:+UseParallelOldGC(可互相激活,即配置其中一个,另一个会自动连带激活)
备注:
- -XX:ParallelGCThreads=数字N 表示启动多少个GC线程
- cpu>8: N=5/8
- cpu<8: N=实际个数
示例代码演示:
import java.util.Random;
public class GCDemo {
public static void main(String[] args) {
System.out.println("=====GCDemo,Hello====");
try {
String str = "GCDemo";
while (true){
str += str + new Random().nextInt(77777777) + new Random().nextInt(88888888);
str.intern();
}
}catch (Throwable e){
e.printStackTrace();
}
}
}
配置JVM参数:
结论:PSYoungGen+ParOldGen
2.2.2 老年代垃圾收集器
(1)Parallel Old收集器
- Parallel Old收集器是Parallel Scavenge的老年代版本
- 使用多线程的标记-整理算法
-
Parallel Old收集器在JDK1.6才开始提供
- 在JDK1.6之前,新生代使用ParallelScavenge收集器只能搭配老年代的Serial Old收集器,只能保证新生代的吞吐量优先,无法保证整体的吞吐量
- Parallel Old正是为了在老年代同样提供吞吐量优先的垃圾收集器,如果系统对吞吐量要求较高,JDK1.8后可以优先考虑新生代Parallel Scavenge和老年代Parallel Old收集器的搭配策略
对应JVM参数是:-XX:+UseParallelGC或-XX:+UseParallelOldGC(可互相激活,即配置其中一个,另一个会自动连带激活)
示例代码演示1:
import java.util.Random;
public class GCDemo {
public static void main(String[] args) {
System.out.println("=====GCDemo,Hello====");
try {
String str = "GCDemo";
while (true){
str += str + new Random().nextInt(77777777) + new Random().nextInt(88888888);
str.intern();
}
}catch (Throwable e){
e.printStackTrace();
}
}
}
配置JVM参数:
结论:PSYoungGen+ParOldGen
示例代码演示2:
import java.util.Random;
public class GCDemo {
public static void main(String[] args) {
System.out.println("=====GCDemo,Hello====");
try {
String str = "GCDemo";
while (true){
str += str + new Random().nextInt(77777777) + new Random().nextInt(88888888);
str.intern();
}
}catch (Throwable e){
e.printStackTrace();
}
}
}
配置JVM参数:
- 不配置垃圾回收器的任何参数
结论:系统默认是PSYoungGen+ParOldGen
(2)CMS收集器(并发标记清除GC)
- CMS收集器(Concurrent Mark Sweep:并发标记清除)
- 是一种以获取最短回收停顿时间为目标的收集器
应用场景:
- 适合应用在互联网站或者B/S系统的服务器上
- 这类应用尤其重视服务器的响应速度,希望系统停顿时间最短
- CMS非常适合堆内存大、CPU核数多的服务器端应用,也是G1出现之前大型应用的首选收集器