七大垃圾收集器
GC算法(可达性分析,复制,标清,标整)是内存回收的方法论,垃圾收集器就是这些方法论的落地实现: 简称 4大算法,7大垃圾收集器
四类垃圾收集器

- Serial 串行收集器:它为单线程环境设计的垃圾回收器,回收垃圾的时候 会暂停所有用户线程, 不适合服务器环境。
- Paraller 并行收集器:多个线程并行收集,此时用户线程也是暂停的。
- CMS 并发收集器:用户线程和收集线程同时执行(不一定是并行,可能交替执行),不需要暂停用户线程,互联网公司大多用这收集器,适用于对相应时间要求高的场景
- G1收集器 将内存分割成不同的区域,然后并发的去收集垃圾。
JVM对参数的缩写:
DefNew 等价于 Default New Generation
Tenured 等价于 Old
ParNew 等价于 Parallel New Generation
PSYoungGen 等价于Parallel Scavenge
ParOldGen 等价于 Parallel Old Generation
Server/Client 模式:
只需要掌握Server模式即可,Client模式基本不会用
32位Windows操作系统,不管什么配置都使用Client模式,
32位其他系统,2G内存同时有2个以上cpu使用Server模式,否则使用Client模式
64位操作系统,使用Server模式
具体的收集器

新生代的GC收集器包含以下三个
- Serial收集器
//收集器公用代码
public class Test {
public static void main(String[] args) throws Exception{
String str = "aaaaa";
byte[] arr = new byte[20*1024*1024];
}
}

串行收集器是最老最稳定效率最高的的收集器,因为会产生较长的停顿,所以现在基本不用。
-Xms8m -Xmx8m -XX:+PrintGCDetails -XX:+PrintCommandLineFlags -XX:+UseSerialGC
上面的参数配置后,JVM会(默认)使用的DefNew+Tenured组合,新生代,老年代都使用串行垃圾器

-
ParNew收集器
ParNew/Serial old组合运行示意图
-Xms8m -Xmx8m -XX:+PrintGCDetails -XX:+PrintCommandLineFlags -XX:+UseParNewGC
-Xms8m -Xmx8m -XX:+PrintGCDetails -XX:+PrintCommandLineFlags -XX:+UseParNewGC -XX:+UseConcMarkSweepGC //老年代指定为CMS收集器
上面的参数配置后,JVM会默认使用的ParNew+Tenured(也可以更改老年代收集器)组合,新生代使用并行垃圾器,老年代使用串行垃圾器
image.png
image.png
-
Parallel Scavenge收集器
image.png
Parallel Scavenge收集器类似于ParNew收集器
它重点关注的是:
1 可控制的吞吐量(代码工作的时间/(代码工作的时间+垃圾收集时间:比如程序运行10分钟,垃圾收集花了1分钟,那么吞吐量是90%))
2 自适应调节策略也是Parallel Scavenge收集器的特点(虚拟机根据当前系统运行情况,动态调整参数以提供最合适的停顿时间或者最大吞吐量)
-Xms8m -Xmx8m -XX:+PrintGCDetails -XX:+PrintCommandLineFlags -XX:+UseParallelGC
-Xms8m -Xmx8m -XX:+PrintGCDetails -XX:+PrintCommandLineFlags -XX:+UseParallelOldGC
-Xms8m -Xmx8m -XX:+PrintGCDetails -XX:+PrintCommandLineFlags //默认情况就是这个组合
上面的三行参数任意一个,JVM会使用的PSYoungGen+ParOldGen组合
image.png
老年代的GC收集器包含以下三个
-
串行GC(Serial Old/Serial MSC)
Serial Old就是Serial 老年代的版本 -
并行GC(Parallel Old/Parallel MSC)
Parallel Old就是Parallel Scavenge老年代的版本,Parallel Old正是为了老年代同样提供吞吐量优先的垃圾器,如果系统对吞吐量要求比较高,可用考虑Parallel Scavenge(PSYoungGen)+Parallel Old组合 -
并发标记清除GC(CMS)
CMS(Concurrent Mark Sweep:并发标记清除)是一种获取最短回收停顿时间为目标的收集器,适合应用在互联网网站或者B/S系统的服务器上,这类应用尤其重视服务器的响应速度,希望系统停顿时间短,CMS非常适合堆内存大,CPU核数多的服务器端应用,也是G1出现之前 大型应用的首先收集器。
image.png
-Xms8m -Xmx8m -XX:+PrintGCDetails -XX:+PrintCommandLineFlags -XX:+UseConcMarkSweepGC
image.png
CMS收集垃圾:分为四个过程
1 初始标记(initial mark):暂停用户线程
只是标记一下GC Roots能直接关联的对象,速度很快,但是仍然需要暂停所有线程
2 并发标记(concurrent mark)和用户线程一起工作
进行GC Roots Tracing 跟踪过程,和用户线程一起工作,不需要暂停工作线程,主要标记过程,标记全部对象
3重新标记(remark):暂停用户线程
为了修正并发标记期间,因用户线程继续运行而导致标记产生变动的那一部分对象的标记记录,因此需要做一次修正,仍然需要暂停所有线程
4 并发清除(concurrent sweep):和用户线程一起工作
清除GC Roots不可达对象,和用户线程一起工作,不需要暂停用户线程,基于标记结果,直接清理对象
四步过程中,由于耗时最长的并发标记和并发清除过程是并发的,没有暂停用户线程,所以总体的来看,CMS收集器的内存回收是和用户线程一起并发工作执行的
说明:由于并发进行,CMS在收集与应用线程会同时增加对堆内存的占用,也就是说,CMS必须要在老年代堆内存用尽之前完成垃圾回收,否则CMS回收失败时,将触发担保机制--使用串行老年代收集器以STW的方式进行一次性GC,从而造成较大的停顿时间。
CMS的优点:并发收集:停顿低
CMS的缺点 :1 并发执行,对cpu资源压力大,2 采用标记-清除算法会导致大量碎片
GC垃圾器组合的选择
- 单CPU或者小内存,单机程序
-XX:+UseSerialGC - 多CPU,需要大的吞吐量,如后台计算型应用
-XX:+UseParallelGC 或者 -XX:+UseParallelOldGC - 多CPU,追求低停顿时间,需要快速处理。如互联网应用
-XX:+UseConcMarkSweepGC
-XX:+ParNewGC
收集器总结:
image.png
G1收集器
上面的六种收集器的特点是:
1 新生代和老年代是各自独立且连续的内存卡
2 新生代收集使用单eden+S0+S1进行复制算法
3 老年代收集必须扫描整个老年代区域
从这几个方面来说,G1完全是全新的收集器。G1收集器的设计目标是取代CMS收集器,在保留CMS收集器的优点下,还有以下改进。
1 G1收集器使用标-整算法(局部使用复制算法),仍然属于分代收集器,不会产生内存碎片。
2 G1的停顿更加可控,G1收集器在停顿时间有预测机制,用户可以指定期望停顿时间。
内存方面来说G1收集器的Eden,Survivor,Tenured内存区域不再是连续的了,而是变成了一个个大小一样的region,每个region从1M到32M不等(JVM在启动的时候会设置这些区域的大小,最多设置2048个区域,所以最大能支持的内存是32M*2048=64G),一个region可能是Eden,Survivor或者Tenured,一个region在A时刻可能是Eden,在B时刻可能是Survivor或者Tenured 其身份是不确定的。

region中新生代,新生代垃圾回收依然暂停所有用户线程,将存活的对象复制到老年代或者Survivor空间中,整理
region中老年代,G1收集器将对象从一个区域复制到另外一个区域 然后整理,完成清理工作,所以 正常的处理过程中 G1完成了堆压缩,这样就不会有碎片的问题了。
G1收集器中还有一个特殊的区域Humongous区域,用于专门存放巨大对象的,如果一个H区存放不下这个巨大对象,那么G1收集器会寻找连续的H分区来存储这个对象,为了找出连续的H区,G1不得不启动Full GC。\
G1运行示意图 初始标记的速度很快很快

配置参数
-Xms8m -Xmx8m -XX:+PrintGCDetails -XX:+PrintCommandLineFlags -XX:+UseG1GC

内存分配规则
- 1 对象优先在Eden区分配
大多数的时候对象是在Eden区分配,当Eden区不够的时候,虚拟机将发起一次Minor GC - 2 大对象直接进入老年代
所谓的大对象是指需要大量连续内存空间的Java对象,比如很长的字符串对象和数组对象,虚拟机提供-XX:PretenureSizeThreshold 参数,让大于这个设置值的对象直接在老年代分配。这样做的目的是避免在Eden和两个Survivor区之间发生大量复制。 - 3 长期存活的对象将进入老年代
虚拟机会给每个对象定义一个年龄计数器,当对象在在Eden区经过15次Minor GC后仍然存活,那么这个时候对象的年龄就是1+15=16岁,16大于阈值15(阈值默认是15),此时对象会被晋升到老年代中。阈值可以通过MaxTenuringThreshold来设置 - 4 动态年龄判断
如果在Survivor空间中,相同年龄(假设为n)的所有对象大小总和大于Survivor空间的一半,那么年龄大于或者等于n的对象就可以直接进入老年代,无需等到MaxTenuringThreshold阈值。 - 5 空间分配担保
发生Minor GC之前虚拟机会检查老年代最大可用连续空间是否大于新生代所有对象总空间,如果这个条件成了,那么此次Minor GC是安全的,如果不成立那么虚拟机会查看担保设置,如果担保设置允许担保失败 那么有可能会尝试一次Minor GC,如果不允许,那么会改为Full GC。
Linux命令查看系统情况
- 全局
top - cpu
vmstat -n 2 3
pidstat -u l -p 5163 +进程号 - 内存
free -m - 硬盘
df -h - 磁盘IO
iostat -xdk 2 3 - 网络IO
ifstat
**Cpu占用过高,如何分析原因? **
import java.util.Random;
//问题代码例子
public class Test {
public static void main(String[] args) throws Exception{
while (true){
System.out.println(new Random().nextInt(100000000));
}
}
}
1 使用top找出cpu占比最高的进程

2 使用ps -ef | grep java 或者jps -l 看看到底是什么样的程序给我们惹麻烦


3 ps -mp 6091 -o THREAD,tid,time 找到6091 这个进程对应的所有线程

4 将上面的线程号转为16进制(小写)的线程号 6092 --> 17cc
5 使用jstack 查看线程堆栈信息
jstack 6091 | grep 17cc -A100 (这里是看一百行)

这里的最终结果是我们的Test.java的第七行代码出现了问题
网友评论