寻找垃圾对象的算法:1. 引用计数(没法处理循环引用) 2. 根寻法(被普遍引用在gc算法中)算法
清理垃圾的算法: 1. 标记复制 2. 标记清理 3. 标记整理数据结构
分代算法的好处:并发
1. 分代处理,能够减小一次处理的内存大小,减小停顿时间。 jvm
2. 不一样的代有不一样的特色,再加上有针对性的gc算法和代码优化,总体的性能更好:性能
年轻代特色是分配快,对象存活时间短,因此采用标记复制时间消耗不会过高,效率高。优化手段就是减小分配需求的速度,减小对象的存活时间,这样使得youngGc频率和停顿都下降,也减小了老年代的压力,是颇有效的优化手段。优化
老年代的特色是内存较大,对象存活时间久,因此相对来讲比较年轻代要复杂。因此有各类各样不一样的gc算法来对老年代进行处理,考量点主要是吞吐量和停顿时间。spa
老年代担任的一个很重要的角色是给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]
三并发标记 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,能够考虑这种状况。