概述
前文已说明,GC的主要工作对象就是堆(和方法区)
堆中分为年轻代(Eden+2个Survior)和老年代就是为了GC服务的,事实上堆完全可以用一个大堆完成所有的工作
- 年轻代
设计年轻代的目的仅仅是为了提升GC效率.是因为大部分的对象都是瞬时对象,也就是很快就不再起作用了.对瞬时对象快速回收,可以极大的提升堆的使用效率. - 年轻代分解为Eden+2个Survior
年轻代再分解为3块则是因为 对象始终优先分配Eden,而年轻代始终维持快速回收.这带来一个问题就是年轻代的内存碎片会非常严重.为此使用了复制算法这种高度浪费内存的算法来快速解决内存碎片
两个Survior始终保持有一个空白,然后通过交替复制来整理碎片.对象首先进入Eden.而在年轻代GC后Eden的剩余对象会进入Survior(空白),再将另一个Survior(使用)的剩余对象全部复制过来.这样交替进行.
这样每次年轻代GC后始终保持 - Eden和其中一个Survior空白
- 另一个Survior中对象保持紧凑连续
关键术语
- STM STM(Stop The World) 是GC中的专业术语.
它描述的是,在GC过程中,不得不暂停所有的应用线程.此时对应用来说,就好像世界被停止一样 - 响应时间
描述的应用程序的单次停顿时间.单次停顿越短表示响应速度越快 - 吞吐量
描述应用程序的总停顿时间,总停顿时间越少表示吞吐量越高.
对象存活判定
GC活动的本质是对不存活的对象进行回收释放,那么首先就是:如何判定一个对象是否存活
引用计数
这是一种已经被淘汰的判定方式,其最大的问题难以界定一个仅仅互相引用的对象是否应该回收
对象可达性
这是如今主流的对象存活判定依据,主要过程如下
- 扫描应用程序根对象
- 根对象继续往下遍历
三色法.已遍历的是黑色,本身已遍历但子对象没有遍历的为灰色(等待递归遍历的),垃圾为白色 - 没有经过的对象就是垃圾对象
卡表
卡表是一种数据结构,类似一个List[Bit],每一个Bit位表示老年代的某一段区域中是否存在某一个老年代对象指向新生代的
卡表的目的是为了快速年轻代回收.工作原理类似一个二级索引.年轻代扫描时可以不扫描整个老年代而是先扫描卡表.只有在某个位标识其中包含有至少一个老年代对象对新生代的引用时才会考虑进入老年代的该区域去详细扫描
回收算法
标记-清除算法
算法分为标记,然后清除两个阶段.
标记清除是一种快速算法,但标记清除最大的问题因为缺乏内存整理而造成内存碎片
标记清除是最基础的算法,后续算法都是针对标记清除算法缺点来演进的
复制算法
复制算法是将内存空间分为两块(各50%),每次GC都会把剩余对象全部移动到另一边
复制算法的优点在于自带内存整理,并且其算法速度相对标记整理也要稍微快些
缺点
- 内存天然只能使用一半,极大的浪费空间
- 如果存活率相对高的话会导致GC过程更慢
标记-整理算法
与标记-清除相同,但清除完毕会将剩余对象全部前移成一个连续部分,也是消除碎片算法
比复制算法的好处在于至少可以使用100%空间
但算法速度较复制算法更慢,因为标记整理的算法是在同一个内存空间中不断移动左移来消除碎片
分代算法
针对不同的特点,将堆分为几代然后对每代分别使用不同的算法策略
GC工作区
GC主要工作于以下位置
方法区
方法区也是可以参与GC的.JVM规范中的确表示可以不对方法区进行GC,而事实上大部分的方法区的回收效率比确实很低.因为方法区的绝大部分都是回收不了
但这只是通用情况,具体应该根据具体的应用程序情况来决定
比如,大量使用反射或者运行时加载时,对方法区GC是非常必要的.事实上在这种情况下,方法区会比常规堆区更加脆弱,因为方法区都远比堆小,并且关注点一般也不在方法区上.但方法区满会直接启动FullGC,其危害又是比较严重的
方法区的回收主要针对 类元数据 和 常量池
类元数据的判定比较复杂,首先没有该类的任何实例,其次属于该类的ClassLoader已经被回收,最后其.class信息也没有任何引用,才会被判定为可回收类元数据.常量池就相对简单,没有任何引用就判定可回收
方法区GC一个比较坑是方法区与堆不同,方法区中的可回收不代表会回收
堆区
堆区,这也是GC的工作重点
GC类别
MinorGC
如果在新生代分配不出一个对象内存,就会触发一次新生代GC.这种GC,就称为MinorGC
因为绝大多数新创建的对象都会被分配到Eden,所以MinorGC是最容易发生的GC.
整个过程
- 对象持续分配Eden,如果Eden分配不出来内存,就会触发MinorGC
- MinorGC首先清理年轻代,然后将Eden活下来的全部搬运给S(to)
- 使用的S(From)判断年龄,如果年龄超标即对其进行晋升到老年代.如果年龄没有超标则也复制S(From)
- 每次MinorGC后,都可以得到空白的Eden和一个空白的Survior,以及一个紧凑的Survivor
MajorGC
MajorGC一般指老年代中的GC,但实际很难界定.事实上MajorGC一般都伴随一个前置的MinorGC
老年代的对象一般都是存活很久,有很高几率还会继续使用的对象,所以触发MajorGC相对不频繁,一定是在MinorGC无法解决的时候才会触发
相对的,MajorGC的代价消耗也更高,因为老年代一般对象都很多,并且垃圾率并不高
FullGC
FullGC是一种比较特殊的GC,就是同时对整个堆进行GC.包括年轻代,老年代以及方法区
FullGC一般发生在
- 程序手动触发执行 System.gc
- MinorGC 到 MajorGC,但是发现老年代也存储不下.这是FullGC清理整个堆区,做最后的尝试
如果FullGC后仍然不能分配对象,就会抛出OOM了
MixGC
MixGC是一种G1才有的概念,它是指一个年轻代GC和部分的老年代GC
GC 产品
新生代GC
SerialGC
串行收集器是一个新生代收集器,是最古老,最稳定以及效率高的收集器,可能会产生较长的停顿,只使用一个线程去回收
ParNewGC
ParNew收集器是一个新生代收集器,其实就是Serial收集器的多线程版本
采用算法:复制算法
参数控制:
-XX:+UseParNewGC ParNew收集器
-XX:ParallelGCThreads 限制线程数量
Parallel Scavenge
Parallel Scavenge收集器是一个新生代收集器,它也是使用复制算法的收集器,又是并行的多线程收集器
老年代GC
Serial Old
Parallel Old
Parallel Old是Parallel Scavenge收集器的老年代版本,使用多线程和“标记-整理”算法
CMS GC
一种追求最短停顿时间的并发标记回收器
优点是并发搜集停顿短
缺点是会产生内存碎片,并发阶段会降低吞吐量
后面详述
G1 GC
G1是如今最前沿的回收器成果之一.它进一步分解引入了分区(Region)概念,其中年轻代和老年代不再是连续的空间而仅仅是分区属性.
G1的优点是自带内存整理和可预测停顿
后面详述
常见GC组合
- UseConcMarkSweepGC
新生代使用ParNew,老年代使用CMS - UseParNewGC
新生代ParNew,老年代Serial Old - UseParallelGC
新生代Parallel Scavenge,老年代Serial Old - UseParallelOldGC
新生代Parallel Scavenge,老年代Parallel Old - UseG1GC
新生代使用G1GC,老年代使用G1GC
JVM默认GC
- 1.7
+UseParallelGC : Parallel Scavenge +Parallel Old - 1.8
+UseParallelGC : Parallel Scavenge +Parallel Old - 1.9
G1
GC调优
调优原则
- GC调优是为了满足系统对响应速度和吞吐量的要求.如果系统本身的响应速度和吞吐量已经满足要求了就没必要再行GC调优.
- 同等情况下,建议优先满足响应速度后吞吐量(即优先追求单次最低)
- FullGC一般都是GC的主要瓶颈所在,如何有效的尽可能规避FullGC是优化的重点
- GC调优是一个反复过程,需要根据实际情况反复实验才能达到很好的效果.所以GC日志,是优化的主要凭据
GC监视
**概览 **
jmap -heap [pid] 查看内存分布
jstat -gcutil [pid] 1000 每隔1s输出java进程的gc情况
jstat -gcutil pid
- YGC Minor GC执行的次数
- YGCT Minor GC执行的总耗时
- YGCT/YGC Minor GC平均耗时
- FGC Full GC执行的次数
- FGCT Full GC执行的总耗时
- FGCT/FGC Full GC平均耗时
**详细信息 **
-verbose:gc -XX:+PrintGCTimeStamps -XX:+PrintGCDetails -Xloggc:./gc.log
GcViewer 导入GC日志,可以以图表方式展示,非常方便
GC日志解读
通用
33.125: [GC [DefNew: 3324K->152K(3712K), 0.0025925 secs] 3324K->152K(11904K), 0.0031680 secs]
100.667: [Full GC [Tenured: 0K->210K(10240K), 0.0149142 secs] 4603K->210K(19456K), [Perm : 2999K->2999K(21248K)], 0.0150007 secs] [Times: user=0.01 sys=0.00, real=0.02 secs]
- 最前面的数字表示GC的时间,含义是从JVM启动以来的秒数
- GC 与 FullGC 说明的是停顿类型(跟年轻代老年代方法区谁发起无关) FullGC表示STW
- DefNew Tenured Perm 表示发生区域.注意这个名字是GC收集器取的,不同收集器名称不同
- 方括号内的3324K->152K(3712K) 表示 GC前目标区->GC后目标区的内存容量
- 方括号外的3324K->152K(11904K) 表示 GC前堆 -> GC后堆的内存容量
- 0.0031680 secs 表示本次GC的耗用时间(秒)
- Times中 user,sys,real分别表示的用户态CPU时间,内核态CPU时间和WallClock 时间
CPU时间只包括CPU消耗时间,IO等待不算在内,如果是多核情况会累加.墙钟时间是普通意义上的系统时间,IO等待等计算在内,但多核CPU并行计算不叠加
Serial/SerialOld
新生代
0.148: [GC 0.148: [DefNew: 32443K->334K(39296K), 0.0383600 secs] 32443K->25080K(126720K),
0.0384220 secs] [Times: user=0.02 sys=0.01, real=0.04 secs]
对象分布
0.280: [GC 0.280: [DefNew
Desired survivor size 2228224 bytes, new threshold 15 (max 15)
- age 1: 56 bytes, 56 total
- age 2: 56 bytes, 112 total
- age 3: 40 bytes, 152 total
- age 4: 536 bytes, 688 total
- age 5: 314800 bytes, 315488 total
: 20473K->308K(39296K), 0.0282400 secs]0.309: [Tenured: 105619K->105927K(107592K), 0.0054030 secs]
105927K->105927K(146888K), [Perm : 2872K->2872K(21248K)], 0.0340160 secs]
[Times: user=0.02 sys=0.01, real=0.04 secs]
晋升失败
0.633: [GC 0.633: [DefNew (promotion failed) : 136158K->149171K(157248K), 0.1441370 secs]0.777:
[Tenured: 333101K->327922K(349568K), 0.0983430 secs] 370626K->327922K(506816K),
[Perm : 2872K->2872K(21248K)], 0.2425880 secs] [Times: user=0.18 sys=0.07, real=0.24 secs]
年老代
2.012: [Full GC 2.012: [Tenured: 343656K->216196K(349568K), 0.0886910 secs]
492212K->216196K(506816K), [Perm : 2870K->2870K(21248K)], 0.0887540 secs]
[Times: user=0.09 sys=0.00, real=0.09 secs]
ParNew
新生代
0.130: [GC 0.130: [ParNew: 24187K->342K(39296K), 0.0275930 secs] 24187K->16133K(126720K),
0.0276490 secs] [Times: user=0.03 sys=0.01, real=0.03 secs]
对象分布
0.245: [GC 0.245: [ParNew
Desired survivor size 2228224 bytes, new threshold 15 (max 15)
- age 1: 392 bytes, 392 total
- age 2: 56 bytes, 448 total
- age 3: 256 bytes, 704 total
- age 4: 311144 bytes, 311848 total
: 29587K->451K(39296K), 0.0349430 secs]0.280: [Tenured: 97630K->80633K(104540K),
0.0371550 secs] 98793K->80633K(143836K), [Perm : 2872K->2872K(21248K)], 0.0723530 secs]
[Times: user=0.08 sys=0.03, real=0.08 secs]
晋升失败
5.031: [GC 5.031: [ParNew (promotion failed): 132163K->146998K(157248K), 0.0405880 secs]5.071:
[Tenured: 335614K->284433K(349568K), 0.1145870 secs] 382197K->284433K(506816K),
[Perm : 2870K->2870K(21248K)], 0.1552900 secs] [Times: user=0.19 sys=0.00, real=0.16 secs]
年老代
0.954: [Full GC 0.954: [Tenured: 338420K->337591K(349568K), 0.1746320 secs]
472544K->429640K(506816K), [Perm : 2872K->2870K(21248K)], 0.1747090 secs]
[Times: user=0.17 sys=0.00, real=0.17 secs]
Parallel/ParallelOld
新生代
0.110: [GC [PSYoungGen: 32768K->384K(38208K)] 32768K->26662K(125632K), 0.0317620 secs]
[Times: user=0.04 sys=0.03, real=0.03 secs]
新生代是PSYoungGen,年老代是ParOldGen,永久代是PSPermGen
对象分布
0.223: [GC
Desired survivor size 5570560 bytes, new threshold 7 (max 15)
[PSYoungGen: 29285K->416K(38208K)] 187914K->181767K(239104K), 0.0342700 secs]
[Times: user=0.04 sys=0.01, real=0.04 secs]
年老代
0.186: [Full GC [PSYoungGen: 29813K->13540K(38208K)] [ParOldGen: 64141K->77375K(87424K)]
93955K->90916K(125632K) [PSPermGen: 2872K->2870K(21248K)], 0.0321400 secs]
[Times: user=0.03 sys=0.01, real=0.03 secs]
完整log
2015-04-30T14:54:41.461+0800: 99170.722: [GC [PSYoungGen: 690000K->8498K(688704K)] 2084845K->1405110K(2086848K), 0.0223390 secs] [Times: user=0.05 sys=0.00, real=0.02 secs]
2015-04-30T14:55:05.874+0800: 99195.134: [GC [PSYoungGen: 688690K->7584K(689600K)] 2085302K->1404798K(2087744K), 0.0223200 secs] [Times: user=0.04 sys=0.01, real=0.03 secs]
2015-04-30T14:55:05.913+0800: 99195.173: [Full GC [PSYoungGen: 7584K->0K(689600K)] [ParOldGen: 1397214K->913970K(1398144K)] 1404798K->913970K(2087744K) [PSPermGen: 83025K->82697K(166016K)], 4.3123060 secs] [Times: user=13.48 sys=0.17, real=4.32 secs]
2015-04-30T14:55:31.227+0800: 99220.488: [GC [PSYoungGen: 680192K->8010K(689792K)] 1594162K->921980K(2087936K), 0.0412930 secs] [Times: user=0.09 sys=0.00, real=0.04 secs]
参考指标
- MiniorGC的平均耗时少于50毫秒,平均频率大于10秒一次
- MajorGC平均耗时少于1秒,平均频率大于10分钟一次
优化思路
堆代空间调整
详见后篇
选择更优秀的GC产品
详见后篇