GC算法基础
寻找垃圾对象的算法:1. 引用计数(无法处理循环引用) 2. 根寻法(被广泛引用在gc算法中)
清理垃圾的算法: 1. 标记复制 2. 标记清理 3. 标记整理
分代算法的好处:
1. 分代处理,可以减少一次处理的内存大小,减少停顿时间。
2. 不同的代有不同的特点,再加上有针对性的gc算法和代码优化,整体的性能更好:
年轻代特点是分配快,对象存活时间短,所以采用标记复制时间消耗不会太高,效率高。优化手段就是减少分配需求的速度,减少对象的存活时间,这样使得youngGc频率和停顿都降低,也减少了老年代的压力,是很有效的优化手段。
老年代的特点是内存较大,对象存活时间久,所以相对来说比较年轻代要复杂。所以有各种各样不同的gc算法来对老年代进行处理,考量点主要是吞吐量和停顿时间。
老年代担任的一个很重要的角色是给youngGC做担保,保证对象的晋升可以成功。所以这里的一个重要的指标是,FullGC发生的频率。FullGC的发生,一般是由于老年代的内存清理速度更不上晋升的速度,或者是老年代的内存碎片,所以保证对象的存活时间尽可能的短是有很多好处的。
GC算法:
1. 串行GC
2. ThroughPutGC(并行GC)—目的:提高吞吐量
3. ConcurrentGC— 目的:降低停顿
垃圾收集器:
Serial收集器:
年轻代的收集器+串行GC+标记复制
ParNew收集器:
年轻代的收集器+并行GC+标记复制
Parallel Scavenge收集器:
年轻代的收集器+并行GC+标记复制。 和ParNew的区别可能是引进了用户设定目标,gc自己调整大小的机制
Serial Old收集器:
老年代的收集器+串行GC+标记整理
Parallel Old收集器:
老年代的收集器+并行GC+标记整理
CMS收集器:
老年代的收集器+并发GC+标记清理
G1收集器:
年轻代+老年代的收集器
年轻代=并行GC+标记复制
老年代=并发GC+标记整理
GC算法选择:
Gc算法的选择考虑的方向主要是停顿时间和吞吐量俩个方面。需要根据程序的特征,CPU资源来进行权衡。
1. 先从吞吐量来说,其实考虑的主要是可用的cpu资源有多少(即有多少空闲的cpu资源)。如果空闲的cpu资源较少,使用throughput类的gc会更好,因为它本身的收集效率就比并发的gc好。而并发的gc算法会和应用程序抢夺cpu资源。就会造成吞吐量下降。而如果空闲的cpu资源很多,那么并发的gc可以利用这些空闲的cpu来并发的完成gc,而且对应用程序的影响是很小的。反正那些cpu空着也是空着。如果这时使用throughput收集器,这些空闲的cpu会被白白浪费,而且还会stw来独占cpu进行gc,如果gc本身无法完全利用这些cpu资源,而且还会影响应用程序,就得不偿失了。
2. 在衡量停顿时间上,一般来说,throughput的收集器的90%的响应时间都要比并发gc的快,因为90%的时间是没有gc的,只有在发生full-gc时才会导致响应时间过长。 但是,当full-gc发生的更频繁的时候,这个百分比也就会下降。当然,当发生这种情况是,并发gc也许也会难以避免full-gc。
CMS和G1的选择:
一般认为,当堆比较大时(大于4G),使用G1比较好,否则CMS的性能会更好一点。原因在于,gc消耗的时间和堆的大小是有很大的关系的,比如标记需要扫描整个堆。但是G1对堆进行了进一步的划分,在清理周期中,可以只清理垃圾最多的一部分分区,所以在大堆的情况下,性能是比CMS要好一些。另外,由于G1是标记复制的算法,所以不容易造成内存碎片。
反过来,由于G1的算法和数据结构要更加复杂,所以它的内存和cpu的消耗也就更大。如果堆本身就比较小的话,使用G1会造成应用程序可以使用的堆大小会更小。
GC调优基础:
1. 调整堆的大小:
A.堆过小,导致gc频率变高。 堆太大,导致单次gc的时间变长。所以,要通过调整对的大小来找到一个我们认为相对合适的目标。目标主要从停顿时间和吞吐量俩个方面来考虑。
B.堆的大小,不要超过机器的物理内存,因为jvm对底层的swap并无感知,swap带来的gc停顿变长,可能会导致并发失效,从而导致full-gc
C.-Xms4096m -Xmx4096m 这种最大和初始一样时,堆的大小就不会因为自适应调整而改变。当我们知道最合适的堆的大小时,这种设置是有好处的,因为jvm不需要再进行堆的大小计算和调整。但是当我们不知道合适的堆的大小应该是多少时,我觉得还是不要这么搞吧。
2. 调整代的大小:
新生代太小,会导致youngGc频率高,对象晋升快,对老年代的压力增大。 新生代太大,新生代的gc时间可能会变长,对象晋升变慢,老年代的大小受限,可能会导致full-gc。
老年代太小,可能会导致full-gc变多。老年代太大,可能会导致老年代的gc太慢。
-XX:NewRatio=N
-XX:NewSize=N
-XmnN
-XX:MaxNewSize=N
3. 调整永久代和元空间的大小:
永久代和元空间也会触发fullgc,所以也需要关注一下大小是否合适。
4. 控制并发:
gc的线程任务是计算密集型
-XX:ParallelGCThreads=N 来控制并并GC的线程数,比如:
a.使用-XX:+UseParallelGC收集新生代空间
b.使用-XX:+UseParallelOldGC收集老年代空间
c. CMS的“STW”阶段(不包括Full-GC)
d. G1的“STW”阶段(不包括Full-GC)
e.使用-XX:+UseParNewGC收集的新生代空间
f.使用-XX:+UseG1GC收集的新生代空间
5. 自适应调整:
用户设定目标:响应时间和gc消耗时间占比 -XX:MaxGCPauseMillis=N 和 -XX:GCTimeRatio=N ,这俩个配置会同时影响年轻代和老年代
自适应调整会调整堆的大小,新生代和老年代的大小来尽量适用目标值。如果堆的初始值和最大值一样,则自适应调整不会对堆的整体大小进行调整。如果堆,年轻代的初始值和最大值一样,则自适应就完全失效了,不过survivor还是会进行自适应调整。
-XX:-UseAdaptiveSizePolicy 关闭自适应(默认是开)。 -XX:+PrintAdaptiveSizePolicy 在gc时会打印调整的信息。
对精细化计算过的jvm内存比例,可以把各个区域的大小进行限定,这样可以减少堆自适应的消耗。
Throughput收集器:
Gc分为youngGc和fullGC。正常的FullGc不会对永久区进行回收,当回收区满时,会触发fullGc,这时会对永久区进行回收。
调优:
1.自适应调整:首先会调整堆的大小来达到目标停顿时间,然后慢慢增大堆的大小来尽可能达到目标吞吐量。然后又开始降低堆的大小来减少jvm的内存占用。目标值的设定一定要合理,太极端会导致极端的问题。
2.调整并行线程数,因为是stw,而且gc的任务是计算密集型的,所以线程数根据cpu和计算密集型的公式就好。
CMS:
CMS有三个gc动作:1. yooung区的gc(STW) 2.老年代的gc(并发gc+标记清除) 3.full-gc(串行full-gc)
老年代的GC过程:
1. 初始标记(STW): 标记那些直接被Root对象引用的对象
2.并发标记: 并发的进行Root Tracing的过程,时间较长。无法处理浮动垃圾。
3.重新标记(STW): 主要是对步骤2中可能出现的遗漏进行补充,时间比1长,但是远比2短
4.并发清理:并发的进行垃圾的清理,不会进行内存压缩,所以可能会造成内存碎片
一 YGC 89.853: [GC 89.853: [ParNew: 629120K->69888K(629120K), 0.1218970 secs]
- 1303940K->772142K(2027264K), 0.1220090 secs]
- [Times: user=0.42 sys=0.02, real=0.12 secs]
- user的时间表示cpu时间,real表示时钟的时间。对于并发gc,real时间一般比user时间少。
- 二初始标记 89.976: [GC [1 CMS-initial-mark: 702254K(1398144K)]
- 772530K(2027264K), 0.0830120 secs]
- [Times: user=0.08 sys=0.00, real=0.08 secs]
三并发标记 90.059: [CMS-concurrent-mark-start]
90.887: [CMS-concurrent-mark: 0.823/0.828 secs]
[Times: user=1.11 sys=0.00, real=0.83 secs]
四预清理 90.887: [CMS-concurrent-preclean-start]
90.892: [CMS-concurrent-preclean: 0.005/0.005 secs]
[Times: user=0.01 sys=0.00, real=0.01 secs]
五重新标记 90.892: [CMS-concurrent-abortable-preclean-start]
92.392: [GC 92.393: [ParNew: 629120K->69888K(629120K), 0.1289040 secs]
1331374K->803967K(2027264K), 0.1290200 secs]
[Times: user=0.44 sys=0.01, real=0.12 secs]
94.473: [CMS-concurrent-abortable-preclean: 3.451/3.581 secs]
[Times: user=5.03 sys=0.03, real=3.58 secs]
94.474: [GC[YG occupancy: 466937 K (629120 K)]
94.474: [Rescan (parallel) , 0.1850000 secs]
94.659: [weak refs processing, 0.0000370 secs]
94.659: [scrub string table, 0.0011530 secs]
[1 CMS-remark: 734079K(1398144K)]
1201017K(2027264K), 0.1863430 secs]
[Times: user=0.60 sys=0.01, real=0.18 secs]
这个过程执行了多个步骤,首先是可中断的预清理。由于jvm为了避免俩次连续的停顿(刚发生一次YGC后,立马发生重新标记),所以在发生YGC后,jvm预测下一个YGC发生的时间周期,在这个周期中间停止了可停顿预清理,然后开始了重新标记。
这也意味了,老年代的并发周期和YGC是并发进行的。
六并发清理阶段 94.661: [CMS-concurrent-sweep-start]
95.223: [GC 95.223: [ParNew: 629120K->69888K(629120K), 0.1322530 secs]
999428K->472094K(2027264K), 0.1323690 secs]
[Times: user=0.43 sys=0.00, real=0.13 secs]
95.474: [CMS-concurrent-sweep: 0.680/0.813 secs]
[Times: user=1.45 sys=0.00, real=0.82 secs]
这里日志表示,在并发清理阶段,发送了YGC,也表明老年代的并发周期和YGC是并发进行的。而且在并发周期中至少会发生一次YGC,就是可中断预清理的过程中。
七并发重置阶段 95.474: [CMS-concurrent-reset-start]
95.479: [CMS-concurrent-reset: 0.005/0.005 secs]
[Times: user=0.00 sys=0.00, real=0.00 secs]
这是并发周期的最后一个阶段,但是我们没法中日志中得知这个阶段回收了多少内存,只能从上下的GC日志中进行推测。
八并发模式失败(concurrent mode failure)
267.006: [GC 267.006: [ParNew: 629120K->629120K(629120K), 0.0000200 secs]
267.006: [CMS267.350: [CMS-concurrent-mark: 2.683/2.804 secs]
[Times: user=4.81 sys=0.02, real=2.80 secs]
(concurrent mode failure):
1378132K->1366755K(1398144K), 5.6213320 secs]
2007252K->1366755K(2027264K),
[CMS Perm : 57231K->57222K(95548K)], 5.6215150 secs]
[Times: user=5.63 sys=0.00, real=5.62 secs]
这是在发生YGC时,老年代没有足够的空间来容纳晋升的对象,就退化成了FULL-GC。这个过程是串行的,所以很慢。
九晋升失败 6043.903: [GC 6043.903:
[ParNew (promotion failed): 614254K->629120K(629120K), 0.1619839 secs]
6044.217: [CMS: 1342523K->1336533K(2027264K), 30.7884210 secs]
2004251K->1336533K(1398144K),
[CMS Perm : 57231K->57231K(95548K)], 28.1361340 secs]
[Times: user=28.13 sys=0.38, real=28.13 secs]
这是在发生YGC后,而且JVM判断old区有足够的空间容纳晋升对象,但是在晋升的过程中,由于old区的内存碎片,导致的晋升失败,然后进行了FULL-GC. 由于这个过程是发送YGC失败导致的,所以耗时比并发模式失败还要多。
十永久代或者元空间用尽导致的full-gc
279.803: [Full GC 279.803:
[CMS: 88569K->68870K(1398144K), 0.6714090 secs]
558070K->68870K(2027264K),
[CMS Perm : 81919K->77654K(81920K)],
0.6716570 secs]
G1: G1对堆内存进全部行了分区(region),一个代的空间里的分区并不是连续的。有些分区属于老年代,有些分区属于年轻代。年轻代的gc和其他回收器是一样的,只所以也进行分区是因为可以更方便的进行分区的大小调整。老年代的gc是对垃圾最多的分区进行清理,这样可以使用更少的时间来达到最好的清理效果。所以对于比较大的堆,G1的停顿时间是要比CMS好的。 G1的老年代清理是把一个region
G1的4个操作:
1.新生代垃圾的收集 :并行收集
2.后台收集,并发周期:对垃圾最多的分区进行标记,期间至少会发生一次YGC
3.混合式垃圾收集
4.必要的full-gc
并发周期:
a. 50.541: [GC pause (young) (initial-mark), 0.27767100 secs]
[Eden: 1220M(1220M)->0B(1220M)
Survivors: 144M->144M Heap: 3242M(4096M)->2093M(4096M)]
[Times: user=1.02 sys=0.04, real=0.28 secs]
并发周期的开始—初始化标记。这个动作是STW的,这里使用了YGC来进行STW,
b. 50.819: [GC concurrent-root-region-scan-start]
51.408: [GC concurrent-root-region-scan-end, 0.5890230]
扫描根分区,这个过程是并发进行的。但是,这个阶段中,不能进行YGC,如果这个时候触发了YGC,YGC必须等待这个动作的结束,然后再进行。这导致了YGC时间变长,意味着需要进行调优了,所以就会有下面这种日志。
350.994: [GC pause (young)
351.093: [GC concurrent-root-region-scan-end, 0.6100090]
351.093: [GC concurrent-mark-start],
0.37559600 secs]
c. 350.994: [GC pause (young)
351.093: [GC concurrent-root-region-scan-end, 0.6100090]
351.093: [GC concurrent-mark-start],
0.37559600 secs]
并发标记,这个过程是可以中断的,中间是可以有YGC并发进行的。
d. 120.910: [GC remark 120.959:
[GC ref-PRC, 0.0000890 secs], 0.0718990 secs]
[Times: user=0.23 sys=0.01, real=0.08 secs]
120.985: [GC cleanup 3510M->3434M(4096M), 0.0111040 secs]
[Times: user=0.04 sys=0.00, real=0.01 secs]
重新标记和清理阶段。这俩个阶段是STW的,而且这个清理阶段只会清理很少的垃圾,主要还是对垃圾的标记。
e. 120.996: [GC concurrent-cleanup-start]
120.996: [GC concurrent-cleanup-end, 0.0004520]
一个并发清理,同样回收的垃圾很少。
混合式垃圾收集周期:
这个周期会进行YGC和老年代的并发清理。这是stw的
a. 79.826: [GC pause (mixed), 0.26161600 secs]
....
[Eden: 1222M(1222M)->0B(1220M)
Survivors: 142M->144M Heap: 3200M(4096M)->1964M(4096M)]
[Times: user=1.01 sys=0.00, real=0.26 secs]
混合式垃圾回收周期会进行多轮,直到满足我们设定的指标。然后整个老年代的回收周期就结束了。
FULL-GC的发生:
1. 并发模式失败:老年代开始了标记周期,但是在标记周期完成之前就被填满了。这种情况下,G1日志里会有放弃标记周期的日志:
51.408: [GC concurrent-mark-start]
65.473: [Full GC 4095M->1395M(4096M), 6.1963770 secs]
[Times: user=7.87 sys=0.00, real=6.20 secs]
71.669: [GC concurrent-mark-abort]
2. 晋升失败:老年代已经开始了mix-gc周期,但是老年代在垃圾回收释放出足够的内存之前就被耗尽了。这种情况是mix-gc之后马上就有一次Full-Gc
2226.224: [GC pause (mixed)
2226.440: [SoftReference, 0 refs, 0.0000060 secs]
2226.441: [WeakReference, 0 refs, 0.0000020 secs]
2226.441: [FinalReference, 0 refs, 0.0000010 secs]
2226.441: [PhantomReference, 0 refs, 0.0000010 secs]
2226.441: [JNI Weak Reference, 0.0000030 secs]
(to-space exhausted), 0.2390040 secs]
....
[Eden: 0.0B(400.0M)->0.0B(400.0M)
Survivors: 0.0B->0.0B Heap: 2006.4M(2048.0M)->2006.4M(2048.0M)]
[Times: user=1.70 sys=0.04, real=0.26 secs]
2226.510: [Full GC (Allocation Failure)
2227.519: [SoftReference, 4329 refs, 0.0005520 secs]
2227.520: [WeakReference, 12646 refs, 0.0010510 secs]
2227.521: [FinalReference, 7538 refs, 0.0005660 secs]
2227.521: [PhantomReference, 168 refs, 0.0000120 secs]
2227.521: [JNI Weak Reference, 0.0000020 secs]
2006M->907M(2048M), 4.1615450 secs]
[Times: user=6.76 sys=0.01, real=4.16 secs]
3.疏散失败:在新生代的回收中,survivor区和老年代没有足够的内存来容纳晋升和存活的对象,这时会有一个比较特别的日志:
60.238: [GC pause (young) (to-space overflow), 0.41546900 secs]
4.巨型对象分配失败:没有什么好的办法来排查这种问题,如果出现了比较奇怪的full-gc,可以考虑这种情况。
GC算法基础的更多相关文章
- GC参考手册 —— GC 算法(基础篇)
本章简要介绍GC的基本原理和相关技术, 下一章节再详细讲解GC算法的具体实现.各种垃圾收集器的实现细节虽然并不相同,但总体而言,垃圾收集器都专注于两件事情: 查找所有存活对象 抛弃其他的部分,即死对象 ...
- 4. GC 算法(实现篇) - GC参考手册
您应该已经阅读了前面的章节: 垃圾收集简介 - GC参考手册 Java中的垃圾收集 - GC参考手册 GC 算法(基础篇) - GC参考手册 学习了GC算法的相关概念之后, 我们将介绍在JVM中这些算 ...
- JVM垃圾回收(三)- GC算法:基础
GC算法:基础 在介绍GC算法在实际场景中的实现之前,我们先定义一些必要的术语,以及GC算法的基本准则.具体的细节会因收集器的不同而稍有区别,但是基本上来说,所有的收集器会关注以下两个方面: 找出所有 ...
- C#基础-gc算法
众所周知,c++是需要程序员手动管理内存的,然而手动释放内存很容易被程序员遗漏,从而导致资源浪费或内存泄露.为解决这个问题,垃圾回收器诞生了,代替程序员自动管理内存的释放.至于gc算法则是垃圾回收器清 ...
- JVM学习(4)——全面总结Java的GC算法和回收机制
俗话说,自己写的代码,6个月后也是别人的代码……复习!复习!复习!涉及到的知识点总结如下: 一些JVM的跟踪参数的设置 Java堆的分配参数 -Xmx 和 –Xms 应该保持一个什么关系,可以让系统的 ...
- jvm系列(三):java GC算法 垃圾收集器
GC算法 垃圾收集器 概述 垃圾收集 Garbage Collection 通常被称为“GC”,它诞生于1960年 MIT 的 Lisp 语言,经过半个多世纪,目前已经十分成熟了. jvm 中,程序计 ...
- Java内存管理及GC算法
概述 内存划分 虚拟机规范中将内存分为六大部分,分别为PC寄存器.JAVA虚拟机栈.JAVA堆.方法区.运行时常量及本地方法栈. 1.PC寄存器:线程独占: 2.JAVA虚拟机栈:线程独有:JAVA虚 ...
- JVM内存管理------GC算法精解(复制算法与标记/整理算法)
本次LZ和各位分享GC最后两种算法,复制算法以及标记/整理算法.上一章在讲解标记/清除算法时已经提到过,这两种算法都是在此基础上演化而来的,究竟这两种算法优化了之前标记/清除算法的哪些问题呢? 复制算 ...
- JVM内存管理------GC算法精解(五分钟让你彻底明白标记/清除算法)
相信不少猿友看到标题就认为LZ是标题党了,不过既然您已经被LZ忽悠进来了,那就好好的享受一顿算法大餐吧.不过LZ丑话说前面哦,这篇文章应该能让各位彻底理解标记/清除算法,不过倘若各位猿友不能在五分钟内 ...
随机推荐
- jmeter入门简介(一)
简介 Apache JMeter是100%纯JAVA桌面应用程序,被设计为用于测试CS/BS的软件.它可以用来测试静态和动态资源的性能,可用于模拟大量负载来测试一台服务器,网络或者对象的健壮性或者分析 ...
- MySQL添加用户并授权
一. 创建用户 命令: CREATE USER 'username'@'host' IDENTIFIED BY 'password'; 说明: username:你将创建的用户名 host:指定该用户 ...
- 关于处理java.lang.IllegalArgumentException at java.sql.Date.getHours异常
https://segmentfault.com/a/1190000016937108
- linux 安装java环境
1.检查是否安装或者linux系统自带jdK 命令:java -version 查找JDK相关包是否被安装: rpm -qa |grep jdk rpm -qa |grep gcj 删除JDK相关包: ...
- 【iOS】値の判断
NSString str; ){ //nilの判断 //nullの判断 //長さの判断 // ...... }
- Ubuntu16.04 藍牙連上,但是聲音裏面找不到設備
解決辦法: 1. sudo apt-get install blueman bluez* 2. sudo vim /etc/pulse/default.pa 注釋掉下面的代碼: #.ifexists ...
- Vue数据交互
注意:本地json文件必须放在static目录下面,读取或交互数据前,请先安装vue-resource.点击前往 -->(vue-resource中文文档) 一.Vue读取本地JSON数据 c ...
- SpringBoot集成MyBatis的分页插件 PageHelper
首先说说MyBatis框架的PageHelper插件吧,它是一个非常好用的分页插件,通常我们的项目中如果集成了MyBatis的话,几乎都会用到它,因为分页的业务逻辑说复杂也不复杂,但是有插件我们何乐而 ...
- 浅谈spring为什么推荐使用构造器注入
转载自: https://www.cnblogs.com/joemsu/p/7688307.html 一.前言 Spring框架对Java开发的重要性不言而喻,其核心特性就是IOC(Inversi ...
- wpf 寻找TreeView的子元素,并对其进行操作
//itemsControl 开始为指定的TreeView控件 item为TreeView子元素 private void PareItems(ItemsControl itemsControl, ...