作者简介肖桦江南白衣开源项目SpringSide春天的旁边发起者 转自 原本想把题目更简单的定为《不要停》的但还是自己YY一下就算了 Java开发Server最大的障碍就是JDK版之前的的串行垃圾收集机制会引起长时间的服务暂停明白原理后想想那些用JDK写Server的先辈不得不后怕 好在JDK已开始支持多线程并行的后台垃圾收集算法JDK则优化了默认值的设置 一参考资料 Tuning Garbage Collection with the Java Virtual Machine 官方指南 Hotspot memory management whitepaper 官方白皮书 Java Tuning White Paper 官方文档 FAQ about Garbage Collection in the Hotspot 官方FAQJVM Java HotSpot 虚拟机中的垃圾收集 JavaOne上的中文ppt A Collection of JVM Options JVM选项的超完整收集 二基本概念 堆(Heap) JVM管理的内存叫堆在Bit操作系统上有GG的限制而Bit的就没有 JVM初始分配的内存由Xms指定默认是物理内存的/但小于G JVM最大分配的内存由Xmx指定默认是物理内存的/但小于G 默认空余堆内存小于%时JVM就会增大堆直到Xmx的最大限制可以由XX:MinHeapFreeRatio=指定 默认空余堆内存大于%时JVM会减少堆直到Xms的最小限制可以由XX:MaxHeapFreeRatio=指定 服务器一般设置XmsXmx相等以避免在每次GC 后调整堆的大小所以上面的两个参数没啥用 基本收集算法 复制将堆内分成两个相同空间从根(ThreadLocal的对象静态对象)开始访问每一个关联的活跃对象将空间A的活跃对象全部复制到空间B然后一次性回收整个空间A因为只访问活跃对象将所有活动对象复制走之后就清空整个空间不用去访问死对象所以遍历空间的成本较小但需要巨大的复制成本和较多的内存 标记清除(marksweep)收集器先从根开始访问所有活跃对象标记为活跃对象然后再遍历一次整个内存区域把所有没有标记活跃的对象进行回收处理该算法遍历整个空间的成本较大暂停时间随空间大小线性增大而且整理后堆里的碎片很多 标记整理(marksweepcompact)综合了上述两者的做法和优点先标记活跃对象然后将其合并成较大的内存块 可见没有免费的午餐无论采用复制还是标记清除算法自动的东西都要付出很大的性能代价 分代 分代是Java垃圾收集的一大亮点根据对象的生命周期长短把堆分为个代YoungOld和Permanent根据不同代的特点采用不同的收集算法扬长避短也 Young(Nursery)年轻代研究表明大部分对象都是朝生暮死随生随灭的因此所有收集器都为年轻代选择了复制算法 复制算法优点是只访问活跃对象缺点是复制成本高因为年轻代只有少量的对象能熬到垃圾收集因此只需少量的复制成本而且复制收集器只访问活跃对象对那些占了最大比率的死对象视而不见充分发挥了它遍历空间成本低的优点 Young的默认值为M随堆内存增大约为/JVM会根据情况动态管理其大小变化 XX:NewRatio= 参数可以设置Young与Old的大小比例server时默认为:但实际上young启动时远低于这个比率?如果信不过JVM也可以用Xmn硬性规定其大小有文档推荐设为Heap总大小的/ Young的大小非常非常重要见后面暂停时间优先收集器的论述 Young里面又分为个区域一个Eden所有新建对象都会存在于该区两个Survivor区用来实施复制算法每次复制就是将Eden和第一块Survior的活对象复制到第块然后清空Eden与第一块SurviorEden与Survivor的比例由XX:SurvivorRatio=设置默认为Survivio大了会浪费小了的话会使一些年轻对象潜逃到老人区引起老人区的不安但这个参数对性能并不重要 Old(Tenured)年老代年轻代的对象如果能够挺过数次收集就会进入老人区老人区使用标记整理算法因为老人区的对象都没那么容易死的采用复制算法就要反复的复制对象很不合算只好采用标记清理算法但标记清理算法其实也不轻松每次都要遍历区域内所有对象所以还是没有免费的午餐啊 XX:MaxTenuringThreshold=设置熬过年轻代多少次收集后移入老人区CMS中默认为熬过第一次GC就转入可以用XX:+PrintTenuringDistribution查看 Permanent持久代装载Class信息等基础数据默认M如果是类很多很多的服务程序需要加大其设置XX:MaxPermSize=否则它满了之后会引起fullgc()或Out of Memory 注意SpringHibernate这类喜欢AOP动态生成类的框架需要更多的持久代内存 minor/major collection 每个代满了之后都会促发collection(另外Concurrent Low Pause Collector默认在老人区%的时候促发)GC用较高的频率对young进行扫描和回收这种叫做minor collection 而因为成本关系对Old的检查回收频率要低很多同时对Young和Old的收集称为major collection Systemgc()会引发major collection使用XX:+DisableExplicitGC禁止它或设为CMS并发XX:+ExplicitGCInvokesConcurrent 小结 Young minor collection 复制算法 Old(Tenured) major colletion 标记清除/标记整理算法 三收集器 古老的串行收集器(Serial Collector) 使用 XX:+UseSerialGC策略为年轻代串行复制年老代串行标记整理 吞吐量优先的并行收集器(Throughput Collector) 使用 XX:+UseParallelGC 也是JDK server的默认值策略为 年轻代暂停应用程序多个垃圾收集线程并行的复制收集线程数默认为CPU个数CPU很多时可用–XX:ParallelGCThreads=减少线程数 年老代暂停应用程序与串行收集器一样单垃圾收集线程标记整理 所以需要+的CPU时才会优于串行收集器适用于后台处理科学计算 可以使用XX:MaxGCPauseMillis= 和 XX:GCTimeRatio 来调整GC的时间 暂停时间优先的并发收集器(Concurrent Low Pause CollectorCMS) 前面说了这么多都是为了这节做铺垫 使用XX:+UseConcMarkSweepGC策略为 年轻代同样是暂停应用程序多个垃圾收集线程并行的复制收集 年老代则只有两次短暂停其他时间应用程序与收集线程并发的清除 年老代详述 并行(Parallel)与并发(Concurrent)仅一字之差并行指多条垃圾收集线程并行并发指用户线程与垃圾收集线程并发程序在继续运行而垃圾收集程序运行于另一个个CPU上 并发收集一开始会很短暂的停止一次所有线程来开始初始标记根对象然后标记线程与应用线程一起并发运行最后又很短的暂停一次多线程并行的重新标记之前可能因为并发而漏掉的对象然后就开始与应用程序并发的清除过程可见最长的两个遍历过程都是与应用程序并发执行的比以前的串行算法改进太多太多了!!! 串行标记清除是等年老代满了再开始收集的而并发收集因为要与应用程序一起运行如果满了才收集应用程序就无内存可用所以系统默认%满的时候就开始收集内存已设得较大吃内存又没有这么快的时候可以用XX:CMSInitiatingOccupancyFraction=恰当增大该比率 年轻代详述 可惜对年轻代的复制收集依然必须停止所有应用程序线程原理如此只能靠多CPU多收集线程并发来提高收集速度但除非你的Server独占整台服务器否则如果服务器上本身还有很多其他线程时切换起来速度就 所以搞到最后暂停时间的瓶颈就落在了年轻代的复制算法上 因此Young的大小设置挺重要的大点就不用频繁GC而且增大GC的间隔后可以让多点对象自己死掉而不用复制了但Young增大时GC造成的停顿时间攀升得非常恐怖比如在我的机器上默认M的Young只需要几毫秒的时间M就升到毫秒而升到M时就要到毫秒了峰值还会攀到恐怖的ms谁叫复制算法要等Young满了才开始收集开始收集就要停止所有线程呢 持久代 可设置XX:+CMSClassUnloadingEnabled XX:+CMSPermGenSweepingEnabled使CMS收集持久代的类而不是fullgcnetbeans performance文档的推荐 增量(train算法)收集器(Incremental Collector) 已停止维护–Xincgc选项默认转为并发收集器 四暂停时间显示 加入下列参数 (请将PrintGC和Details中间的空格去掉CSDN很怪的认为是禁止字句) verbose:gc XX:+PrintGC Details XX:+PrintGCTimeStamps 会程序运行过程中将显示如下输出 : [GC : [ParNew: K>K(K) secs] K>K(K) secs] 显示在程序运行的秒发生了Minor的垃圾收集前一段数据针对新生区从k整理为k新生区总大小为k程序暂停了ms而后一段数据针对整个堆 对于年老代的收集暂停发生在下面两个阶段CMSremark的中断是毫秒 [GC [ CMSinitialmark: K(K)] K(K) secs] [ CMSremark: K(K)] K(K) secs] 再加两个参数 XX:+PrintGCApplicationConcurrentTime XX:+PrintGCApplicationStoppedTime对暂停时间看得更清晰 五真正不停的BEA JRockit 与Sun RTS Bea的JRockit R 的特色之一是动态决定的垃圾收集策略用户可以决定自己关心的是吞吐量暂停时间还是确定的暂停时间再由JVM在运行时动态决定改变改变垃圾收集策略 它的Deterministic GC的选项是Xgcprio: deterministic号称可以把暂停可以控制在毫秒非常的牛一句Deterministic道尽了RealTime的真谛 不过细看一下文档ms的测试环境是 GB heap 和 平均 % 的活跃对象(也就是M)活动对象 个 Xeon GHz G内存 或者是 个Xeon GHzG内存 最可惜JRockt的license很奇怪虽然平时使用免费但这个ms的选项就需要购买整个Weblogic Real Time Server的license 其他免费选项有 Xgcprio:pausetime Xpausetarget=ms 因为免费所以最低只能设置到ms pause target ms是Sun认为RealTime的分界线 Xgc:gencon 普通的并发做法效率也不错 JavaOne上有Sun的 Java RealTime System 的介绍RTS基于JDK在RealTime Garbage Collctor上又有改进但还在beta版状态只供给OEM更怪 六JDK 的改进 因为JDK在Young较大时的表现还是不够让人满意又继续看JDK的改进结果稍稍失望不涉及我最头痛的年轻代复制收集改良 年老代的标识清除收集并行执行标识 JDK只开了一条收集进程与应用线程并发标识而可以开多条收集线程来做标识缩短标识老人区所有活动对象的时间 加大了Young区的默认大小 默认大小从M加到M从堆内存的/增加到/ Systemgc()可以与应用程序并发执行 使用XX:+ExplicitGCInvokesConcurrent 设置 七小结 JDK/ 对于服务器应用我们使用Concurrent Low Pause Collector对年轻代暂停时多线程并行复制收集对年老代收集器与应用程序并行标记整理收集以达到尽量短的垃圾收集时间 本着没有深刻测试前不要胡乱优化的宗旨命令行属性只需简单写为 server Xms<heapsize>M Xmx<heapsize>M XX:+UseConcMarkSweepGC XX:+PrintGC Details XX:+PrintGCTimeStamps 然后要根据应用的情况在测试软件辅助可以下看看有没有JVM的默认值和自动管理做的不够的地方可以调整如xmn 设Young的大小XX:MaxPermSize设持久代大小等 JRockit R 但因为JDK的测试结果实在不能满意后来又尝试了JRockit总体效果要好些 JRockit的特点是动态垃圾收集器是根据用户关心的特征动态决定收集算法的参数如下 Xms<heapsize>M Xmx<heapsize>M Xgcprio:pausetime Xpausetarget=ms XgcReport XgcPause Xverbose:memory |