java GC概述

文章目录

  • Java GC原理概述
    • Java GC(garbage collec,垃圾收集,回收)
    • 1.年轻代的GC(存放实例化的对象)
    • 2.老年代的GC(存放较大的实例化的对象和在年轻代中存活了足够久的对象)
    • 3.永久代的GC(存放常量、类)
  • Java垃圾回收概况
  • Java内存区域
  • Java对象的访问方式
  • Java内存分配机制
  • Java GC机制
  • 垃圾收集器

Java GC原理概述

Java GC(garbage collec,垃圾收集,回收)

GC是对JVM中的内存进行标记和回收,Sun公司的JDK用的虚拟机都是HotSpot
对象化的实例是放在heap堆内存中的,这里讲的分代收集也是指对堆内存的回收

GC的分代收集分为:年轻代、老年代、永久代。(方法区是被当做永久代的,不过JDK1.6后将被取消掉了)年轻代(Young Generation)、年老代(Old Generation)、永久代(Permanent Generation,也就是方法区)

1.年轻代的GC(存放实例化的对象)

年轻代分为三个区:Eden和两个存活区(Survivor0和Survivor1),分别占内存的80%、10%、10% 使用“停止-复制(Stop-and-copy)”清理法(将Eden区和一个Survivor中仍然存活的对象拷贝到另一个Survivor中)

当Eden区满时,就执行一次MinorGC,并将剩余存活的对象都添加到Surivivor0,回收Eden中的没有存活的对象。

当Surivivor0页都满了的时候,就将仍然存活的存到Surivivor1中,回收Surivivor0中的对象

Surivivor0和Surivivor1依次去存,当两个存活区切换了几次后(HotSpot默认是15次),将仍然存活的对象复制到老年代

2.老年代的GC(存放较大的实例化的对象和在年轻代中存活了足够久的对象)

老年代GC用的是标记-整理算法,即标记存活的对象,向一端移动,保证内存的完整性,然后将未标记的清掉

当老年代不够用时,也会执行Major GC,即Full GC

注意:如果永久代代存放的常量和类过大,无法全部放入永久代,也会触发永久代的GC,将一部分放入老年代

3.永久代的GC(存放常量、类)

说明:在JDK1.6版本之后,永久代就要被取消掉了,只留下年轻代和老年代

java GC概述

Java垃圾回收概况

Java GC(Garbage Collection,垃圾收集,垃圾回收)机制,是Java与C++/C的主要区别之一,作为Java开发者,一般不需要专门编写内存回收和垃圾清理代码,对内存泄露和溢出的问题,也不需要像C程序员那样战战兢兢。这是因为在Java虚拟机中,存在自动内存管理和垃圾清扫机制。概括地说,该机制对JVM(Java Virtual Machine)中的内存进行标记,并确定哪些内存需要回收,根据一定的回收策略,自动的回收内存,永不停息(Nerver Stop)的保证JVM中的内存空间,防止出现内存泄露和溢出问题。

关于JVM,需要说明一下的是,目前使用最多的Sun公司的JDK中,自从1999年的JDK1.2开始直至现在仍在广泛使用的JDK6,其中默认的虚拟机都是HotSpot。2009年,Oracle收购Sun,加上之前收购的EBA公司,Oracle拥有3大虚拟机中的两个:JRockit和HotSpot,Oracle也表明了想要整合两大虚拟机的意图,但是目前在新发布的JDK7中,默认的虚拟机仍然是HotSpot,因此本文中默认介绍的虚拟机都是HotSpot,相关机制也主要是指HotSpot的GC机制。

Java GC机制主要完成3件事:确定哪些内存需要回收,确定什么时候需要执行GC,如何执行GC。经过这么长时间的发展(事实上,在Java语言出现之前,就有GC机制的存在,如Lisp语言),Java GC机制已经日臻完善,几乎可以自动的为我们做绝大多数的事情。然而,如果我们从事较大型的应用软件开发,曾经出现过内存优化的需求,就必定要研究Java GC机制。

学习Java GC机制,可以帮助我们在日常工作中排查各种内存溢出或泄露问题,解决性能瓶颈,达到更高的并发量,写出更高效的程序。

我们将从4个方面介绍Java GC机制,1,内存是如何分配的;2,如何保证内存不被错误回收(即:哪些内存需要回收);3,在什么情况下执行GC以及执行GC的方式;4,如何监控和优化GC机制。

Java内存区域

了解Java GC机制,必须先清楚在JVM中内存区域的划分。在Java运行时的数据区里,由JVM管理的内存区域分为下图几个模块:
  

java GC概述

通过句柄访问的实现方式中,JVM堆中会专门有一块区域用来作为句柄池,存储相关句柄所执行的实例数据地址(包括在堆中地址和在方法区中的地址)。这种实现方法由于用句柄表示地址,因此十分稳定。

2,通过直接指针访问:(图来自于《深入理解Java虚拟机:JVM高级特效与最佳实现》)

java GC概述
  • 年轻代(Young Generation):
      对象被创建时,内存的分配首先发生在年轻代(大对象可以直接被创建在年老代),大部分的对象在创建后很快就不再使用,因此很快变得不可达,于是被年轻代的GC机制清理掉(IBM的研究表明,98%的对象都是很快消亡的),这个GC机制被称为Minor GC或叫Young GC。注意,Minor GC并不代表年轻代内存不足,它事实上只表示在Eden区上的GC。
      年轻代上的内存分配是这样的,年轻代可以分为3个区域:Eden区(伊甸园,亚当和夏娃偷吃禁果生娃娃的地方,用来表示内存首次分配的区域,再贴切不过)和两个存活区(Survivor 0 、Survivor 1)。内存分配过程为(来源于《成为JavaGC专家part I》,http://www.importnew.com/1993.html):     java GC概述

    在介绍垃圾收集器之前,需要明确一点,就是在新生代采用的停止复制算法中,“停 止(Stop-the-world)”的意义是在回收内存时,需要暂停其他所 有线程的执行。这个是很低效的,现在的各种新生代收集器越来越优化这一点,但仍然只是将停止的时间变短,并未彻底取消停止。

    • Serial收集器:新生代收集器,使用停止复制算法,使用一个线程进行GC,串行,其它工作线程暂停。使用-XX:+UseSerialGC可以使用Serial+Serial
      Old模式运行进行内存回收(这也是虚拟机在Client模式下运行的默认值)

    • ParNew收集器:新生代收集器,使用停止复制算法,Serial收集器的多线程版,用多个线程进行GC,并行,其它工作线程暂停,关注缩短垃圾收集时间。使用-XX:+UseParNewGC开关来控制使用ParNew+Serial Old收集器组合收集内存;使用-XX:ParallelGCThreads来设置执行内存回收的线程数。

    • Parallel Scavenge 收集器:新生代收集器,使用停止复制算法,关注CPU吞吐量,即运行用户代码的时间/总时间,比如:JVM运行100分钟,其中运行用户代码99分钟,垃 圾收集1分钟,则吞吐量是99%,这种收集器能最高效率的利用CPU,适合运行后台运算(关注缩短垃圾收集时间的收集器,如CMS,等待时间很少,所以适 合用户交互,提高用户体验)。使用-XX:+UseParallelGC开关控制使用Parallel Scavenge+Serial Old收集器组合回收垃圾(这也是在Server模式下的默认值);使用-XX:GCTimeRatio来设置用户执行时间占总时间的比例,默认99,即1%的时间用来进行垃圾回收。使用-XX:MaxGCPauseMillis设置GC的最大停顿时间(这个参数只对Parallel Scavenge有效),用开关参数-XX:+UseAdaptiveSizePolicy可以进行动态控制,如自动调整Eden/Survivor比例,老年代对象年龄,新生代大小等,这个参数在ParNew下没有。

    • Serial Old收集器:老年代收集器,单线程收集器,串行,使用标记整理(整理的方法是Sweep(清理)和Compact(压缩),清理是将废弃的对象干掉,只留幸存的对象,压缩是将移动对象,将空间填满保证内存分为2块,一块全是对象,一块空闲)算法,使用单线程进行GC,其它工作线程暂停(注意,在老年代中进行标记整理算法清理,也需要暂停其它线程),在JDK1.5之前,Serial Old收集器与ParallelScavenge搭配使用。

    • Parallel Old收集器:老年代收集器,多线程,并行,多线程机制与Parallel
      Scavenge差不错,使用标记整理(与Serial Old不同,这里的整理是Summary(汇总)和Compact(压缩),汇总的意思就是将幸存的对象复制到预先准备好的区域,而不是像Sweep(清理)那样清理废弃的对象)算法,在Parallel Old执行时,仍然需要暂停其它线程。Parallel Old在多核计算中很有用。Parallel Old出现后(JDK 1.6),与Parallel Scavenge配合有很好的效果,充分体现Parallel Scavenge收集器吞吐量优先的效果。使用-XX:+UseParallelOldGC开关控制使用Parallel Scavenge +Parallel Old组合收集器进行收集。

    • CMS(Concurrent Mark Sweep)收集器:老年代收集器,致力于获取最短回收停顿时间(即缩短垃圾回收的时间),使用标记清除算法,多线程,优点是并发收集(用户线程可以和GC线程同时工作),停顿小。使用-XX:+UseConcMarkSweepGC进行ParNew+CMS+Serial Old进行内存回收,优先使用ParNew+CMS(原因见后面),当用户线程内存不足时,采用备用方案Serial Old收集。

    CMS收集的执行过程是:初始标记(CMS-initial-mark) -> 并发标记(CMS-concurrent-mark)
    –>预清理(CMS-concurrent-preclean)–>可控预清理(CMS-concurrent-abortable-preclean)->
    重新标记(CMS-remark) -> 并发清除(CMS-concurrent-sweep)
    ->并发重设状态等待下次CMS的触发(CMS-concurrent-reset)
    具体的说,先2次标记,1次预清理,1次重新标记,再1次清除。
    1,首先jvm根据-XX:CMSInitiatingOccupancyFraction,-XX:+UseCMSInitiatingOccupancyOnly来决定什么时间开始垃圾收集;
    2,如果设置了-XX:+UseCMSInitiatingOccupancyOnly,那么只有当old代占用确实达到了-XX:CMSInitiatingOccupancyFraction参数所设定的比例时才会触发cms
    gc;
    3,如果没有设置-XX:+UseCMSInitiatingOccupancyOnly,那么系统会根据统计数据自行决定什么时候触发cms
    gc;因此有时会遇到设置了80%比例才cms gc,但是50%时就已经触发了,就是因为这个参数没有设置的原因;
    4,当cms gc开始时,首先的阶段是初始标记(CMS-initial-mark),是stop the world阶段,因此此阶段标记的对象只是从root集最直接可达的对象;
    CMS-initial-mark:961330K(1572864K),指标记时,old代的已用空间和总空间
    5,下一个阶段是并发标记(CMS-concurrent-mark),此阶段是和应用线程并发执行的,所谓并发收集器指的就是这个,主要作用是标记可达的对象,此阶段不需要用户停顿。
    此阶段会打印2条日志:CMS-concurrent-mark-start,CMS-concurrent-mark
    6,下一个阶段是CMS-concurrent-preclean,此阶段主要是进行一些预清理,因为标记和应用线程是并发执行的,因此会有些对象的状态在标记后会改变,此阶段正是解决这个问题因为之后的Rescan阶段也会stop
    the world,为了使暂停的时间尽可能的小,也需要preclean阶段先做一部分工作以节省时间
    此阶段会打印2条日志:CMS-concurrent-preclean-start,CMS-concurrent-preclean
    7,下一阶段是CMS-concurrent-abortable-preclean阶段,加入此阶段的目的是使cms gc更加可控一些,作用也是执行一些预清理,以减少Rescan阶段造成应用暂停的时间
    此阶段涉及几个参数:
    -XX:CMSMaxAbortablePrecleanTime:当abortable-preclean阶段执行达到这个时间时才会结束
    -XX:CMSScheduleRemarkEdenSizeThreshold(默认2m):控制abortable-preclean阶段什么时候开始执行,
    即当eden使用达到此值时,才会开始abortable-preclean阶段
    -XX:CMSScheduleRemarkEdenPenetratio(默认50%):控制abortable-preclean阶段什么时候结束执行
    此阶段会打印一些日志如下:
    CMS-concurrent-abortable-preclean-start,CMS-concurrent-abortable-preclean,
    CMS:abort preclean due to time XXX
    8,再下一个阶段是第二个stop the world阶段了,即Rescan阶段,此阶段暂停应用线程,停顿时间比并发标记小得多,但比初始标记稍长。对对象进行重新扫描并标记;
    YG occupancy:964861K(2403008K),指执行时young代的情况
    CMS remark:961330K(1572864K),指执行时old代的情况
    此外,还打印出了弱引用处理、类卸载等过程的耗时
    9,再下一个阶段是CMS-concurrent-sweep,进行并发的垃圾清理
    10,最后是CMS-concurrent-reset,为下一次cms gc重置相关数据结构

    gc,就可以缓解当old代达到80%,cms gc处理不完,从而造成concurrent mode failure引发full gc
    B,修改-XX:CMSMaxAbortablePrecleanTime=500,缩小CMS-concurrent-abortable-preclean阶段的时间
    C,考虑到cms gc时不会进行compact,因此加入-XX:+UseCMSCompactAtFullCollection
    (cms gc后会进行内存的compact)和-XX:CMSFullGCsBeforeCompaction=4(在full
    gc4次后会进行compact)参数

    GC,而是在使用了一大半(默认68%,即2/3,使用-XX:CMSInitiatingOccupancyFraction来设置)的时候就要进行Full
    GC,如果用户线程消耗内存不是特别大,可以适当调高-XX:CMSInitiatingOccupancyFraction以降低GC次数,提高性能,如果预留的用户线程内存不够,则会触发Concurrent
    Mode Failure,此时,将触发备用方案:使用Serial Old
    收集器进行收集,但这样停顿时间就长了,因此-XX:CMSInitiatingOccupancyFraction不宜设的过大。
    还有,CMS采用的是标记清除算法,会导致内存碎片的产生,可以使用-XX:+UseCMSCompactAtFullCollection来设置是否在Full
    GC之后进行碎片整理,用-XX:CMSFullGCsBeforeCompaction来设置在执行多少次不压缩的Full
    GC之后,来一次带压缩的Full GC。

    • G1收集器:在JDK1.7中正式发布,与现状的新生代、老年代概念有很大不同,目前使用较少,不做介绍。

      所以,Serial是串行的,Parallel收集器是并行的,而CMS收集器是并发的.

    文章知识点与官方知识档案匹配,可进一步学习相关知识Java技能树首页概览91437 人正在系统学习中

    来源:xp_9512

    声明:本站部分文章及图片转载于互联网,内容版权归原作者所有,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!

上一篇 2019年3月1日
下一篇 2019年3月1日

相关推荐