相对全面的gc总结

首先是大家都要提到的GC的基础算法:标记清除,标记整理,复制,分代。这些算法的第一步都是做的一件事: 标记(Mark)。

JVM的标记算法采用了根搜索算法(Root Tracing)。根有几种:

1. JVM栈的Frame里面的引用

2. 静态类,常量的引用

3. 本地栈中的引用

4. 本地方法的引用

5.分代式GC是一种部分收集的做法,在执行部分收集时,从GC堆的非收集部分指向收集部分的引用,也必须作为GC roots的一部分
    具体到分两代的分代式GC来说,如果young gc只收集young区的垃圾,则old gen属于非收集部分,那么从old gen指向young gen的引用就必须作为young GC的GC roots的一部分。

有些年轻代回收时,即使看到了回收日志,也不能够回收空间的原因?
ParNew的实现中,有个判断是:
如果旧生代剩余的空间(available)大于新生代中使用的空间(max_promotion_in_bytes),或者大于之前平均晋升的old的大小(av_promo)就能正常进行YGC,
否则 仅仅打印日志,但不进行回收(YGC悲观策略)


一般我们能控制的就是JVM栈中的引用和静态类,常量的引用。标记也分为几个阶段,比如

1. 标记直接和根引用的对象

2. 标记间接和根引用的对象

3. 由于分代算法,被老年代对象所引用的新生代的对象



对于第三种,JVM采用了Card Marking(卡片标记)的方法,避免了在做Minor GC时需要对整个老年代扫描。具体的方法如下:

1. 将老年代的内存分片,1个片默认是512byte

2. 如果老年代的对象发生了修改,就把这个老年代对象所在的片标记为脏 dirty。或者老年代对象指向了新生代对象,那么它所在的片也会被标记为dirty

3. 没有标记为脏的老年代片它没有指向新的新生代对象,所以可以不需要去扫描

4. Minor GC扫描老年代空间时,只需要去扫描脏的卡片的对象,不需要扫描整个老年代空间

所以做Minor GC时标记的时间 = T(stack_scan) + T(card_scan) + T(old_root_scan).

T(stack_scan): 级联扫描在JVM栈里的根的时间

T(card_scan): 级联扫描卡表中脏卡片的时间

T(old_root_scan): 扫描在老年代中的直接的根的时间。注意是直接的根,不会去级联扫描老年代的对象。因为扫描都是从根开始的,一开始不知道根到底是在老年代还是新生代

和Card Marking相关的一个重要的JVM参数是 -XX:UseCondCardMark  。使用这个参数的原因是在高并发的情况下,Card标记为脏的操作本身就存在着竞争,使用这个参数可以避免卡片被重复标记为脏,从而提高性能。

说完了标记,下面提一下几种基础的GC算法,没有什么新的点,直接引用网上的图

标记-清除算法

第一阶段从引用根节点开始标记所有被引用的对象
第二阶段遍历整个堆,把未标记的对象清除
缺点:会产生内存碎片

复制算法



将内存空间划为两个相等的区域,每次只使用其中一个区域。回收时复制正在使用中的对象到另一个区域,复制完后清空之前的区域。
复制成本小,不会出现碎片,缺点需要两倍内存空间。

标记--整理算法

标记整理算法的标识过程仍然与标记-清除算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。

分代算法 是将对象分为新生代和老年代,然后使用不同的GC策略来进行回收,提高整体的效率。

由于新生代的大部分对象都会在一次Minor GC中死亡,存活的对象很少,所以新生代的GC收集器都采用了复制算法。新生代分为Eden + S0 + S1. S0和S1就是用来实现复制的, 在任何一次Minor GC后,S0和S1总是只有一个区域有数据,另一个区域为空,以便于下一次复制使用

当新生代空间不能满足大对象分配时,老年代空间为它提供了分配担保,大对象可以直接进入老年代。有两个JVM参数可以控制新生代进入老年代的门槛:

PretenureSizeThreshold: 单位是B,设置了对象大小的阀值

MaxTenuringThreshold: 设置了进入老年代的年龄的阀值



老年代对象一般都是存活时间久,老年代的空间本来就大,所以没有更多空间来提供分配担保,所以老年代一般采用标记--清理或者标记--整理算法。

下面这张图很好地介绍了JDK6的各种GC收集器以及各自的特点:


1. 新生代都采用复制算法

2. CMS采用了标记--清除算法,由于标记清除算法会生成内存碎片,所以JVM提供了参数来使CMS可以在几次清除后作一次整理

-XX:CMSFullGCsBeforeCompaction :由于并发收集器不对内存空间进行压缩、整理,所以运行一段时间以后会产生“碎片”,使得运行效率降低。此值设置运行多少次GC以后对内存空间进行压缩、整理。 -XX:+UseCMSCompactAtFullCollection :打开对年老代的压缩。可能会影响性能,但是可以消除碎片

3. Serial Old(MSC)和Parallel Old都采用标记整理算法

4. UseSerialGC默认会在新生代使用Serial收集器,在老年代使用Serial Old收集器,这两个都是单线程的收集器

5. UseConcMarkSweepGC默认会再新生代使用ParNew收集器,这是个并发的收集器。在老年代会使用CMS + Serial Old收集器,当CMS失败的时候,会启用Serial Old做FULL GC

6. UseParallelOldGC默认会在新生的使用Parallel Scavenge收集器,在老年代使用Parallel Old收集器。这两个收集器都是吞吐量优先,所谓吞吐量优先就是它可以严格控制GC的时间,从而保证吞吐量。 但是吞吐量提高了,新生代和老年代的空间就是动态调整的,而不是按照初始配置的大小。因为单位时间清除的垃圾量近乎一个常量,既然要保证时间,那么必须保证垃圾总量,而垃圾总量可以通过新生代和老年代的大小来控制的

7. 对于和用户有交互的应用,比如Web应用,一个重要的考量是系统的响应时间,要保证系统的响应时间就要保证由GC导致的stop the world次数少,或者让用户线程和GC线程一起运行。所以Web应用是使用CMS收集器的一个重要场景。 CMS减少了stop the world的次数,不可避免地让整体GC的时间拉长了

8. 对于计算密集型的应用可能会考虑计算的吞吐量,这时候可以使用Parallel Scavenge收集器来保证吞吐量

9. Serial, ParNew, Parallel Scanvange, Parallel Old, Serial Old全程都会Stop the world,JVM这时候只运行GC线程,不运行用户线程

10. CMS主要分为 initial Mark, Concurrent Mark, ReMark, Concurrent Sweep等阶段,initial Mark和Remark占整体的时间比较较小,它们会Stop the world. Concurrent Mark和Concurrent Sweep会和用户线程一起运行。

下面这张图对GC的日志信息做了说明:

查看JVM启动参数

1. jps -v

2. jinfo -flags pid

3. jinfo pid -- 列出JVM启动参数和system.properties

4. ps -ef | grep  Java



查看当前堆的配置

1. jstat -gc pid 1000 3  -- 列出堆的各个区域的大小

2. jstat -gcutil pid 1000 3 -- 列出堆的各个区域使用的比例

3. jmap -heap pid  -- 列出当前使用的GC算法,堆的各个区域大小



查看线程的堆栈信息

1. jstack -l pid 



dump堆内的对象

1. jmap -dump:live,format=b,file=xxx pid

2. -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=xxx  -- 设置JVM参数,当JVM OOM时输出堆的dump

3. ulimit  -c unlimited  -- 设置Linux ulimit参数,可以产生coredump且不受大小限制。之前在线上遇到过一个极其诡异的问题,JVM整个进程突然挂了,这时候依靠JVM本身生成dump文件已经不行了,只有依赖Linux,让系统来生成进程挂掉的core dump文件

使用jstack 可以来获得这个coredump的线程堆栈信息:  jstack "$JAVA_HOME/bin/java" core.xxx > core.log



获得当前系统占用CPU最高的10个进程,线程

ps Hh -eo pid,tid,pcpu,pmem | sort -nk3 |tail > temp.txt



图形化界面
1. jvisualvm 里面有很多插件,比如Visual GC,可以可视化地看到各个堆区域时候的状态,从而可以对整体GC的性能有整体的认识

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值