您好,登录后才能下订单哦!
这篇文章给大家介绍一文读懂JVM中的垃圾回收器,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。
JVM的GC经过多年的发展,大家对Minor GC、major GC的理解并不完全一致,所以我不打算在本文中使用这个概念。我把GC大概分为一下4类:
因为笔者目前使用G1还是比较少的,所以本文不打算将G1。
垃圾回收器算法
目前主流垃圾回收器都采用的是可达性分析算法来判断对象是否已经存活,不使用引用计数算法判断对象时候存活的原因在于该算法很难解决相互引用的问题。
标记-清除算法(Mark-Sweep)
标记-清除算法由标记阶段和清除阶段构成。标记阶段是把所有活着的对象都做上标记的阶段;清除阶段是把那些没有标记的对象,也就是非活动对象回收的阶段。通过这两个阶段,就可以令不能利用的内存空间重新得到利用。
从标记-清除算法我们可以看出,该算法不涉及对象移动,但是可能会产生内存碎片化问题。空间碎片太高可能会导致程序运行时需要分配较大内存时候,无法找到足够的连续内存,需要其他垃圾回收帮助回收内存。
复制算法(Copying)
复制算法内存空间分为两块区域:From、to,每次只使用其中一块,在垃圾回收时将正在使用的内存中的存活对象复制到未被使用的内存块中,之后,清除正在使用的内存块中的所有对象,交换两个内存的角色,完成垃圾回收。
上面那种复制算法有一半的空间是浪费的。所以在Java新生代把内存区域分为Eden空间、from、to空间3个部分,from和to空间也称为survivor 空间,用于存放未被回收的对象。对象开始都是Eden生成;当回收时,将Eden和from中存活的对象移动到to区域中。
复制算法存在空间浪费的情况,始终都要保持一个Survivor是空闲的,并且在GC的时候要是存活对象大小超过了Survivor中的大小,就需要另外的策略存储存活对象。
目前open JDK新生代回收策略就是采用的复制算法,其中Eden和Survivor的默认配置为8:1
标记-压缩算法(Mark-Compact)
标记-压缩算法由标记阶段和压缩阶段构成。标记阶段标记-清除算法中的标记阶段完全一样,压缩阶段是让所有存活的对象向一端移动。这样空闲内存都在另外一端,属于连续空间,不存在内存碎片化问题,但是会产生对象移动。
分代算法(Generational GC)
根据对象的不同生命周期分别管理, JVM 中将对象分为我们熟悉的新生代、老年代和永久代分别管理。这样做的好处就是可以根据不同类型对象进行不同策略的管理,例如新生代中对象更新速度快,就会使用效率较高的复制算法。老年代中内存空间相对分配较大,而且时效性不如新生代强,就会常常使用Mark-Sweep-Compact (标记-清除-压缩)算法。
各种算法性能比较
常见的垃圾回收器
垃圾回收器分类
总体上可以把Java的垃圾回收器分为3类:
Java垃圾回收器主要有6种,各自优缺点以及组合关系如下:
其中的连线表示young gc和old gc可以搭配使用
垃圾回收器选择策略:
目前很大一部分的Java应用都集中在互联网的服务器端,这类应用尤其关系服务的响应时间,希望应用暂停时间更短,所以基本上使用的都是ParNew + CMS,这也是我司默认使用的配置。
CMS垃圾回收器
在启动JVM参数加上 -XX:+UseConcMarkSweepGC ,这个参数表示对于老年代的回收采用 CMS。
CMS执行过程
CMS 的回收过程主要分为下面的几个步骤:
CMS日志解析
标准的CMS日志如下:
2018-11-10T18:23:27.531+0800: 1495270.652: [GC (CMS Initial Mark) [1 CMS-initial-mark: 2008820K(2510848K)] 2038212K(4398336K), 0.0231086 secs] [Times: user=0.01 sys=0.00, real=0.03 secs] 2018-11-10T18:23:27.554+0800: 1495270.675: [CMS-concurrent-mark-start] 2018-11-10T18:23:27.644+0800: 1495270.765: [CMS-concurrent-mark: 0.090/0.090 secs] [Times: user=0.34 sys=0.03, real=0.09 secs] 2018-11-10T18:23:27.644+0800: 1495270.765: [CMS-concurrent-preclean-start] 2018-11-10T18:23:27.654+0800: 1495270.775: [CMS-concurrent-preclean: 0.010/0.010 secs] [Times: user=0.02 sys=0.00, real=0.01 secs] 2018-11-10T18:23:27.655+0800: 1495270.775: [CMS-concurrent-abortable-preclean-start] 2018-11-10T18:23:32.305+0800: 1495275.425: [CMS-concurrent-abortable-preclean: 4.623/4.650 secs] [Times: user=7.01 sys=1.01, real=4.65 secs] 2018-11-10T18:23:32.307+0800: 1495275.427: [GC (CMS Final Remark) [YG occupancy: 847369 K (1887488 K)]1495275.427: [Rescan (parallel) , 0.0902177 secs]1495275.518: [weak refs processing, 0.0514433 secs]1495275.569: [class unloading, 0.0256119 secs]1495275.595: [scrub symbol table, 0.0074695 secs]1495275.602: [scrub string table, 0.0015014 secs][1 CMS-remark: 2008820K(2510848K)] 2856190K(4398336K), 0.1806988 secs] [Times: user=0.68 sys=0.00, real=0.18 secs] 2018-11-10T18:23:32.488+0800: 1495275.609: [CMS-concurrent-sweep-start] 2018-11-10T18:23:33.660+0800: 1495276.781: [CMS-concurrent-sweep: 1.172/1.172 secs] [Times: user=1.89 sys=0.24, real=1.17 secs] 2018-11-10T18:23:33.661+0800: 1495276.782: [CMS-concurrent-reset-start] 2018-11-10T18:23:33.667+0800: 1495276.788: [CMS-concurrent-reset: 0.006/0.006 secs] [Times: user=0.01 sys=0.00, real=0.01 secs]
初始标记(CMS Initial Mark)
并发标记(CMS-concurrent-mark)
并发标记就需要标记出 GC ROOTS 关联到的对象的引用对象有哪些。比如说 A -> B (A 引用 B,假设 A 是 GC Roots 关联到的对象),那么这个阶段就是标记出 B 对象, A 对象会在初始标记中标记出来。
并发预清理(CMS-concurrent-preclean)
这个阶段主要并发查找在做并发标记阶段时从年轻代晋升到老年代的对象或老年代新分配的对象(大对象直接进入老年代)或被用户线程更新的对象,来减少重新标记阶段的工作量。
重新标记(CMS Final Remark)
由于在并发标记和并发预清理这个阶段,用户线程和GC 线程并发,假如这个阶段用户线程产生了新的对象,总不能被 GC 掉吧。这个阶段就是为了让这些对象重新标记。该阶段也会暂停应用
并发清理(CMS-concurrent-sweep)
这个阶段的目的就是移除那些不用的对象,回收他们占用的空间并且为将来使用。注意这个阶段会产生新的垃圾,新的垃圾在此次GC无法清除,只能等到下次清理。这些垃圾有个专业名词:浮动垃圾。
并发重置(CMS-concurrent-reset)
CMS清除内部状态,为下次回收做准备。
注意:CMS虽然是老年代算法,但也是需要扫描新生代区域的。
CMS算法降级
cms存在着内存碎片化问题:申请内存时,虽然总内存大于申请内存,但是没有连续内存大于申请内存,导致内存申请失败。CMS提供了机制(CMS GC降级到Full GC)来解决该问题。Full GC使用的算法是mark-sweep-compact(类似于Serial垃圾回收器),他的作用域在整个堆的对象,包括年轻代、老年代、持久代,但compaction是可选的。其中参数CMSFullGCsBeforeCompaction=N表示每隔N次真正的full GC才做一次压缩(而不是每N次CMS GC就做一次压缩,目前JVM里没有这样的参数),CMSFullGCsBeforeCompaction默认值是0,也就是每次full GC都会进行内存压缩。这个尽量使用默认值,不然内存碎片化可能会更严重些。
那么配置的CMS GC啥时候会触发Full gc呢?主要有下面几种情况触发Full Gc:
如何识别是执行的是CMS GC还是 Full GC呢?主要是根据GC log,CMS GC会在日志中标记出各个执行阶段,但是要是执行Full GC只会显示full次数加1。
CMS相关参数
-XX:CMSInitiatingOccupancyFraction=N 和-XX:+UseCMSInitiatingOccupancyOnly
这两个设置一般配合使用, 目的在于降低CMS GC频率或者增加频率。
-XX:CMSInitiatingOccupancyFraction=N 是指设定CMS在对内存占用率达到N%的时候开始进行CMS GC。
-XX:+UseCMSInitiatingOccupancyOnly 只是用设定的回收阈值(上面指定的N%),如果不指定,JVM仅在第一次使用设定值,后续则自动调整.
-XX:+CMSScavengeBeforeRemark
这个参数表示CMS GC前启动一次ygc,目的在于减少old区域对ygc区域的引用,降低remark时的开销,一般CMS的GC耗时80%都在remark阶段
-XX:+UseCMSCompactAtFullCollection和-XX:CMSFullGCsBeforeCompaction=N
这两个参数要配合使用,其中CMSFullGCsBeforeCompaction上面已经讲解过了。
CMS 的缺点
关于一文读懂JVM中的垃圾回收器就分享到这里了,希望以上内容可以对大家有一定的帮助,可以学到更多知识。如果觉得文章不错,可以把它分享出去让更多的人看到。
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。