GC==垃圾收集,他的主要工作分为两部1.检测垃圾 2.清除垃圾
一.检测垃圾
检测垃圾主要通过引用计数法和可达性分析算法来进行
1.1 引用计数法
就是给对象添加一个引用计数器,每当有一个地方引用的时候计数器就+1,当引用失效的时候计数器就-1.
这种方法实现简单,效率很高,但是它很难解决对象之间循环引用的问题
如:
ObjA.C =ObjB ObjB.C =ObjA
这两个对象相互引用,导致计数不为0,无法回收。
1.2 可达性分析算法
通过GC ROOTs的对象作为起点,从这些起点开始向下查找,搜索走过路径(称为引用链),若一个对象的GC ROOTs 没有任何引用链相连,就证明这个对象不可用。
那么GC roots 都用哪些呢
1.虚拟机栈中引用的对象
2.方法区中类静态属性引用的对象
3.方法区中常量引用的对象
4.本地方法栈中JNI(一般说Native方法)应用的对象
二. 回收算法也有如下几种
2.1 标记–清除
标记 和清除两个阶段首先标记出所用要回收的对象,标记完统一回收所有被标记的对象
不足:效率低;标记清除之后会产生大量碎片。
2.2 标记–整理
与上面的方法不同 首先标记出所用要回收的对象,然后让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存
2.3 复制算法
此算法把内存空间划为两个相等的区域,每次只使用其中一个区域。垃圾回收时,遍历当前使用区域,把正在使用中的对象复制到另外一个区域中。此算法每次只处理正在使用中的对象,因此复制成本比较小,同时复制过去以后还能进行相应的内存整理,不会出现“碎片”问题。
不足:需要两倍内存空间
2.4 按分区对待的方式分
(1)增量收集(Incremental Collecting):实时垃圾回收算法,即:在应用进行的同时进行垃圾回收。不知道什么原因JDK5.0中的收集器没有使用这种算法的。
(2)分代收集(Generational Collecting):基于对对象生命周期分析后得出的垃圾回收算法。把对象分为年青代、年老代、持久代,对不同生命周期的对象使用不同的算法(上述方式中的一个)进行回收。现在的垃圾回收器(从J2SE1.2开始)都是使用此算法的。
2.5 按系统线程区分
(1)串行收集
串行收集使用单线程处理所有的垃圾回收工作,无需多线程交互。
优点:容易实现,效率较高,适合单处理器机器
缺点:无法使用多处理器的优势
(2)并行收集
并行收集使用多线程处理垃圾回收工作,因而速度快效率高
适合对吞吐量优先,后台运算而不需要太多交互的任务
吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间)
(3)并发收集
相对于串行收集和并行收集而言,前面两个在进行垃圾回收工作时,需要暂停整个运行环境,而只有垃圾回收程序在运行,因此,系统在垃圾回收时会有明显的暂停,而且暂停时间会因为堆越大而越长。并发收集器不会暂停应用,适合响应时间优先的应用。保证系统的响应时间,减少垃圾收集时的停顿时间。
注意:并行与并发
并行:多条垃圾回收线程同时操作
并发:垃圾回收线程与用户线程一起操作
2.6 常见配置汇总
2.6.1 堆设置
-Xms:初始堆大小
-Xmx:最大堆大小
-XX:NewSize=n:设置年轻代大小
-XX:NewRatio=n:设置年轻代和年老代的比值。默认为2.,表示年轻代与年老代比值为1:2,年轻代占整个年轻代年老代和的1/3
-XX:SurvivorRatio=n:年轻代中Eden区与两个Survivor区的比值。注意Survivor区有两个。默认为8,表示Eden:Survivor=8:2,一个Survivor区占整个年轻代的1/10
-XX:MaxPermSize=n:设置持久代大小
2.6.2 收集器设置
-XX:+UseSerialGC:设置串行收集器
-XX:+UseParallelGC:设置并行收集器
-XX:+UseParalledlOldGC:设置并行年老代收集器
-XX:+UseConcMarkSweepGC:设置并发收集器
2.6.3 并行收集器设置
-XX:ParallelGCThreads=n:设置并行收集器收集时使用的CPU数。并行收集线程数。
-XX:MaxGCPauseMillis=n:设置并行收集最大暂停时间
-XX:GCTimeRatio=n:设置垃圾回收时间占程序运行时间的百分比。公式为1/(1+n)
2.6.4 并发收集器设置
-XX:+CMSIncrementalMode:设置为增量模式。适用于单CPU情况。
-XX:ParallelGCThreads=n:设置并发收集器年轻代收集方式为并行收集时,使用的CPU数。并行收集线程数。
2.6.5 垃圾回收统计信息
-XX:+PrintGC
-XX:+PrintGCDetails
-XX:+PrintGCTimeStamps
-Xloggc:filename
2.7垃圾收集器
2.7.1 七种垃圾收集器:(名称/线程/算法)
名称 | 算法 | 线程 | 范围 |
---|---|---|---|
Serial | 复制 | 串行 | 新生 |
ParNew | 复制 | 并行 | 新生 |
Parallel Scavenge | 复制 | 并行 | 新生 |
Serial old | 标记-整理 | 串行 | 老年 |
CMS | 标记清除 | 并发 | 老年 |
Parallel Old | 标记-整理 | 并行 | 老年 |
G1 | 分代处理 | 并发+并行 | All |
2.7.2 常用的5种组合:
1.Serial/Serial Old
2.ParNew/Serial Old:与上边相比,只是比年轻代多了多线程垃圾回收而已
3.ParNew/CMS:当下比较高效的组合
4.Parallel Scavenge/Parallel Old:自动管理的组合
5.G1:最先进的收集器,但是需要JDK1.7update14以上
2.7.2.1 G1
原理:
- G1收集器将整个堆划分为多个大小相等的Region
- G1跟踪各个region里面的垃圾堆积的价值(回收后所获得的空间大小以及回收所需时间长短的经验值),在后台维护一张优先列表,每次根据允许的收集时间,优先回收价值最大的region,这种思路:在指定的时间内,扫描部分最有价值的region(而不是扫描整个堆内存),并回收,做到尽可能的在有限的时间内获取尽可能高的收集效率。
运作流程:
- 初始标记:标记出所有与根节点直接关联引用对象。需要STW
- 并发标记:遍历之前标记到的关联节点,继续向下标记所有存活节点。在此期间所有变化引用关系的对象,都会被记录在Remember Set Logs中
- 最终标记:标记在并发标记期间,新产生的垃圾。需要STW
- 筛选回收:根据用户指定的期望回收时间回收价值较大的对象(看"原理"第二条)。需要STW
优点:
- 停顿时间可以预测:我们指定时间,在指定时间内只回收部分价值最大的空间,而CMS需要扫描整个年老代,无法预测停顿时间
- 无内存碎片:垃圾回收后会整合空间,CMS采用"标记-清理"算法,存在内存碎片
- 由于只回收部分region,所以STW时间我们可控,所以不需要与用户线程并发争抢CPU资源,而CMS并发清理需要占据一部分的CPU,会降低吞吐量。
- 由于STW,所以不会产生"浮动垃圾"(即CMS在并发清理阶段产生的无法回收的垃圾)
适用范围:
- 追求STW短:若ParNew/CMS用的挺好,就用这个;若不符合,用G1
- 追求吞吐量:用Parallel Scavenge/Parallel Old,而G1在吞吐量方面没有优势
2.7.2.2 Serial/Serial Old:
特点:
- 年轻代Serial收集器采用单个GC线程实现"复制"算法(包括扫描、复制)
- 老年代Serial Old收集器采用单个GC线程实现"标记-整理"算法
- Serial与Serial Old都会暂停所有用户线程(即STW)
说明:
STW(stop the world):编译代码时为每一个方法注入safepoint(方法中循环结束的点、方法执行结束的点,异常),在暂停应用时,需要等待所有的用户线程进入safepoint,之后暂停所有线程,然后进行垃圾回收。
适用场合:
CPU=1,物理内存<2G的机器(简单来讲,单CPU,新生代空间较小且对STW时间要求不高的情况下使用)
2.7.2.3 Parallel Scavenge/Parallel Old:
特点:
- 年轻代Parallel Scavenge收集器采用多个GC线程实现"复制"算法(包括扫描、复制)
- 年老代Parallel Old收集器采用多个GC线程实现"标记-整理"算法
- Parallel Scavenge与Parallel Old都会暂停所有用户线程(即STW)
说明:
- 吞吐量:CPU运行代码时间/(CPU运行代码时间+GC时间)
- CMS主要注重STW的缩短(该时间越短,用户体验越好,所以主要用于处理很多的交互任务的情况)
- Parallel Scavenge/Parallel Old主要注重吞吐量(吞吐量越大,说明CPU利用率越高,所以主要用于处理很多的CPU计算任务而用户交互任务较少的情况)
适用场合:
- 很多的CPU计算任务而用户交互任务较少的情况
- 不想自己去过多的关注GC参数,想让虚拟机自己进行调优工作
- 对吞吐量要求较高,或需要达到一定的量。
2.7.2.4 ParNew/Serial Old:
ParNew除了采用多GC线程来实现复制算法以外,其他都与Serial一样,但是此组合中的Serial Old又是一个单GC线程,所以该组合是一个比较尴尬的组合,在单CPU情况下没有Serial/Serial Old速度快(因为ParNew多线程需要切换),在多CPU情况下又没有之后的三种组合快(因为Serial Old是单GC线程),所以使用其实不多。
-XX:ParallelGCThreads:指定ParNew GC线程的数量,默认与CPU核数相同,该参数在于CMS GC组合时,也可能会用到
2.7.2.5 ParNew/CMS:
说明:
- CMS是多回收线程的,不要被上图误导,默认的线程数:(CPU数量+3)/4
- CMS主要注重STW的缩短(该时间越短,用户体验越好,所以主要用于处理很多的交互任务的情况)
特点:
-
年轻代ParNew收集器采用多个GC线程实现"复制"算法(包括扫描、复制)
-
年老代CMS收集器采用多线程实现"标记-清除"算法
- 初始标记:标记与根集合节点直接关联的节点。时间非常短,需要STW
- 并发标记:遍历之前标记到的关联节点,继续向下标记所有存活节点。时间较长。
- 重新标记:重新遍历trace并发期间修改过的引用关系对象。时间介于初始标记与并发标记之间,通常不会很长。需要STW
- 并发清理:直接清除非存活对象,清理之后,将该线程占用的CPU切换给用户线程 -
初始标记与重新标记都会暂停所有用户线程(即STW),但是时间较短;并发标记与并发清理时间较长,但是不需要STW
缺点:
- 并发清理:在这一过程中,产生的垃圾无法被清理(因为发生在重新标记之后)
- 并发标记与并发清理:
1。降低了吞吐量(即降低了CPU使用率)。
2.由于是与用户线程并发的,在用户线程创建对象时,可能导致老年代中没有空间来进行垃圾回收(没空间不能回收)可以通过-XX:CMSInitiatingOccupancyFraction来指定当年老代空间满了多少后进行垃圾回收 - 标记-清理算法:会产生内存碎片,由于是在老年代,可能会提前触发Full GC(这正是我们要尽量减少的)
适用场合:
- 用于处理很多的交互任务的情况
- 方法区的回收一般使用CMS,配置两个参数:-XX:+CMSPermGenSweepingEnabled与-- XX:+CMSClassUnloadingEnabled
- 适用于一些需要长期运行且对相应时间有一定要求的后台程序