JVM之垃圾回收

简介

JVM具有自动垃圾回收处理,比起手动回收,开发效率提高了,并且不会出现忘记回收和多次回收的问题。通过了解垃圾回收的原理,选择合适的垃圾回收器,可以提高程序的性能。

如何标记垃圾

在没有任何引用指向一个对象时称为垃圾,通过引用计数法/根可达算法来标记垃圾

  1. 引用计数法就是通过给对象添加一个引用计数器,有对象被一个地方引用时,计数器加1,当没引用时则减1,这种算法有一个缺陷,无法标记互相引用的对象,在Java中没用这种算法作为标记垃圾的算法
  2. 根可达算法就是通过一些GC Roots的对象作为起始点,从这个起始点向下搜索,走过的地方叫引用链,当一个对象到GC Roots没有引用链相连时则标记为垃圾

目前Java中可作为GC Root 的对象有:虚拟机栈中的对象、静态属性的对象、常量池中的对象、本地方法栈中引用的对象

上图中红色块的object都没有引用链到GC Roots,则会被标记成为垃圾

在根可达算法中,并不是被标记完是垃圾就立马回收,而是先执行finalize(),并将对象放在F-Queue中,在第二次垃圾回收时才会被回收,若是finalize()中有把到GC Roots的引用链连接上,则不会进行回收

垃圾回收算法

标记清除算法(mark sweep)

标记清除算法需要进行两次扫描,第一次是标记哪些是垃圾,第二次是回收被标记的垃圾,所以这种算法带来两个问题一是效率偏低,二是产生内存碎片,

 

复制算法(copying)

为了解决标记清除算法的问题,有了复制算法,将内存分为两个区域,只使用其中一块区域,在垃圾回收时将还存活的对象复制到另一块未使用的区域,再一次性回收掉未存活的对象。这种算法不会产生空间碎片,但可使用的内存只有一半


标记压缩算法(mark compact)

垃圾回收过程与标记清除算法一样,区别是标记后会将存活对象移动到另一端(调整指针),再清理掉边界以外的内存。不会产生内存碎片,效率比复制算法

分代收集算法 

为了提高垃圾回收的效率,一般将堆中对象的存活周期分新生代、老年代,再根据不同的区域选择不同的垃圾回收算法。

例如在新生代每次垃圾回收都有大量的对象被回收,所以选择复制算法,将其分为Eden区和两个Survivor区,新创建的对象都在Eden区,永远保证其中一个Survivor区是空的,在垃圾回收时,将Eden区和一个Survivor区的存活对象复制到空的Survivor区中,每次垃圾回收只需要复制对象就能完成垃圾回收。

在老年代中大多数对象会长期存活,没有额外的空间分配担保,所以使用标记压缩算法或者标记清除算法。

这就是堆为啥分为老年代和年轻代的原因。

垃圾收集器

Hospot垃圾收集器,分为新生代收集器和老年代收集器,连线表示可以配合使用 

1.Serial

新生代收集器,使用复制算法,使用一个线程GC,在GC过程必须暂停其他线程的工作(stop the word)。与其他收集器相比优势是简单高效,没有与其他线程交互的开销,在单个CPU环境中效率最高。

使用-XX:+UseSerialGC可以用Serial+Serial Old组合进行垃圾回收

2.ParNew

Serial的多线程版,减少了暂停其他线程的工作的时间

使用-XX:+UseParNewGC可以用ParNew+Serial Old组合进行垃圾回收;

使用-XX:ParallelGCThreads来设置GC的线程数

3.Parallel Scavenge

使用复制算法,多线程,GC过程必须暂停其他线程的工作,关注CPU吞吐量,减少GC的时间,让用户代码获得更长的运行时间。是1.8中默认的垃圾收集器。比较适合运算比较多,交互比较少的场景,如报表汇总的程序。

使用-XX:+UseParallelGC可以用Parallel Scavenge+Serial Old组合进行垃圾回收

使用-XX:GCTimeRatio可以设置用户代码执行时间占总时间的比例,默认99,即1%的时间用来GC

使用-XX:MaxGCPauseMillis设置GC最大停顿时间,这只是个建议时间,可能达不到这个时间,会尽可能靠近这个时间

4.Serial Old

Serial的老年代版本,使用的是标记压缩算法

5.Parallel Old

Parallel Scavenge的老年代版本,使用的是标记压缩算法

使用-XX:+UseParallelOldGC可以用Parallel Scavenge+Parallel Old组合进行垃圾回收

6.CMS(Concurrent Mark Sweep)

以减少最短停顿时间为目标的垃圾收集器,注重用户体验,让垃圾回收与用户代码同时运行(基本上),逻辑更复杂,使用的是标记清除算法

过程分为:

  1. 初始化标记:暂停其他线程,寻找垃圾,时间较短
  2. 并发标记:寻找垃圾,用户线程与GC线程同时运行,时间很长
  3. 重新标记:修改并发标记过程中因用户线程继续运行导致标记产生变动那一部分对象的标记记录,暂停其他线程,时间比并发标记短
  4. 并发清理:开启用户线程,并将标记的对象进行回收

使用-XX:+UseConcMarkSweepGC可以指定CMS收集器

使用-XX:CMSInitiatingOccupancyFraction设置CMS预留内存空间,因为在并发清除时,用户线程可能产生新垃圾,称为浮动垃圾,所以不能像其他垃圾收集器一样等内存满了再清理,如果预留空间无法满足浮动垃圾的内存空间则会产生“Concurrent Mode Failure”,并使用Serail Old进行一次Full GC。可以降低CMSInitiatingOccupancyFraction保持足够的老年代空间

使用-XX:+UseCMSCompactAtFullCollection出现Concurrent Mode Failure不进行Full GC,而进行内存压缩整理

使用-XX:+CMSFullGCsBeforeCompaction设置多少次Full GC后进行压缩整理,默认为0

7.G1

G1设计初衷出来是为了在将来能替代CMS的,使用G1收集器时,将堆分为了大小相等的独立区域(Region),年轻代和老年代只是一个概念,在物理上不再进行分代,它们都存在于Region上。有以下特点:

  1. 并行:充分利用多CPU、多核环境下的优势,减少stop the word的时间
  2. 分代收集:独立管理整个堆,针对不同存活时间的对象有不同的管理方式
  3. 不会产生空间碎片:使用标记压缩算法
  4. 可预测停顿时间:G1获得每个Region的收集价值大小,维护一个优先列表,优先回收价值最大的Region,可以有计划地避免在Java堆的进行全区域的垃圾收集

使用-XX:+UseG1GC可以指定G1收集器

-XX:MaxGCPauseMillis:建议值,G1的暂停时间,会通过调整年轻代的数量接近这个时间

XX:G1HeapRegionSize:每块Region的大小,越大垃圾存活越久,GC间隔时间越长,每次GC时间越长

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值