620 likes | 957 Views
Sun Hotspot 1.6 JVM GC ( Garbage Collector ) . http://bluedavy.com 2010-05-13 V0.2 2010-05-18 V 0.5. only 介绍 怎么实现的 如何 控制和使用. ppt 中未特别强调的 JVM 均指 Sun Hotspot 1.6.0. GC : Garbage Collector. 不是只负责内存回收. 还决定了内存分配. JVM 内存结构. - Xss. PC 寄存器. 本地方法栈. 局部变量区. - XX:PermSize. 操作数栈. JVM 方法区. 栈帧.
E N D
Sun Hotspot 1.6 JVM GC(Garbage Collector) http://bluedavy.com 2010-05-13 V0.2 2010-05-18 V0.5
only介绍 怎么实现的 如何控制和使用 ppt中未特别强调的JVM均指Sun Hotspot 1.6.0
GC:Garbage Collector 不是只负责内存回收 还决定了内存分配
JVM内存结构 -Xss PC寄存器 本地方法栈 局部变量区 -XX:PermSize 操作数栈 JVM方法区 栈帧 JVM堆 JVM方法栈 -Xms -Xmx
JVM堆:分代 -Xmn New Generation Eden S0 S1 Old Generation SurvivorRatio
JVM可用GC 新生代可用的GC 串行GC (Serial Copying) 并行回收GC (Parallel Scavenge) 并行GC (ParNew) 旧生代可用的GC 串行GC (Serial MSC) 并行GC (Parallel MSC) 并发GC (CMS) 我该用哪个呢?
JVM新生代可用GC—串行 找出活的对象,并基于Copying算法进行回收
JVM新生代可用GC—串行 何谓活的对象?(适用于所有GC方式) root set 全局/静态变量、正在执行的方法,还有一些特殊的(VM Handle)等 这些root set强引用的对象 至于Soft、Weak和Phantom在Full GC时特殊处理 Soft:内存不够时一定会被GC、长期不用也会被GC,可通过-XX:SoftRefLRUPolicyMSPerMB来设置; Weak:一定会被GC,当被mark为dead,会在ReferenceQueue中通知; Phantom:本来就没引用,当从jvm heap中释放,会通知。
JVM新生代可用GC—串行 由于只扫描新生代,此时如旧生代的对象引用了新生代的,怎么办?在给对象赋引用时,会产生一个write barrier;检查是否为旧生代引用新生代,如为则记录到remember set中;在minor gc时,remember set指向的新生代对象也作为root set。
JVM新生代可用GC—串行 如何暂停应用线程?对于每个线程执行的动作,都有一些Safepoint,当要进行GC时,则等待各线程进入Safepoint,进入后则将其需要才做的内存块设置为不可读,就此实现暂停应用线程。
JVM新生代可用GC—串行 Copying算法关于此PPT中提及的算法,除特殊的实现外,均可直接参考相关的介绍自动内存管理的算法的文章,或《垃圾回收》一书。
JVM新生代可用GC—串行 client模式下默认的GC方式,也可使用-XX:+UseSerialGC强制指定; SurvivorRatio是指eden:survivor space的比例,默认为8; 内存分配采用bump-the-pointer的方式。
JVM新生代可用GC—PS server模式下默认的GC方式,也可使用-XX:+UseParallelGC强制指定; 在不配置survivorRatio时,以InitialSurvivorRatio为准,此值默认为8, 代表的意思为young space:survivor space的比例; 也可通过survivorRatio强制指定,意思仍然为eden:survivor。 在采用PS时,默认情况下JVM会在运行时动态调整eden:s0:s1 如不希望动态调整,可设置-XX:-UseAdaptiveSizePolicy参数。 内存分配和回收算法和串行相同,唯一的不同仅在于回收时为多线程,PS针对大内存新生代做了很多的优化,在参数的使用上和串行以及ParNew有很多不同。
JVM新生代可用GC—ParNew CMS GC时默认采用,也可采用-XX:+UseParNewGC强制指定; SurvivorRatio代表的为eden:survivor,默认为8; 内存分配、回收和PS相同,不同的仅在于回收时会配合CMS做些处理。
JVM新生代GC—什么时候触发 又称minor GC。 分配内存时eden space不够用; 默认情况下Full GC会先触发minor GC; 在PS GC时可通过设置-XX:-ScavengeBeforeFullGC来禁止先触发minor GC。
JVM新生代GC—晋升机制 在经历了多次Minor GC后仍存活,则晋升到旧生代; 可通过-XX:MaxTenuringThreshold设置,在PS GC时无效。 Minor GC时To Space空间不够,就直接往Old放了; 因此要特别注意是不是这个原因,导致系统频繁Full GC。 大对象、或大的数组对象,直接就在Old分配了; 多大的对象会在Old分配可通过-XX:PretenureSizeThreshold=1024 来设置,单位为字节,默认为0,此参数在PS GC时无效,PS GC会自行计算来决定,什么时候分配到old。
JVM新生代GC—日志解读 • -verbose:gc -XX:+PrintGCDetails -Xloggc:gc.log • 串行GC版本 • [GC [DefNew: 11509K->1138K(14336K), 0.0110060 secs] 11509K->1138K(38912K), • 0.0112610 secs] [Times: user=0.00 sys=0.01, real=0.01 secs] • PS GC版本 • [GC [PSYoungGen: 11509K->1184K(14336K)] 11509K->1184K(38912K), 0.0113360 secs] • [Times: user=0.03 sys=0.01, real=0.01 secs] • ParNew GC版本 • [GC [ParNew: 11509K->1152K(14336K), 0.0129150 secs] 11509K->1152K(38912K), • 0.0131890 secs] [Times: user=0.05 sys=0.02, real=0.02 secs]
JVM新生代可用GC—总结 PS和ParNew方式性能更高,因此对于server应用而言为首选,PS针对大的新生代区做了一些优化,例如动态调整eden、s0、s1的大小等; PS和ParNew具体选择哪个,取决于旧生代用什么GC; 新生代采用的为Copying算法,性能通常取决于Minor GC时存活的对象有多少。
JVM新生代可用GC—Demo 一段这样的代码: public class Demo{ public static void main(String[] args) throws Exception{ byte[] bytes1=new byte[1024*1024*2]; byte[] bytes2=new byte[1024*1024*2]; byte[] bytes3=new byte[1024*1024*2]; bytes1=null; bytes2=null; byte[] bytes4=new byte[1024*1024*2]; Thread.sleep(5000); } } 执行参数: -server –XX:+UseSerialGC –Xmn10m –Xms20M –Xmx20M 请问在执行到byte[] bytes4这行代码后会发生什么? 如将GC换为PS GC、ParNew GC又会发生什么?
JVM可用GC 新生代可用的GC 串行GC (Serial Copying) 并行回收GC (Parallel Scavenge) 并行GC (ParNew) 旧生代可用的GC 串行GC (Serial MSC) 并行GC (Parallel MSC) 并发GC (CMS) 我该用哪个呢?
JVM旧生代可用GC—串行 基于Mark-Sweep-Compact算法实现; Mark为三色着色 compact为sliding compaction Stop-the-world client方式下默认GC方式,可通过-XX:+UseSerialGC强制指定。
JVM旧生代可用GC—并行 Stop-the-world; 优化点在于不是每次回收全部; server模式下默认GC方式,可通过-XX:+UseParallelGC强制指定; 多线程并行,性能较串行好; 并行的线程数 cpu core<=8 ? cpu core : 3+(cpu core*5)/8 或通过-XX:ParallelGCThreads=x来强制指定。
JVM旧生代可用GC—并发 基于Mark-Sweep实现; Initial Marking(Stop-the-world)Concurrent MarkingPreClean(Sun HotSpot 1.5后引入的优化步骤)Final Marking (Stop-the-world) Concurrent Sweeping 可通过-XX:+UseConcMarkSweepGC来强制指定,并发的线程数默认为:( 并行GC线程数+3)/4,也可通过ParallelCMSThreads指定;
JVM旧生代可用GC—并发 Initial Marking(Stop-the-world) mark下root set直接引用的对象 Concurrent Marking并发标识上面mark出来的对象的引用; Mod Union Table Minor GC同时进行,有可能会导致旧生代引用的对象关系改变 Card Table旧生代中的对象引用关系也有可能改变
JVM旧生代可用GC—并发 Preclean 重新扫描上一步过程中新创建的对象和引用关系改变了的对象的引用关系;此步什么时候执行,以及执行到什么时候再触发后续动作,取决于两个值:-XX: CMSScheduleRemarkEdenSizeThreshold、 -XX: CMSScheduleRemarkEdenPenetration 第一个值默认为2,第二个值默认为50%,代表着当eden space使用超过2M时,执行此步,当使用超过50%时,触发remark;上面这个步骤有些时候有可能会引发bug,有对象需要在old分配空间,但由于remark总是没执行,导致old空间不足,默认此步的超时时间为5秒,可通过-XX: CMSMaxAbortablePrecleanTime设置,单位为毫秒。
JVM旧生代可用GC—并发 Final Marking(Stop-the-world)处理Mod Union Table和Card Table中dirty的对象,重新mark。 Concurrent Sweeping并发回收。
JVM旧生代可用GC—并发 优缺点 大部分时候和应用并发进行,因此只会造成很短的暂停时间; 浮动垃圾,没办法,so内存空间要稍微大一点; 内存碎片,-XX:+UseCMSCompactAtFullCollection来解决; 争抢CPU,这GC方式就这样; 多次remark,所以总的gc时间会比并行的长; 内存分配,free list方式,so性能稍差,对minor GC会有一点影响; 和应用并发,有可能分配和回收同时,产生竞争,引入了锁,JVM分配优先。
JVM旧生代可用GC—并发 浮动垃圾 产生于card table
JVM旧生代可用GC—并发 Promotion Failed minor GC了,to space空间不够,往old跑,old也满了,so..解决方法:增大to space,增大old,或降低cmsgc触发时机 Concurrent mode failure old要分配内存了,但old空间不够,此时cmsgc正在进行,so..解决方法:增大old,降低cmsgc触发的old所占比率。 在这两种情况下,为了安全,JVM转为触发Full GC。
JVM旧生代GC—什么时候触发 旧生代不够用了; Perm不够用了; CMS GC时出现Promotion failed和Concurrent Mode failure;
JVM旧生代GC—什么时候触发 对于CMS GC而言,则基于CMSInitiatingOccupancyFraction来决定什么时候触发,默认值为68,即旧生代使用比率为68%时触发; Hotspot还有可能根据运行状况来决定什么时候触发CMS GC可通过设置UseCMSInitiatingOccupancyOnly=true来禁止
JVM旧生代GC—什么时候触发 [Full GC [PSYoungGen: 176K->0K(8960K)] [PSOldGen: 6144K->6258K(10240K)] 6320K->6258K(19200K) [PSPermGen: 1685K->1685K(16384K)], 0.0065500 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] Perm也没满 Old没满 那为什么Full GC?
JVM旧生代GC—什么时候触发 基于悲观原则的复杂触发策略 minor GC时,如old剩余空间小于之前多次minor gc晋升的对象的平均值,那么就直接执行full,psgc时稍有不同; 不同的地方在于psgc先执行minor gc,执行完毕后如发现old剩余空间比之前晋升的对象的平均值小,那么就直接触发一次对旧生代的收集;
JVM旧生代GC—日志解读 串行、并行方式和Minor GC的日志没太多差别[Full GC [Tenured: 9216K->4210K(10240K), 0.0066570 secs] 16584K->4210K(19456K), [Perm : 1692K->1692K(16384K)], 0.0067070 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]ps: 串行方式Full GC时有些时候日志上可能不会有Full GC字样... 并发的就复杂了[GC [1 CMS-initial-mark: 13433K(20480K)] 14465K(29696K), 0.0001830 secs] [Times: user=0.00 sys=0.00, real=0.00 secs][CMS-concurrent-mark: 0.004/0.004 secs] [Times: user=0.01 sys=0.00, real=0.01 secs] [CMS-concurrent-preclean: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] CMS: abort preclean due to time [CMS-concurrent-abortable-preclean: 0.007/5.042 secs] [Times: user=0.00 sys=0.00, real=5.04 secs] [GC[YG occupancy: 3300 K (9216 K)][Rescan (parallel) , 0.0002740 secs][weak refs processing, 0.0000090 secs] [1 CMS-remark: 13433K(20480K)] 16734K(29696K), 0.0003710 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [CMS-concurrent-sweep: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [CMS-concurrent-reset: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
JVM旧生代GC—总结 串行、并行都是Stop-the-world,要扫描整个heap,速度较慢,会造成应用较长时间的暂停;例如某应用,minor gc大概消耗15ms,但 full一次就需要500ms+,有些应用更是要消耗长达2—3s。(1.5G Heap、4核CPU、full后存活大概是300M) 并发是mostly concurrent,耗时较长,但对应用造成的暂停时间较短, 适合于对响应要求高的应用;
JVM旧生代GC—Demo 一个例子 public class Demo{ public static void main(String[] args) throws Exception{ byte[] bytes1=new byte[1024*1024*2]; byte[] bytes2=new byte[1024*1024*2]; byte[] bytes3=new byte[1024*1024*2]; byte[] bytes4=new byte[1024*1024*2]; bytes1=null; bytes2=null; bytes3=null; byte[] bytes5=new byte[1024*1024*2]; byte[] bytes6=new byte[1024*1024*2]; byte[] bytes7=new byte[1024*1024*2]; } } 执行参数: -server –Xmn10M –Xms20M –Xmx20M –XX:+UseSerialGC 请问以上代码执行会触发几次minor GC、几次Full GC; 请问切换GC方式后会是什么执行状况?
JVMGC—可见的未来 Garbage First 超出范围,以后再扯
GC频繁,怎么办? GC暂停时间长导致系统变慢,怎么办? 我们需要GC Tuning!
Case show first! 一个真实案例,收集的信息如下: 在系统运行到67919.837秒时发生了一次Full GC,日志信息如下: 67919.817: [GC [PSYoungGen: 588706K->70592K(616832K)] 1408209K->906379K(1472896K), 0.0197090 secs] [Times: user=0.06 sys=0.00, real=0.02 secs] 67919.837: [Full GC [PSYoungGen: 70592K->0K(616832K)] [PSOldGen: 835787K->375316K(856064K)] 906379K->375316K(1472896K) [PSPermGen: 64826K->64826K(98304K)], 0.5478600 secs] [Times: user=0.55 sys=0.00, real=0.55 secs]
Case show first! 一个真实案例,收集的信息如下: 之后Minor GC的信息
Case show first! 一个真实案例,收集的信息如下: 在68132.893时又发生了一次Full GC,日志信息如下: 68132.862: [GC [PSYoungGen: 594736K->63715K(609920K)] 1401225K->891090K(1465984K), 0.0309810 secs] [Times: user=0.06 sys=0.01, real=0.04 secs] 68132.893: [Full GC [PSYoungGen: 63715K->0K(609920K)] [PSOldGen: 827375K->368026K(856064K)] 891090K->368026K(1465984K) [PSPermGen: 64869K->64690K(98304K)], 0.5341070 secs] [Times: user=0.53 sys=0.00, real=0.53 secs] 之后的时间的GC基本也在重复上述过程。 对于这个case,我们该如何Tuning呢?
JVM GC Tuning—衡量现状 衡量工具-XX:+PrintGCDetails –XX:+PrintGCApplicationStoppedTime-Xloggc: {文件名} –XX:+PrintGCTimeStampsjmap(由于每个版本jvm的默认值可能会有改变,建议还是用jmap首先观察下目前每个代的内存大小、GC方式)jstat、visualvm、sar 、gclogviewer系统运行状况的监测工具 应收集到的信息minor gc多久执行一次,full gc多久执行一次,每次耗时多少?高峰期什么状况?minor gc时回收的效果如何,survivor的消耗状况如何,每次有多少对象会进入old?old区在full gc后会消耗多少(简单的memory leak判断方法)系统的load、cpu消耗、qps or tps、响应时间
JVM GC Tuning—设定目标 调优的目标是什么呢降低Full GC执行频率?降低Full GC消耗时间?降低Full GC所造成的应用暂停时间?降低Minor GC执行频率?降低Minor GC消耗时间?例如某系统的GC调优目标:降低Full GC执行频率的同时,尽可能降低minor GC的执行频率、消耗时间以及GC对应用造成的暂停时间。
JVM GC Tuning—尝试调优 根据目标针对性的寻找瓶颈以及制定调优策略来说说常见的降低Full GC执行频率根据前面学习到的Full GC触发时机,寻找到瓶颈为什么Full GC执行频率高呢,old经常满?还是old本来占用就高呢? old为什么经常满呢?请参见PPT前面的内容...是不是因为minor gc后经常有对象进入old呢?为什么?注意Java RMI的定时GC触发,可通过:-XX:+DisableExplicitGC来禁止;或通过 -Dsun.rmi.dgc.server.gcInterval=3600000来控制触发的时间。
JVM GC Tuning—尝试调优 降低Full GC执行频率 – 通常瓶颈 Old本身占用的就一直高,所以只要稍微放点对象到old,就full了;通常原因:缓存的东西太多 oracle 10g驱动时preparedstatement cache太大查找办法,很简单:dump then mat,bingo!