jvm垃圾收集器 以及 垃圾收集算法

简介: jvm垃圾收集器 以及 垃圾收集算法


一.垃圾收集器:
1.串行回收器
1.1, 新生代串行回收器

(1)特点: 
  –它仅仅使用单线程进行垃圾回收 
  –它是独占式的垃圾回收 
  –进行垃圾回收时, Java应用程序中的线程都需要暂停(Stop-The-World) 
  –使用复制算法 
  –适合CPU等硬件不是很好的场合 
(2)设置参数: 
  -XX:+UseSerialGC 指定新生使用新生代串行收集器和老年代串行收集器, 当以client模式运行时, 它是默认的垃圾收集器
1.2, 老年代串行回收器

(1)特点: 
  –同新生代串行回收器一样, 单线程, 独占式的垃圾回收器 
  –通常老年代垃圾回收比新生代回收要更长时间, 所以可能会使应用程序停顿较长时间 
(2)设置参数: 
  -XX:+UseSerialGC 新生代, 老年代都使用串行回收器 
  -XX:+UseParNeGC 新生代使用ParNew回收器, 老年代使用串行回收器 
  -XX:+UseParallelGC 新生代使用ParallelGC回收器, 老年代使用串行回收器
2, 并行回收器
2.1, 新生代ParNew回收器

(1)特点: 
  –将串行回收多线程化, 
  –使用复制算法 
  –垃圾回收时, 应用程序仍会暂停, 只不过由于是多线程回收, 在多核CPU上,回收效率会高于串行回收器, 反之在单核CPU, 效率会不如串行回收器 
(2)设置参数: 
  -XX:+UseParNewGC 新生代使用ParNew回收器, 老年代使用串行回收器 
  -XX:+UseConcMarkSweepGC 新生代使用ParNew回收器, 老年代使用CMS回收器 
  -XX:ParallelGCThreads=n 指回ParNew回收器工作时的线程数量, cpu核数小时8时, 其值等于cpu数量, 高于8时,可以使用公式(3+((5*CPU_count)/8))
2.2, 新生代ParallelGC回收器

(1)特点: 
  –同ParNew回收器一样, 不同的地方在于,它非常关注系统的吞吐量(通过参数控制) 
  –使用复制算法 
  –支持自适应的GC调节策略

(2)设置参数:

  -XX:+UseParallelGC  新生代用ParallelGC回收器, 老年代使用串行回收器 
  -XX:+UseParallelOldGC  新生代用ParallelGC回收器, 老年代使用ParallelOldGC回收器系统吞吐量的控制: 
  -XX:MaxGCPauseMillis=n(单位ms)   设置垃圾回收的最大停顿时间, 
  -XX:GCTimeRatio=n(n在0-100之间)  设置吞吐量的大小, 假设值为n, 那系统将花费不超过1/(n+1)的时间用于垃圾回收 
  -XX:+UseAdaptiveSizePolicy  打开自适应GC策略, 在这种模式下, 新生代的大小, eden,survivior的比例, 晋升老年代的对象年龄等参数会被自动调整,以达到堆大小, 吞吐量, 停顿时间之间的平衡点

2.3, 老年代ParallelOldGC回收器

(1)特点: 
  –同新生代的ParallelGC回收器一样, 是属于老年代的关注吞吐量的多线程并发回收器 
  –使用标记压缩算法, 
(2)设置参数: 
  -XX:+UseParallelOldGC  新生代用ParallelGC回收器, 老年代使用ParallelOldGC回收器, 是非常关注系统吞吐量的回收器组合, 适合用于对吞吐量要求较高的系统 
  -XX:ParallelGCThreads=n   指回ParNew回收器工作时的线程数量, cpu核数小时8时, 其值等于cpu数量, 高于8时, 可以使用公式(3+((5*CPU_count)/8))
3.CMS回收器(Concurrent Mark Sweep,并发标记清除)
3.1, 老年代的并发回收器

(1)特点: 
  –是并发回收, 非独占式的回收器, 大部分时候应用程序不会停止运行 
  –针对年老代的回收器, 
  –使用并发标记清除算法, 因此回收后会有内存碎片, 可以使参数设置进行内存碎片的压缩整理 
  –与ParallelGC和ParallelOldGC不同, CMS主要关注系统停顿时间 
(2)CMS主要步骤: 
  1. 初始标记 
  2. 并发标记 
  3. 预清理 
  4. 重新标记 
  5. 并发清理 
  6. 并发重置

–>注:初始标记与理新标记是独占系统资源的,不能与用户线程一起执行,而其它阶段则可以与用户线程一起执行 
(3)设置参数: 
  -XX:-CMSPrecleaningEnabled  关闭预清理, 不进行预清理, 默认在并发标记后, 会有一个预清理的操作,可减少停顿时间 
  -XX:+UseConcMarkSweepGC  老年代使用CMS回收器, 新生代使用ParNew回收器 
  -XX:ConcGCThreads=n  设置并发线程数量, 
  -XX:ParallelCMSThreads=n  同上, 设置并发线程数量, 
  -XX:CMSInitiatingOccupancyFraction=n  指定老年代回收阀值, 即当老年代内存使用率达到这个值时, 会执行一次CMS回收,默认值为68, 设置技巧: (Xmx-Xmn)*(100-CMSInitiatingOccupancyFraction)/100)>=Xmn 
  -XX:+UseCMSCompactAtFullCollection  开启内存碎片的整理, 即当CMS垃圾回收完成后, 进行一次内存碎片整理, 要注意内存碎片的整理并不是并发进行的, 因此可能会引起程序停顿 
  -XX:CMSFullGCsBeforeCompation=n  用于指定进行多少次CMS回收后, 再进行一次内存压缩 
  -XX:+CMSParallelRemarkEnabled  在使用UseParNewGC 的情况下, 尽量减少 mark 的时间 
  -XX:+UseCMSInitiatingOccupancyOnly  表示只有达到阀值时才进行CMS回收

3.2, Class的回收(永久区的回收)

设置参数: 
  -XX:+CMSClassUnloadingEnabled  开启回收Perm区的内存, 默认情况下, 是需要触发一次FullGC 
  -XX:CMSInitiatingPermOccupancyFraction=n  当永久区占用率达到这个n值时,启动CMS回收, 需上一个参数开启的情况下使用

  1. G1回收器(jdk1.7后全新的回收器, 用于取代CMS)

(1)特点: 
  –独特的垃圾回收策略, 属于分代垃圾回收器, 
  –使用分区算法, 不要求eden, 年轻代或老年代的空间都连续 
  –并行性: 回收期间, 可由多个线程同时工作, 有效利用多核cpu资源 
  –并发性: 与应用程序可交替执行, 部分工作可以和应用程序同时执行, 
  –分代GC: 分代收集器, 同时兼顾年轻代和老年代 
  –空间整理: 回收过程中, 会进行适当对象移动, 减少空间碎片 
  –可预见性: G1可选取部分区域进行回收, 可以缩小回收范围, 减少全局停顿 
(2)G1的收集过程 

  1. 新生代GC: 
  2. 并发标记周期: 

  –初始标记新生代GC(此时是并行, 应用程序会暂停止)–>根区域扫描–>并发标记–>重新标记(此时是并行, 应用程序会暂停止)–>独占清理(此时应用程序会暂停止)–>并发清理 

  1. 混合回收: 

  –这个阶段即会执行正常的年轻代gc, 也会选取一些被标记的老年代区域进行回收, 同时处理新生代和年老轻 

  1. 若需要, 会进行FullGC: 

  –混合GC时发生空间不足 
  –在新生代GC时, survivor区和老年代无法容纳幸存对象时, 
  –以上两者都会导致一次FullGC产生 
(3)设置参数: 
  -XX:+UseG1GC  打开G1收集器开关, 
  -XX:MaxGCPauseMillis=n  指定目标的最大停顿时间,任何一次停顿时间超过这个值, G1就会尝试调整新生代和老年代的比例, 调整堆大小, 调整晋升年龄 
  -XX:ParallelGCThreads=n  用于设置并行回收时, GC的工作线程数量 
  -XX:InitiatingHeapOccpancyPercent=n  指定整个堆的使用率达到多少时, 执行一次并发标记周期, 默认45, 过大会导致并发标记周期迟迟不能启动, 增加FullGC的可能, 过小会导致GC频繁, 会导致应用程序性能有所下降
5.其他GC相关的设置
5.1, System.gc()

(1)禁用System.gc() 
  -XX:+DisableExplicitGC  禁止程序中调用System.gc(), 加了此参数, 程序若有调用, 返回的空函数调用 
   System.gc()的调用, 会使用FullGC的方式回收整个堆而会忽略CMS或G1等相关回收器 
(2)System.gc()使用并发回收 
  -XX:+ExplicitGCCinvokesConcurrent   使用并发方式处理显示的gc, 即开启后, System.gc()这种显示GC才会并发的回收, (CMS, G1)
5.2, 并行GC前额外触发的新生代GC

(1)使用并行回收器(UseParallelGC或者UseParallelOldGC)时, 会额外先触发一个新生代GC, 目的是尽可能减少停顿时间 
(2)若不需要这种特性, 可以使用以下参数去除 
  -XX:-ScavengeBeforeFullGC   即去除在FullGC之前的那次新生代GC, 原本默认值为true 
5.3, 对象何时进入老年代

(1)当对象首次创建时, 会放在新生代的eden区, 若没有GC的介入,会一直在eden区, GC后,是可能进入survivor区或者年老代 
(2)当对象年龄达到一定的大小 ,就会离开年轻代, 进入老年代, 对象进入老年代的事件称为晋升, 而对象的年龄是由GC的次数决定的, 每一次GC,若对象没有被回收, 则对象的年龄就会加1, 可以使用以下参数来控制新生代对象的最大年龄: 
  -XX:MaxTenuringThreshold=n  假设值为n , 则新生代的对象最多经历n次GC, 就能晋升到老年代, 但这个必不是晋升的必要条件 
  -XX:TargetSurvivorRatio=n  用于设置Survivor区的目标使用率,即当survivor区GC后使用率超过这个值, 就可能会使用较小的年龄作为晋升年龄 
(3)除年龄外, 对象体积也会影响对象的晋升的, 若对象体积太大, 新生代无法容纳这个对象, 则这个对象可能就会直接晋升至老年代, 可通过以下参数使用对象直接晋升至老年代的阈值, 单位是byte 
  -XX:PretenureSizeThreshold  即对象的大小大于此值, 就会绕过新生代, 直接在老年代分配, 此参数只对串行回收器以及ParNew回收有效, 而对ParallelGC回收器无效.
5.4, 在TLAB上分配对象(Thread Local Allocation Buffer, 线程本地分配缓存)

(1)TLAB: TLAB是一个线程专用的内存分配区域, 虚拟机为线程分配空间, 针对于体积不大的对象, 会优先使用TLAB, 这个可以加速对象的分配, TLAB是默认开启的, 若要关闭可以使用以下参数关闭 
  -XX:-UseTLAB  关闭TLAB 
  -XX:+UseTLAB  开启TLAB, 默认也是开启的 
  -XX:+PrintTLAB  观察TALB的使用情况 
  -XX:TLABRefillWasteFraction=n  设置一个比率n, 而refill_waste的值就是(TLAB_SIZE/n), 即TLAB空间较小, 大对象无法分配在TLAB,所以会直接分配到堆上,TLAB较小也很容易装满, 因此当TLAB的空间不够分配一个新对象, 就会考虑废弃当前TLAB空间还是直接分配到堆上, 就会使用此参数进行判断, 小于refill_waste就允许废弃, 而新建TLAB来分配对象,而大于refill_waste就直接在堆上分配, 默认是64 
  -XX:+ResizeTLAB  开启TLAB自动调整大小, 默认是开启的, 若要关闭把+号换成-号即可 
  -XX:TLABSize=n  设置一个TLAB的大小, 前提先关闭TLAB的自动调整
二 .jvm垃圾收集算法:
   java虚拟机JVM垃圾收集算法有四种:标记-清除算法、复制算法、标记-整理算法以及分代收集算法。
1、标记-清除算法

      这是JVM最基础的垃圾收集算法。如下图:

          

      该算法分为两个阶段:“标记”和“清除”。首先标记处所有需要回收的对象,然后统一清除被标记的对象。

      该算法,标记和清除两个阶段的效率不高。此外,回收后会产生大量的不连续的内存碎片,进一步会导致垃圾回收次数的增加。

2、复制算法

      为了解决标记-清除算法的效率问题,出现了复制算法,如下图:

          

      该算法的思想是将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这块的内存用完了,则将还存活的对象复制到另外一块内存上去,然后再把刚使用过的内存空间一次清理掉。从而达到了每次垃圾回收时只是针对其中一块,避免了产生内存碎片等情况。

      该算法的代价是只是使用了其中一本的内存,代价有点高。

      随着研究的不断发现,商业虚拟机在该算法中不断进行优化尝试。比如在HotSpot虚拟机中,新生代中Eden和Survivor的大小比例为8::1,因为新生代的对象需要回收的概率大(对象的生命周期短,存活率低),所以内存的可用率达到了90%(新生代分为:Eden和两块Survivor)。每次都是把Eden和Survivor中存活的对象拷贝到另一块Survivor中,然后清理掉Eden和Survivor空间。

3、标记-整理算法

      当复制收集算法面对的回收对象为存活率较高的情况时,要执行较多的复制操作,效率会变低。为了提高这些对象垃圾回收效率,充分利用可用内存,标记-整理算法出现了。如下图:

        

      该算法集成了标记-清除和复制收集算法的优点。第一个阶段仍是进行标记,第二个阶段是把所有存活的对象都向一端移动,按顺序排放,然后直接清理掉端边界意外的内存。该算法避免了标记-清除的内存碎片问题以及复制算法的空间问题。该算法适合于老年代对象的回收。

4、分代收集

      这是目前大多数虚拟机采用的垃圾回收算法。基于对象的生命周期划分为新生代、老年代以及持久代。比如新生代就采用复制收集算法,而老年代就采用标记-清除或者标记-整理算法。如下图:

          

      对于分代收集,虚拟机需要区分对象的分配年代,是放在新生代还是否放在老年代?。解决的办法是:jvm为每个对象定义了一个对象年龄计数器。如果对象在Eden出生并且经过第一次新生代GC(Minor GC)后仍然存活并且能被Survivor容纳,则该对象将被移动到另一块Survivor空间,并将对象年龄计数器加1。对象在Survivor区中每经历过一次Minor GC,年龄计数器就加1,当它的年龄达到设定的阈值(默认是15)时,则被移动到老年代中。阈值的设置通过参数-XX:MaxTenuringThreshold设置。

      当然,并不是一定达到阈值才被移动到老年代,为了适应复杂的情况,动态的判定对象年龄,虚拟机规定:如果Survivor空间中相同年龄的所有对象大小的总和大于Survivor空间的一半,对象的年龄大于或者等于该年龄的对象就可以直接进入老年代,不必等到达到设定的阈值。

相关文章
|
4天前
|
存储 算法 Java
深入浅出JVM(十八)之并发垃圾收集器G1
深入浅出JVM(十八)之并发垃圾收集器G1
|
4天前
|
存储 算法 Java
深入浅出JVM(十七)之并发垃圾收集器CMS
深入浅出JVM(十七)之并发垃圾收集器CMS
|
4天前
|
算法 Java
深入浅出JVM(十五)之垃圾收集器(上篇)
深入浅出JVM(十五)之垃圾收集器(上篇)
|
4天前
|
安全 算法 Java
深入浅出JVM(十三)之垃圾回收算法细节
深入浅出JVM(十三)之垃圾回收算法细节
|
4天前
|
存储 算法 Java
深入浅出JVM(十二)之垃圾回收算法
深入浅出JVM(十二)之垃圾回收算法
|
4天前
|
算法 Java PHP
JVM 的垃圾回收机制以及垃圾回收算法的详解
JVM 的垃圾回收机制以及垃圾回收算法的详解
10 0
|
4天前
|
Arthas 监控 算法
JVM工作原理与实战(二十五):堆的垃圾回收-垃圾回收算法
JVM作为Java程序的运行环境,其负责解释和执行字节码,管理内存,确保安全,支持多线程和提供性能监控工具,以及确保程序的跨平台运行。本文主要介绍了垃圾回收算法评价标准、标记清除算法、复制算法、标记整理算法、分代垃圾回收算法等内容。
22 0
JVM工作原理与实战(二十五):堆的垃圾回收-垃圾回收算法
|
4天前
|
算法 Java
JVM 垃圾回收算法(重要)
JVM 垃圾回收算法(重要)
|
4天前
|
存储 缓存 算法
深度解析JVM世界:垃圾判断和垃圾回收算法
深度解析JVM世界:垃圾判断和垃圾回收算法
|
4天前
|
算法 Java UED
【五一创作】值得一看的JVM垃圾收集器
【五一创作】值得一看的JVM垃圾收集器