java虚拟机系列 -------GC

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的缩短(该时间越短,用户体验越好,所以主要用于处理很多的交互任务的情况)

特点:

  1. 年轻代ParNew收集器采用多个GC线程实现"复制"算法(包括扫描、复制)

  2. 年老代CMS收集器采用多线程实现"标记-清除"算法
        - 初始标记:标记与根集合节点直接关联的节点。时间非常短,需要STW
        - 并发标记:遍历之前标记到的关联节点,继续向下标记所有存活节点。时间较长。
        - 重新标记:重新遍历trace并发期间修改过的引用关系对象。时间介于初始标记与并发标记之间,通常不会很长。需要STW
        - 并发清理:直接清除非存活对象,清理之后,将该线程占用的CPU切换给用户线程

  3. 初始标记与重新标记都会暂停所有用户线程(即STW),但是时间较短;并发标记与并发清理时间较长,但是不需要STW

缺点:

  • 并发清理:在这一过程中,产生的垃圾无法被清理(因为发生在重新标记之后)
  • 并发标记与并发清理:
    1。降低了吞吐量(即降低了CPU使用率)。
    2.由于是与用户线程并发的,在用户线程创建对象时,可能导致老年代中没有空间来进行垃圾回收(没空间不能回收)可以通过-XX:CMSInitiatingOccupancyFraction来指定当年老代空间满了多少后进行垃圾回收
  • 标记-清理算法:会产生内存碎片,由于是在老年代,可能会提前触发Full GC(这正是我们要尽量减少的)

适用场合:

  • 用于处理很多的交互任务的情况
  • 方法区的回收一般使用CMS,配置两个参数:-XX:+CMSPermGenSweepingEnabled与-- XX:+CMSClassUnloadingEnabled
  • 适用于一些需要长期运行且对相应时间有一定要求的后台程序
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值