JVM垃圾收集器

声明:此篇文章是读《深入理解JAVA虚拟机》的笔记

1. 对象已死?

  堆中几乎存放着Java中所有的对象实例,垃圾收集器在回收前,如何判断哪些对象是活着,哪些对象已经死去

  • 引用计数算法
      给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1;任何时刻计数器都为0的对象就是不可能再被使用的。
      但是,Java语言中没有选用引用计数算法来管理内在,其中最主要的原因是它很难解决对象之间的相互循环引用的问题。
      模拟代码如下:
/**
 *JVM的GC日志的主要参数包括如下几个:
 *-XX:+PrintGC 输出GC日志
 *-XX:+PrintGCDetails 输出GC的详细日志
 *-XX:+PrintGCTimeStamps 输出GC的时间戳(以基准时间的形式)
 *-XX:+PrintGCDateStamps 输出GC的时间戳(以日期的形式,如 2013-05-04T21:53:59.234+0800)
 *-XX:+PrintHeapAtGC 在进行GC的前后打印出堆的信息
 *-Xloggc:../logs/gc.log 日志文件的输出路径
 */
public class ReferenceCountingGC {
    public Object instance = null;
    private static final int _1MB = 1024 * 1024;
    //占用空间
    private byte[] bigSize = new byte[2*_1MB];

    public static void main(String[] args) throws InterruptedException {
        ReferenceCountingGC objA = new ReferenceCountingGC();
        ReferenceCountingGC objB = new ReferenceCountingGC();
        objA.instance = objB;
        objB.instance = objA;
        objA = null;
        objB = null;
        System.gc();
    }
}

GC日志显示:

[GC (System.gc()) [PSYoungGen: 5336K->504K(6144K)] 5336K->608K(19968K), 0.0012319 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]

  • 根搜索算法
      这个算法的基本思路就是通过一系列的名为GC Roots的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的。如图所示,object5、object6、object7虽然互相有关联,但是它们到GC Roots是不可达的,所以将会被判定为是可回收的对象:

    根搜索算法判定对象是否可回收

      在Java语言里,可作为GC Roots的对象包括下面几种:
      1. 虚拟机栈(栈帧中的本地变量表)中的引用的对象。
      2. 方法区中的类静态属性引用的对象。
      3. 方法区中的常量引用的对象。
      4. 本地方法栈中JNI引用的对象。

  • 再谈引用
      在JDK1.2之后,Java对引用的概念进行了扩充,将引用分为强引用(Strong Reference)软引用(Soft Reference)弱引用(Weak Reference)虚引用(Phantom Reference)四种,这四种引用强度依次逐渐减弱。
      1. 强引用就是在指程序代码之中普遍存在的,类似Object obj= new Object()这类的引用,只要强引用存在,垃圾收集器永远不会回收掉被引用的对象。
      2. 软引用用来区描述一些还有用,但并非必需的对象。对于软引用关联着的对象,在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围之中并进行第二次回收。在JDK1.2之后,提供了SoftReference类来实现软引用。
      3. 弱引用也是用来描述非必需对象的,但是它的强度比软引用更弱一些,被弱引用关联的对象只能生存到下一次垃圾收集发生之前。当垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。在JDK1.2之后,提供了WeakReference类来实现弱引用。
      4. 虚引用也称为幽灵引用或者幻影引用,它是最弱的一个引用关系。虚引用对一个对象的生存时间完全不会影响。为一个对象设置虚引用关联的唯一目的就是希望能在这个对象被收集器回收时收到 一个系统通知

  • 生存还是死亡?
      在根搜索算法中不可达对象,也并非是非死不可的,虚拟机在回收对象之前会调用对象的finalize()方法,来判断此对象是否重新被引用,前提是这个方法没有被执行过,因为这个方法只能被虚拟机调用一次。但是此方法的运行代价大,不确认性高,所以不推荐使用。

  • 回收方法区
      永久代(元空间)的垃圾收集主要回收两部分内容:废弃常量和无用的类。
      当在发生内存回收的时候,常量池中的某些常量没有被任何地方引用,那这个常量就会被请出常量池。
      如果要判定类是否为无用的类,条件要苛刻的多:
      1. 该类所有的实例都已经被回收。
      2. 加载该类的ClassLoader已经被回收。
      3. 该类对应的java.lang.Class对象没有在任何地主被引用,无法在任何地方通过反射访问该类的方法。
    虚拟机可以对满足以上3个条件的无用类进行回收。但是对于类的回收不是必然的,HotSpot虚拟机提供了-Xnoclassgc参数进行控制,还可以使用-verbose:class-XX:+TraceClassLoading、-XX:+TraceClassUnLoading查看类的加载和卸载信息。
    注意:在大量使用反射、动态代理、CGLib等bytecode框架的场景,以及动态生成jsp和OSGi这类频繁自定义ClassLoader的场景都需要虚拟机具备类卸载的功能,以保永久代(元空间)不会溢出。

2. 垃圾收集算法

  • 标记-清除算法
      最基础的收集算法,包含标记清除两个阶段。缺点有两个:一个是标记和清除的效率都不高;另一个是标记清除之后会产生大量不连续的内存碎片(当程序以后在分配较大的对象时,若无法找到足够的连续的内存,就不得不提前触发另一次垃圾回收动作)。
    标记-清除算法
  • 复制算法
      将可用的内存容量划分为大小相等的两块,每次只使用其中的一块,当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。这种算法的代价是将内存缩小为原来的一半。
    复制算法

      Tips:HotSpot虚拟机默认分为一个Eden空间和两个Survivor。Eden空间的大小与Survivor的大小比例为8:1;
  • 标记-整理算法
      根据老年代的特点,提出的一种算法。先进行标记,然后把所有存活的对象都向一端移动,最后直接清理掉边界以外的内存。
    标记-整理算法
  • 分代收集算法
      大部分虚拟机采用的算法,这种算法没有什么新的思想,只是根据对象的存活周期和不同将内存划分为几块。一般是把Java堆分为新生代和老生代,这样就可以根据各个年代的特点采用最适当的收集算法。

3. 垃圾收集器

  简单来说垃圾收集器就是内存回收的具体实现。虽然有不同的收集器,但是目前为止还没有最好的收集器出现,也没有万能收集器,我们选择的只是对具体应用最合适的收集器。


JVM虚拟机收集器
  • Serial收集器
      最基本,历史最悠久的收集器。单线程收集器,这里的单线程不是只会使用一个CPU或者一条收集线程去完成垃圾收集工作,而在它在进行垃圾收集时,必须暂停其他所有的工作线程,直到它收集结束

    Serial/Serial Old收集器

    -XX:+UseSerialGC开启Serial收集器。

  • ParNew收集器
      ParNew收集器其实就是Serial收集器的多线程版本,默认开启的收集线程数与CPU数量相同,使用-XX:ParallelGCThreads参数来限制垃圾收集的线程数。

    ParNew收集器

    -XX:+UseParNewGC开启ParNew收集器。

  • Parallel Scavenge收集器
      同样使用复制算法的收集器,又是并行的多线程收集器。其目标是达到一个可控制的吞吐量。所谓吞吐量就是CPU用于运行用户代码的时间与CPU总消耗时间的比值,即吞吐量=运行用户代码时间/(运行用户代码时间 +垃圾收集时间),虚拟机总共运行了100分钟,其中垃圾收集花掉1分钟,那吞吐量就是99%。

    Parallel Scavenge/Parallel Old收集器

    Tips:
    -XX:+UseParallelGC开启Parallel收集器。
    -XX:MaxGCPauseMillis用来设置最大垃圾收集停顿时间。
    -XX:GCTimeRatio用来设置吞吐量的大小。
    -XX:+UseAdaptiveSizePolicy打开此参数时,虚拟机可以自己调节GC策略。自适应调节策略是Parallel Scavenge收集器与ParNew收集器的一个重要区别。

  • Serial Old收集器
      Serial Old是Serial收集器的老年代版本,它同样是一个单线程收集器,使用标记-清理算法。

  • Parallel Old收集器
      Parallel Old是Parallel Scavenge收集器的老年代版本,使用多线程和标记-整理算法。在注重吞吐量及CPU敏感的场合,都可以优先考虑Parallel Scavenge加Parallel Old收集器。
    -XX:+UseParallelOldGC开启Parallel Old收集器。

  • CMS收集器
      CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。CMS收集器是基于标记-清除算法,不过它的运作过程要复杂一些,分为4个步骤:
      1. 初始标记(CMS initial mark)
      2. 并发标记(CMS concurrent mark)
      3. 重新标记(CMS remark)
      4. 并发清除(CMS concurrent sweep)

    Concurrent Mark Sweep收集器

    并发收集、低停顿是CMS收集器的优点,但是它也是三个显著的缺点:
      a. CMS收集器对CPU资源非常敏感。CMS默认启动的回收线程数是(CPU数量+3)/4,也就是当CPU在4个以上时,并发回收垃圾时垃圾收集线程最多占用不超过25%的CPU资。但是当CPU不足4个时,那么CMS对用户程序的影响就可能变得很大。
      b. CMS收集器无法处理浮动垃圾,可能出现Concurrent Mode Failure失败而导致另一次Full GC的产生。所谓的浮动垃圾是在清理期间用户线程产生的新垃圾。也正是用户线程还需要运行,所以还需要预留足够的空间给用户线程使用。可以用-XX:CMSInitiatingOccupancyFraction设置。
    注意:设置的太高将会容易导致大量Concurrent Mode Failure,导致性能降低。
      c. 最后一个缺点是基于标记-清除算法的收集器,会产生大量的空间碎片。
    Tips:
    -XX:+UseParNewGC -XX:+UseConcMarkSweepGC开启CMS收集器(搭配新生代ParNew收集器)。
    -XX:UseCMSCompactAtFullCollection参数用于在享受完一个FullGC后提供一次碎片整理过程。
    -XX:CMSFullGCCsBeforeCompaction参数用于设置在执行多少次不压缩的Full GC后,来一次带压缩的。

  • G1收集器
      基于标记-整理算法实现的收集器。优点:
      1. 不会产生空间碎片
      2. 可以非常精确地控制停顿
      G1收集器可以实现在基本不牺牲吞吐量的前提下完成低停顿的内存回收,这是由于它能够极力地避免全区域的垃圾收集,之前的收集器进行收集的范围都是整个新生代或老年代,而G1将整个Java堆(包括新生代、老年代)划分为多个大小固定的独立区域(Region),并且跟踪这些区域里面的垃圾堆积程度,在后台维护一个优先列表,每次根据允许的收集时间,优先回收垃圾最多的区域。区域划分及有优先级的区域回收,保证了G1收集器在有限的时间内可以获得最高的收集效率。
    Tips:
    G1可用的命令行选项有:
    -XX:+UseG1GC 让JVM使用G1垃圾回收器。
    -XX:MaxGCPauseMillis=200 设置GC暂停时间目标值,缺省200毫秒。但这不是硬指标,JVM会尽力满足。
    -XX:InitiatingHeapOccupancyPercent=45 整个堆被占用多少之后开始进行GC,缺省为45,0表示持续不停进行GC。
    -XX:NewRatio=n 年轻代和老年代的比例,缺省为2。
    -XX:SurvivorRatio=n Eden和Survivro的比例,缺省为8。
    -XX:G1ReservePercent=n 保留的堆大小,减少晋升过程中出错的可能性,也就是增加可用的to-space内存,缺省是10。
    -XX:G1HeapRegionSize=n G1中,堆分为大小相等的区域。这个参数设置区域的大小,缺省值取决于堆的总大小,有效取值是1M-32M。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,921评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,635评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,393评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,836评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,833评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,685评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,043评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,694评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,671评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,670评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,779评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,424评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,027评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,984评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,214评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,108评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,517评论 2 343

推荐阅读更多精彩内容