JVM 垃圾回收

    从JVM工做原理中咱们知道,Class文件被加载到JVM内存后会产生各类对象,像类静态信息、方法等会被存储在方法区,对象则会被存储在堆上,程序运行一段时间后,有些对象已经不会再被引用,因此须要被释放掉,这部分工做正是垃圾回收的功能。开发者须要理解垃圾回收机制,而且进行些优化设置,从而使JVM垃圾回收器更好的工做。

垃圾回收总体架构:


1. 垃圾回收分类:
 
  垃圾回收分类的标准有三个:回收策略,分区方式和系统线程,垃圾回收器通常根据这三个标准组合使用,例如CMS垃圾回收器使用标记清除,分代分区和并发收集来进行垃圾回收。java

  • 根据基本回收策略
    a) 引用计数(Reference counting)。此算法的原理是对象有一个引用,即增长一个计数,删除一个引用则减小一个计数。垃圾回收时,引用收集计数为0的对象。此算法最致命的是没法处理循环引用的问题。
     
    b) 标记清除(Mark-Sweep)。此算法执行分两阶段。第一阶段从引用根节点开始标记全部被引用的对象,第二阶段遍历整个堆,把未标记的对象清除。此算法须要暂停整个应用,同时会产生内存碎片。

    c) 复制(Copying)。此算法把内存空间划为两个相等的区域,每次只使用其中一个区域。垃圾回收时,遍历当前使用区域,把正在使用中的对象复制到另一个区域中。次算法每次只处理正在使用中的对象,所以复制成本比较小,同时复制过去之后还能进行相应的内存整理,不会出现“碎片”问题。固然,此算法的缺点也是很明显的,就是须要两倍内存空间。

    d) 标记整理(Mark-Compact)。此算法结合了“标记-清除”和“复制”两个算法的优势。也是分两阶段,第一阶段从根节点开始标记全部被引用对象,第二阶段遍历整个堆,把清除未标记对象而且把存活对象“压缩”到堆的其中一块,按顺序排放。此算法避免了“标记-清除”的碎片问题,同时也避免了“复制”算法的空间问题。
     
  • 根据分区方式
    a) 增量收集(Incremental Collecting)。实时垃圾回收算法,即在应用进行的同时进行垃圾回收。

    b) 分代收集(Generational Collecting)。基于对对象生命周期分析后得出的垃圾回收算法。把对象分为年青代、年老代、持久代,对不一样生命周期的对象使用不一样的算法进行回收。如今的垃圾回收器(从J2SE1.2开始)都是使用此算法的。
     
  • 根据系统线程
    a) 串行收集(Serial)。串行收集使用单线程处理全部垃圾回收工做,由于无需多线程交互,实现容易,并且效率比较高。可是,其局限性也比较明显,即没法使用多处理器的优点,因此此收集适合单处理器机器。固然,此收集器也能够用在小数据量(100M左右)状况下的多处理器机器上。

    b) 并行收集(Parallel)。并行收集使用多线程处理垃圾回收工做,于是速度快,效率高。并且理论上CPU数目越多,越能体现出并行收集器的优点。

    c) 并发收集(Concurrent)。相对于串行收集和并行收集而言,前面两个在进行垃圾回收工做时,须要暂停整个运行环境,而只有垃圾回收程序在运行,所以,系统在垃圾回收时会有明显的暂停,并且暂停时间会由于堆越大而越长。


2. 分代方式:
   
目前大部分垃圾回收器都是经过分代方式来实现垃圾回收,回收策略和系统线程能够进行选择搭配。JVM内存分代方式如图所示:

    虚拟机中的共划分为三个代:年轻代(Young Generation)、年老点(Old Generation)、持久代(Permanent Generation),其中持久代主要存放的是Java类的类信息,与垃圾收集要收集的Java对象关系不大。年轻代和年老代的划分是对垃圾收集影响比较大的。linux

  •  年轻代(Young Generation)
    全部新生成的对象首先都是放在年轻代的。年轻代的目标就是尽量快速的收集掉那些生命周期短的对象。年轻代分三个区。一个Eden区,两个Survivor区from和to。大部分对象在Eden区中生成。当Eden区满时,还存活的对象将被复制到to,from中也会拷贝到to,若是to满时,对象将被拷贝到“年老区(Tenured)”,此时,Eden和to的对象就是垃圾,能够直接清除,to则是存活的对象,下次YGC时to和from角色互调换
     
  • 年老代(Old Generation)
    在年轻代中经历了N次垃圾回收后仍然存活的对象,就会被放到年老代中。所以,能够认为年老代中存放的都是一些生命周期较长的对象。
     
  • 持久代(Permanent Generation)Java8 中已经取消,由MetaSpace代替
    用于存放静态文件,现在Java类、方法等。持久代对垃圾回收没有显著影响,可是有些应用可能动态生成或者调用一些class,例如hibernate等,在这种时候须要设置一个比较大的持久代空间来存放这些运行过程当中新增的类。持久代大小经过-XX:MaxPermSize=<N>进行设置。
     
  • 元空间(MetaSpace)
    用于存放静态数据,现在Java类、方法,静态成员等。元空间并不在虚拟机中,而是使用本地内存。所以,默认状况下,元空间的大小仅受本地内存限制,但能够经过如下参数来指定元空间的大小。元空间经过-XX:MetaspaceSize, -XX:MaxMetaspaceSize进行配置。


3. 垃圾回收算法

     GC有两种类型:Young GC和Full GC,Young GC只清理Young Generation,Full GC会清理全部Generation。数据结构

  • Young GC(minor gc)
    通常状况下,当新对象生成,而且在Eden申请空间失败时,就会触发Young GC,对Eden区域进行GC,清除非存活对象,而且把尚且存活的对象移动到Survivor区。而后整理Survivor的两个区。这种方式的GC是对年轻代的Eden区进行,不会影响到年老代。由于大部分对象都是从Eden区开始的,同时Eden区不会分配的很大,因此Eden区的GC会频繁进行。于是,通常在这里须要使用速度快、效率高的算法,使Eden去能尽快空闲出来。
     
  • Full GC (major gc)
    对整个堆进行整理,包括Young、Tenured和Perm。Full GC由于须要对整个对进行回收,因此比Young GC要慢,所以应该尽量减小Full GC的次数。在对JVM调优的过程当中,很大一部分工做就是对于FullGC的调节。
     

4. 垃圾回收器
    垃圾回收器按照垃圾回收类型进行分类:多线程

  • Young GC
    a) 串行GC(Serial Copying)。client模式下的默认GC方式,也可以使用-XX:+UseSerialGC指定。
    b) 并行回收GC(Parallel Scavenge)。server模式下的默认GC方式,也可用-XX:+UseParallelGC强制指定。
    c) 并行GC(ParNew)。CMS GC时默认采用,会配合CMS作些处理,也能够采用-XX:+UseParNewGC指定。
     
  • Old GC
    a) 串行GC(Serial MSC)。client模式下的默认GC方式,可经过-XX:+UseSerialGC强制指定。每次进行所有回收,进行Compact,很是耗费时间。
    b) 并行GC(Parallel MSC)。server模式下的默认GC方式,也可用-XX:+UseParallelGC=强制指定。能够在选项后加等号来制定并行的线程数。
    c) 并发GC(CMS)。使用CMS是为了减小GC执行时的停顿时间,垃圾回收线程和应用线程同时执行,可使用-XX:+UseConcMarkSweepGC=指定使用,后边接等号指定并发线程数,CMS分6个阶段,初始标记和重标记是STW,其余4个阶段则是并发进行。碎片是因为CMS默认不对内存进行Compact所致,能够经过-XX:+UseCMSCompactAtFullCollection和-XX:+UseCMSCompactAtFullCollection=0。
    d) G1收集器(Garbage First)。与 CMS 收集器相比,G1 收集器是基于标记-压缩算法的。所以,它不会产生空间碎片,也没有必要在收集完成后,进行一次独占式的碎片整理工做。G1 收集器还能够进行很是精确的停顿控制。它可让开发人员指定当停顿时长为 M 时,垃圾回收时间不超过 N。使用参数-XX:+UnlockExperimentalVMOptions –XX:+UseG1GC 来启用 G1 回收器,设置 G1 回收器的目标停顿时间:-XX:MaxGCPauseMills=20,-XX:GCPauseIntervalMills=200。


5. CMS垃圾回收过程架构

  • 初始标记(initial mark) 会STW
    这个阶段从垃圾回收的根引用开始,只扫描到可以和根引用直接关联的对象(包括栈中变量表,方法区静态属性引用对象,常量区引用对象,JNI引用对象),并做标记,主要包括栈引用标记和年轻代引用标记,栈引用标记会在ms内完成,栈引用标记取决于Eden区大小,因此能够经过设置-XX:CMSWaitDuration=等待时间,在初始标记以前进行YGC。这个过程会STW,可是很快就完成了。
     
  • 并发标记(concurrent mark)
    这个阶段紧随初始标记阶段,在初始标记的基础上继续向下追溯标记。并发标记阶段,应用程序的线程和并发标记的线程并发执行,因此用户不会感觉到停顿。
     
  • 并发预清理(concurrent preclean)
    在这个阶段,虚拟机查找在执行并发标记阶段新进入老年代的对象(可能会有一些对象重新生代晋升到老年代, 或者有一些对象被分配到老年代,这样下一阶段可能不须要扫描不少对象)。经过从新扫描,减小下一个阶段从新标记的工做,由于下一个阶段会STW
     
  • 从新标记 (remark)  会STW
    这个阶段会STW,收集器线程扫描在CMS堆中剩余的对象。扫描从根引用开始向下追溯,并处理对象关联。回收器并不须要遍历所有的对象图,只须要遍历从标记开始到当前发生变化的引用便可,同时,线程栈和年轻代须要从新扫描一遍。一般状况下,从新标记的大多数时间,都消耗在扫描年轻代,能够经过设置-XX:CMSScavengeBeforRemark墙纸从新标记前进行YGC。
     
  • 并发清理 (concurrent sweep)
    这个阶段收集器线程和应用程序线程并发执行,全部再也不被引用的对象将从堆里清除掉。
     
  • 并发重置(concurrent reset)
    这个阶段,重置CMS收集器的数据结构,等待下一次垃圾回收。


6. GC 相关参数并发

  • 与串行回收器相关的参数
    -XX:+UseSerialGC:在新生代和老年代使用串行回收器。
    -XX:+SuivivorRatio:设置 eden 区大小和 survivor 区大小的比例。
    -XX:+PretenureSizeThreshold:设置大对象直接进入老年代的阈值。当对象的大小超过这个值时,将直接在老年代分配。
    -XX:MaxTenuringThreshold:设置对象进入老年代的年龄的最大值。每一次 Minor GC 后,对象年龄就加 1。任何大于这个年龄的对象,必定会进入老年代。
     
  • 与并行 GC 相关的参数
    -XX:+UseParNewGC: 在新生代使用并行收集器。
    -XX:+UseParallelOldGC: 老年代使用并行回收收集器。
    -XX:ParallelGCThreads:设置用于垃圾回收的线程数。一般状况下能够和 CPU 数量相等。但在 CPU 数量比较多的状况下,设置相对较小的数值也是合理的。
    -XX:MaxGCPauseMills:设置最大垃圾收集停顿时间。它的值是一个大于 0 的整数。收集器在工做时,会调整 Java 堆大小或者其余一些参数,尽量地把停顿时间控制在 MaxGCPauseMills 之内。
    -XX:GCTimeRatio:设置吞吐量大小,它的值是一个 0-100 之间的整数。假设 GCTimeRatio 的值为 n,那么系统将花费不超过 1/(1+n) 的时间用于垃圾收集。
    -XX:+UseAdaptiveSizePolicy:打开自适应 GC 策略。在这种模式下,新生代的大小,eden 和 survivor 的比例、晋升老年代的对象年龄等参数会被自动调整,以达到在堆大小、吞吐量和停顿时间之间的平衡点。
     
  • 与 CMS 回收器相关的参数
    -XX:+UseConcMarkSweepGC: 新生代使用并行收集器,老年代使用CMS+串行收集器。
    -XX:+ParallelCMSThreads: 设定 CMS 的线程数量。
    -XX:+CMSInitiatingOccupancyFraction:设置 CMS 收集器在老年代空间被使用多少后触发,默认为 68%。
    -XX:+UseFullGCsBeforeCompaction:设定进行多少次 CMS 垃圾回收后,进行一次内存压缩。
    -XX:+CMSClassUnloadingEnabled:容许对类元数据进行回收。
    -XX:+CMSParallelRemarkEndable:启用并行重标记。
    -XX:CMSInitatingPermOccupancyFraction:当永久区占用率达到这一百分比后,启动 CMS 回收 (前提是-XX:+CMSClassUnloadingEnabled 激活了)。
    -XX:UseCMSInitatingOccupancyOnly:表示只在到达阈值的时候,才进行 CMS 回收。
    -XX:CMSWaitDuration:初始标记前进行YGC
    -XX:CMSScavengeBeforRemark:重标记前进行YGC。
    -XX:+CMSIncrementalMode:使用增量模式,比较适合单 CPU。
     
  • 与 G1 回收器相关的参数
    -XX:+UseG1GC:使用 G1 回收器。
    -XX:+UnlockExperimentalVMOptions:容许使用实验性参数。
    -XX:+MaxGCPauseMills:设置最大垃圾收集停顿时间。
    -XX:+GCPauseIntervalMills:设置停顿间隔时间。
     
  • 其余参数
    -XX:+DisableExplicitGC: 禁用显示 GC。
     

7. 通常状况下设置
    -Xmx(size)  // 官方默认为内存1/4,linux不能超过3/4
    -Xms(size)  // 官方默认为内存1/64
    -Xmn(0.25*size ~ 0.5*size)  // 官方推荐为堆的3/8
    -XX:+UseParNewGC
    -XX:+UseConcMarkSweepGC
    -XX:CMSInitiatingOccupancyFraction=70 // 使用了多少后进行full gc,默认92%
    -XX:CMSFullGCsBeforeCompaction=0    // 多少次full gc后对年老代进行压缩

    Xmx大小等于Xms大小,避免堆大小调整
    Xmn年代的大小在1/4~1/2堆大小之间,避免由于年轻代过小,数据大部分移到年老代,最后致使频繁Full GC
    Young GC和Full GC分别采用并行GC和并发GC,充分利用多CPU,新版的JVM中-XX:+UseConcMarkSweepGC默认打开-XX:+UseParNewGC
    Full GC后要进行整理压缩,减小年老区碎片


    java -Xmx1024m -Xms1024m -Xmn400m -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:CMSFullGCsBeforeCompaction=0 -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintHeapAtGC -Xloggc:/home/parallels/gc.log -jar server.jar优化

相关文章
相关标签/搜索