More Related Content Similar to Sun jdk 1.6 gc (20) More from bluedavy lin (6) Sun jdk 1.6 gc1. Sun JDK 1.6 GC(Garbage Collector) http://bluedavy.com 2010-05-13 V0.2 2010-05-19 V0.5 2010-06-01 V0.8 ppt中未特别强调的JVM均指Sun JDK 1.6.0 6. 内存结构 -Xss PC寄存器 本地方法栈 局部变量区 -XX:PermSize –XX:MaxPermSize 操作数栈 JVM方法区 栈帧 JVM堆 JVM方法栈 -Xms -Xmx 备注:在Hotspot中本地方法栈和JVM方法栈是同一个,因此也可用-Xss控制 8. 内存回收(Garbage Collection) GC要做的是将那些dead的对象所占用的内存回收掉; 1、Hotspot认为没有引用的对象是dead的 2、Hotspot将引用分为四种 Strong、Soft、Weak、Phantom Strong即默认通过Object o=new Object()这种方式赋值的引用; Soft、Weak、Phantom这三种则都是继承Reference; 在Full GC时会对Reference类型的引用进行特殊处理: Soft:内存不够时一定会被GC、长期不用也会被GC,可通过 -XX:SoftRefLRUPolicyMSPerMB来设置; Weak:一定会被GC,当被mark为dead,会在ReferenceQueue中通知; Phantom:本来就没引用,当从jvm heap中释放,会通知。 10. JVM堆:分代 -Xmn New Generation Eden S0 S1 Old Generation -XX:SurvivorRatio 备注:通常将对新生代进行的回收称为Minor GC;对旧生代进行的回收称为Major GC,但由于 Major GC除并发GC外均需对整个堆进行扫描和回收,因此又称为Full GC。 14. 新生代可用GC—串行 默认情况下,仅在TLAB或eden上分配,只有两种状况会在旧生代分配: 1、需要分配的大小超过eden space大小; 2、在配置了PretenureSizeThreshold的情况下,对象大小大于此值。 public class SerialGCDemo{ public static void main(String[] args) throws Exception{ byte[] bytes=new byte[1024*1024*2]; byte[] bytes2=new byte[1024*1024*2]; byte[] bytes3=new byte[1024*1024*2]; Thread.sleep(3000); byte[] bytes4=new byte[1024*1024*4]; Thread.sleep(3000); } } -Xms20M –Xmx20M –Xmn10M –XX:+UseSerialGC -Xms20M –Xmx20M –Xmn10M -XX:PretenureSizeThreshold=3145728 –XX:+UseSerialGC 15. 新生代可用GC—串行 当eden space空间不足时触发。 public class SerialGCDemo{ public static void main(String[] args) throws Exception{ byte[] bytes=new byte[1024*1024*2]; byte[] bytes2=new byte[1024*1024*2]; byte[] bytes3=new byte[1024*1024*2]; System.out.println(“step 1"); byte[] bytes4=new byte[1024*1024*2]; Thread.sleep(3000); System.out.println(“step 2"); byte[] bytes5=new byte[1024*1024*2]; byte[] bytes6=new byte[1024*1024*2]; System.out.println(“step 3"); byte[] bytes7=new byte[1024*1024*2]; Thread.sleep(3000); } } -Xms20M –Xmx20M –Xmn10M –XX:+UseSerialGC 17. 新生代可用GC—串行 public class SerialGCDemo{ public static void main(String[] args) throws Exception{ byte[] bytes=new byte[1024*1024*2]; byte[] bytes2=new byte[1024*1024*2]; byte[] bytes3=new byte[1024*1024*2]; System.out.println("step 1"); bytes=null; byte[] bytes4=new byte[1024*1024*2]; Thread.sleep(3000); System.out.println("step 2"); byte[] bytes5=new byte[1024*1024*2]; byte[] bytes6=new byte[1024*1024*2]; bytes4=null; bytes5=null; bytes6=null; System.out.println("step 3"); byte[] bytes7=new byte[1024*1024*2]; Thread.sleep(3000); } } -Xms20M –Xmx20M –Xmn10M –XX:+UseSerialGC -Xms20M –Xmx20M –Xmn10M -XX:-HandlePromotionFailure –XX:+UseSerialGC 20. 新生代可用GC—串行 public class SerialGCThreshold{ public static void main(String[] args) throws Exception{ SerialGCMemoryObject object1=new SerialGCMemoryObject(1); SerialGCMemoryObject object2=new SerialGCMemoryObject(8); SerialGCMemoryObject object3=new SerialGCMemoryObject(8); SerialGCMemoryObject object4=new SerialGCMemoryObject(8); object2=null; object3=null; SerialGCMemoryObject object5=new SerialGCMemoryObject(8); Thread.sleep(4000); object2=new SerialGCMemoryObject(8); object3=new SerialGCMemoryObject(8); object2=null; object3=null; object5=null; SerialGCMemoryObject object6=new SerialGCMemoryObject(8); Thread.sleep(5000); } } class SerialGCMemoryObject{ private byte[] bytes=null; public SerialGCMemoryObject(int multi){ bytes=new byte[1024*256*multi]; } } -Xms20M –Xmx20M –Xmn10M –XX:+UseSerialGC -Xms20M –Xmx20M –Xmn10M –XX:+UseSerialGC -XX:MaxTenuringThreshold=1 22. 新生代可用GC—串行 上面示例中object1在第二次minor gc时直接转入了old,在于Serial GC的 这个规则: 每次Minor GC后会重新计算TenuringThreshold (第一次以MaxTenuringThreshold为准) 计算的规则为: 累积每个age中的字节,当这个累计值 > To Space的一半时,对比此时的age和 MaxTenuringThreshold,取其中更小的值。 可通过PrintTenuringDistribution来查看下次minor gc时的TenuringThreshold 值:Desired survivor size 524288 bytes, new threshold 1 (max 15),其中 的new threshold 1即为新的TenuringThreshold的值。 例如在上面的例子中: 当第一次Minor GC结束时,遍历age table,当累积age 1的字节后,发现此时所 占用的字节数 > To Space的一半,因此将TenuringThreshold赋值为1,下次 Minor GC时即把age超过1的对象全部转入old。 23. JVM新生代可用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] 25. 新生代可用GC—ParNew [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] 如启动参数上设置了-XX:+UseAdaptiveSizePolicy,则会输出 [GC [ASParNew: 7495K->120K(9216K), 0.0403410 secs] 7495K->7294K(19456K), 0.0406480 secs] [Times: user=0.06 sys=0.15, real=0.04 secs] 27. 新生代可用GC—PS 大多数情况下,会在TLAB或eden上分配。 如下一段代码: public class PSGCDemo{ public static void main(String[] args) throws Exception{ byte[] bytes=new byte[1024*1024*2]; byte[] bytes2=new byte[1024*1024*2]; byte[] bytes3=new byte[1024*1024*2]; Thread.sleep(3000); byte[] bytes4=new byte[1024*1024*4]; Thread.sleep(3000); } } -Xms20M –Xmx20M –Xmn10M –XX:SurvivorRatio=8 –XX:+UseParallelGC 29. 新生代可用GC—PS eden space分配不下,且需要分配的对象大小未超过eden space的一半或old区分配失败, 触发回收; public class PSGCDemo{ public static void main(String[] args) throws Exception{ byte[] bytes=new byte[1024*1024*2]; byte[] bytes2=new byte[1024*1024*2]; byte[] bytes3=new byte[1024*1024*2]; System.out.println(“step 1"); byte[] bytes4=new byte[1024*1024*2]; Thread.sleep(3000); System.out.println(“step 2"); byte[] bytes5=new byte[1024*1024*2]; byte[] bytes6=new byte[1024*1024*2]; System.out.println(“step 3"); byte[] bytes7=new byte[1024*1024*2]; Thread.sleep(3000); } } -Xms20M –Xmx20M –Xmn10M –XX:SurvivorRatio=8 –XX:+UseParallelGC -XX:+PrintGCDetails –XX:verbose:gc 31. 新生代可用GC—PS 新生代对象晋升到旧生代的规则 1、经历多次minor gc仍存活的对象,可通过以下参数来控制: AlwaysTenure,默认false,表示只要minor GC时存活,就晋升到旧生代; NeverTenure,默认false,表示永不晋升到旧生代; 上面两个都没设置的情况下,如UseAdaptiveSizePolicy,启动时以 InitialTenuringThreshold值作为存活次数的阈值,在每次psgc后会动态调整如不使用UseAdaptiveSizePolicy,则以MaxTenuringThreshold为准。 2、to space放不下的,直接放入旧生代; 33. 新生代可用GC—PS [GC [PSYoungGen: 11509K->1184K(14336K)] 11509K->1184K(38912K), 0.0113360 secs] [Times: user=0.03 sys=0.01, real=0.01 secs] 34. 旧生代可用的GC 串行GC (Serial MSC) 并行 MS GC (Parallel MSC) 并发GC (CMS) 并行 Compacting GC (Parallel Compacting) 我该用哪个呢? 36. 旧生代可用GC—串行 触发机制 1、old gen空间不足; 2、perm gen空间不足; 3、minor gc时的悲观策略; 4、minor GC后在eden上分配内存仍然失败; 5、执行heap dump时; 6、外部调用System.gc,可通过-XX:+DisableExplicitGC来禁止。 ps: 如CollectGen0First为true(默认为false),则先执行minor GC; 37. 旧生代可用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] 40. 旧生代可用GC—并行MSC [Full GC [PSYoungGen: 1208K->0K(8960K)] [PSOldGen: 6144K->7282K(10240K)] 7352K->7282K(19200K) [PSPermGen: 1686K->1686K(16384K)], 0.0165880 secs] [Times: user=0.01 sys=0.01, real=0.02 secs] 43. 旧生代可用GC—并行Compacting [Full GC [PSYoungGen: 1224K->0K(8960K)] [ParOldGen: 6144K->7282K(10240K)] 7368K->7282K(19200K) [PSPermGen: 1686K->1685K(16384K)], 0.0223510 secs] [Times: user=0.02 sys=0.06, real=0.03 secs] 45. 旧生代可用GC—并发 触发机制 1、当旧生代空间使用到一定比率时触发; JDK V 1.6中默认为92%,可通过PrintCMSInitiationStatistics(此参数在V 1.5中不能用)来查看这个值到底是多少; 可通过CMSInitiatingOccupancyFraction来强制指定,默认值并不是赋值在了这个值上,是根据如下公式计算出来的: ((100 - MinHeapFreeRatio) +(double)(CMSTriggerRatio * MinHeapFreeRatio) / 100.0)/ 100.0; MinHeapFreeRatio默认值: 40 CMSTriggerRatio默认值: 80 2、当perm gen采用CMS收集且空间使用到一定比率时触发; perm gen采用CMS收集需设置:-XX:+CMSClassUnloadingEnabled JDKV 1.6中默认为92%; 可通过CMSInitiatingPermOccupancyFraction来强制指定,同样,它是根据如下公式计算出来的: ((100 - MinHeapFreeRatio) +(double)(CMSTriggerPermRatio* MinHeapFreeRatio) / 100.0)/ 100.0; MinHeapFreeRatio默认值: 40 CMSTriggerPermRatio默认值: 80 47. 旧生代可用GC—并发 public class CMSGCOccur{ public static void main(String[] args) throws Exception{ byte[] bytes=new byte[1024*1024*2]; byte[] bytes1=new byte[1024*1024*2]; byte[] bytes2=new byte[1024*1024*2]; byte[] bytes3=new byte[1024*1024*1]; byte[] bytes4=new byte[1024*1024*2]; Thread.sleep(5000); } } -Xms20M –Xmx20M –Xmn10M -XX:+UseConcMarkSweepGC -XX:+UseCMSInitiatingOccupancyOnly -XX:+PrintGCDetails -Xms20M –Xmx20M –Xmn10M -XX:+UseConcMarkSweepGC -XX:+PrintGCDetails 48. 旧生代可用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。 49. 旧生代可用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] 当在启动参数上设置了-XX:+UseAdaptiveSizePolicy后,上面的日志中的CMS会变为ASCMS CMS GC Log解读 55. OOM(一些代码造成OOM的例子) Java Heap OOM产生的原因是在多次gc后仍然分配不了,具体策略取决于这三个 参数: -XX:+UseGCOverheadLimit -XX:GCTimeLimit=98 –XX:GCHeapFreeLimit=2 1、OOM的前兆通常体现在每次Full GC后旧生代的消耗呈不断上涨趋势; 查看方法:jstat –gcutil [pid] [intervel] [count] 2、解决方法 dump多次Full GC后的内存消耗状况,方法: jmap –dump:format=b,file=[filename] [pid] dump下来的文件可用MAT进行分析,简单视图分析:MAT Top Consumers 或在启动参数上增加:-XX:+HeapDumpOnOutOfMemoryError,当OOM时会在工作路径(或通过-XX:HeapDumpPath来指定路径)下生成java_[pid].hprof文件。 还有Native Heap造成的OOM,堆外内存使用过多。 56. GC监测 1、jstat –gcutil [pid] [intervel] [count] 2、-verbose:gc // 可以辅助输出一些详细的GC信息 -XX:+PrintGCDetails // 输出GC详细信息 -XX:+PrintGCApplicationStoppedTime // 输出GC造成应用暂停的时间 -XX:+PrintGCDateStamps // GC发生的时间信息 -XX:+PrintHeapAtGC // 在GC前后输出堆中各个区域的大小 -Xloggc:[file] // 将GC信息输出到单独的文件中 gc的日志拿下来后可使用GCLogViewer或gchisto进行分析。 3、图形化的情况下可直接用jvisualvm进行分析。 59. case 1 – 场景 4 cpu,os: linux 2.6.18 32 bit 启动参数 -Xms1536M –Xmx1536M –Xmn500M 系统响应速度大概为100ms; 当系统QPS增长到40时,机器每隔5秒就执行一次minor gc,每隔3分钟就执行一次full gc,并且很快就一直full了; 每次Full gc后旧生代大概会消耗400M,有点多了。 60. case 1 – 目标和方法 减少Full GC次数,以避免由于GC造成频繁的长暂停,从而导致难以支撑高并发量。 方法 降低响应时间或请求次数,这个需要重构,比较麻烦; 减少旧生代内存的消耗,比较靠谱; 减少每次请求的内存的消耗,貌似比较靠谱; 降低GC造成的应用暂停的时间。 61. case 1 – tuning 减少旧生代内存的消耗 jmap dump; 发现除了一些确实需要在旧生代中消耗的内存外,还有点诡异现象; 可以肯定的是这里面的线程大部分是没有在处理任务的; 于是根据MAT查找到底谁消耗掉了这些内存; 发现是由于有一个地方使用到了ThreadLocal,但在使用完毕后没有去将ThreadLocal.set(null)。 62. case 1 – tuning 在做完上面的tuning后,旧生代的内存使用下降了大概200M,效果是full gc的频率稍微拖长了一点,但仍然不理想,于是旧生代部分无法继续优化了; 想减少每次请求所分配的内存,碰到的巨大问题: 怎么才知道呢?貌似没办法 想了一些方法,放弃了。 63. case 1 – tuning 降低GC所造成的长暂停 采用CMS GC QPS只能提升到50… 于是放弃,而且还有和jmap –heap的冲突。 64. case 1 – tuning 终极必杀技 降低系统响应时间 QPS终于能支撑到90… 65. case 2 – 场景 4 cpu,os: linux 2.6.18 32 bit 启动参数 -server -Xms1536m -Xmx1536m –Xmn700m 在系统运行到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] 67. case 2 – 场景 在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基本也在重复上述过程。 68. case 2 – 目标和方法 目标 降低full gc的频率,以及gc所造成的应用暂停; 如果在达到上面目标的情况下,还能降低minor gc的频率以及所造成的应用暂停时间就好了。 方法 降低响应时间或请求次数,比较麻烦; 减少每次请求所需分配的内存,貌似比较麻烦; 减少每次minor gc晋升到old的对象,比较靠谱。 69. case 2 – tuning 减少每次minor gc晋升到old的对象 调大new,在当前的参数下不好操作; 调大Survivor,根据目前的状况,是可选的方法; 调大TenuringThreshold,根据目前的状况,这不是关键点。 70. case 2 – tuning 调大Survivor 当前为PS GC方式,Survivor space会被动态调整,有些时候会调整的很小,所以导致了经常有对象直接跳到了old; 于是不让动态调整了,-XX:-UseAdaptiveSizePolicy 计算Survivor Space需要的大小,简单的计算了下 看目前的to space的大小,然后minor gc后晋升到old的,old+to space的大小作为需要的大小; 统计多次后做平均; 于是调整… 继续观察,并做微调,保证高峰期以及一定的冗余。 做过这个调整后,效果很明显,minor gc更频繁了些,但full的频率推迟到了至少两小时一次。 71. case 2 – tuning 上面的tuning达到了降低full gc的目标,但整体GC所造成的响应时间下降的仍然不够多,大概只下降了10%; 于是保持Survivorspace,同时将GC方式切换为CMS GC。 -Xms1536m -Xmx1536m -Xmn700m -XX:SurvivorRatio=7 -XX:+UseConcMarkSweepGC -XX:+UseCMSCompactAtFullCollection -XX:CMSMaxAbortablePrecleanTime=1000 -XX:+CMSClassUnloadingEnabled -XX:+UseCMSInitiatingOccupancyOnly -XX:+DisableExplicitGC 73. GC Tuning—衡量现状 衡量工具-XX:+PrintGCDetails –XX:+PrintGCApplicationStoppedTime-Xloggc: {文件名} –XX:+PrintGCTimeStampsjmap(由于每个版本jvm的默认值可能会有改变,建议还是用jmap首先观察下目前每个代的内存大小、GC方式)jstat、jvisualvm、sar 、gclogviewer系统运行状况的监测工具 应收集到的信息minor gc多久执行一次,full gc多久执行一次,每次耗时多少?高峰期什么状况?minor gc时回收的效果如何,survivor的消耗状况如何,每次有多少对象会进入old?old区在full gc后会消耗多少(简单的memory leak判断方法)系统的load、cpu消耗、qps or tps、响应时间 74. GC Tuning—设定目标 调优的目标是什么呢降低Full GC执行频率?降低Full GC消耗时间?降低Full GC所造成的应用暂停时间?降低Minor GC执行频率?降低Minor GC消耗时间?例如某系统的GC调优目标:降低Full GC执行频率的同时,尽可能降低minor GC的执行频率、消耗时间以及GC对应用造成的暂停时间。 75. 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来控制触发的时间。 76. GC Tuning—尝试调优 降低Full GC执行频率 – 通常瓶颈 Old本身占用的就一直高,所以只要稍微放点对象到old,就full了;通常原因:缓存的东西太多 oracle 10g驱动时preparedstatement cache太大查找办法,很简单:dump then mat,bingo! 77. GC Tuning—尝试调优 降低Full GC执行频率 – 通常瓶颈 Minor GC后总是有对象不断的进入Old,导致Old不断的满通常原因:Survivor太小了系统响应太慢、请求量太大、每次请求分配内存多分配的对象太大...查找办法:dump这时就不是很好用了,需要的是能分析两次 minor GC之间到底哪些地方分配了内存;jstat观察Survivor的消耗状况,-XX:PrintHeapAtGC输出GC前后的详细信息;系统响应慢那行属于系统优化,不在这里扯; 78. GC Tuning—尝试调优 降低Full GC执行频率 – 调优策略Old本身占用的就一直高调优办法① 扩大old大小(减少new或调大heap); 减少new注意对minor gc的影响并且同时有可能造成fullgc还是严重;调大heap注意full gc的时间的延长,cpu够强悍嘛,os 32 bit的吗? ② 程序优化(去掉一些不必要的缓存) 79. GC Tuning—尝试调优 降低Full GC执行频率 – 调优策略Minor GC后总是有对象不断的进入Old 前提:这些进入Old的对象在full时大部分都会被回收调优办法① 降低Minor GC执行频率(等到那部分再讲)② 让对象尽量在Minor GC中就被回收掉放大new、放大survivor、增大TenuringThreshold但要注意这些有可能会造成minor gc执行频繁③ 换CMS GC Old还没满就回收掉,从而降低Full GC触发的可能④ 程序优化提升响应速度、降低每次请求分配的内存 80. GC Tuning—尝试调优 降低单次Full GC执行时间 通常原因:旧生代太大了...通常办法:是并行GC吗?加点CPU吧升级CPU吧减小Heap或旧生代吧 81. GC Tuning—尝试调优 降低Minor GC执行频率 通常原因:每次请求分配的内存多、请求量大通常办法:扩大heap、扩大新生代、扩大eden,扩大时请综合考虑;降低每次请求分配的内存;加机器吧,分担点请求量。 82. GC Tuning—尝试调优 降低Minor GC执行时间 通常原因:新生代太大了响应速度太慢了,导致每次Minor GC时存活的对象多通常办法:减小点新生代吧;加点CPU吧、升级CPU吧;响应能不能快点呢? 83. GC Tuning—算命 ① 当响应速度下降到多少、或请 求量上涨到多少时,系统会 挂掉? ② 参数调整后系统多久会执行一次Minor,多久会执行一次Full,高峰期会如何? 84. GC Tuning—不是瞎算的 ① 系统的生辰八字 每次请求平均需要分配多少内存?系统的平均响应时间是多少呢?请求量是多少、多久一次Minor、Full? ② 先掐指算下在现在的参数下,应该是多久一次 Minor、Full,对比真实状况,做一定的偏差; ③ 根据所掌握的知识,就可以判断了 88. 内存回收 1、通常有两种实现方法:1.1 引用计数 不适合复杂对象引用关系,尤其是有循环依赖的场景; 需要计数; 优点是只要计数器降为0,就可被回收。 1.2 跟踪(有向图Tracing) 适合于复杂对象引用关系场景,Hotspot采用这种; 需要到达某个时机后触发执行,并且通常需要暂停应用线程。 常用算法:Copying、Mark-Sweep、Mark-Compact,算法请参见《垃圾回收》这本绝版书。 90. 内存回收 如何暂停应用线程?safepoint first: 检测某内存页是否可读的指令 先想想:只有会改变引用关系的地方才需要暂停,否则没必要,因此对于正在native中执行的线程,JVM是不管的,而当native代码需要改变引用关系时,又回到了hotspot java部分的代码,而在那些代码上是会有safepoint的。 代码编译时会在某些部分生成safepoint,当需要GC时,GC会向core vm提交暂停应用线程的请求,core vm会将safepoint检测的内存页置为不可读状态,当解释执行或JIT编译执行到safepoint时,发现内存页不可读,于是就挂起了。 95. 新生代可用GC—串行 完整内存分配策略 1、首先在tlab上尝试分配; 2、检查是否需要在new上分配,如需要分配的大小小于PretenureSizeThreshold,则在eden上进行分配,分配成功则返回,分配失败则继续; 3、检查是否需要尝试在旧生代上分配,如需要,则遍历所有代,并检查是否可在该代上分配,如可以则进行分配;如不需要在旧生代上尝试分配,那么则检查是否可以在eden上分配,如可以则分配,否则则尝试在old上分配; 4、根据策略决定执行新生代GC或Full GC,执行full gc时不清除soft Ref; 5、如需要分配的大小大于PretenureSizeThreshold,尝试在old上分配,否则尝试在eden上分配; 6、尝试扩大堆并分配; 7、执行full gc,并清除所有soft Ref,按步骤5继续尝试分配。 96. 新生代可用GC—串行 完整内存回收策略 1、检查to是否为空,不为空返回false; 2、检查old剩余空间是否大于当前eden+from已用的大小,如大于则返回true,如小于且HandlePromotionFailure为true,则检查剩余空间是否大于之前每次minor gc晋级到old的平均大小,如大于返回true,如小于返回false。 3、如上面的结果为false,则执行full gc,如上面的结果为true,执行下面的步骤; 4、扫描引用关系,将活的对象copy到to space,如对象在minor gc中的存活次数超过tenuring_threshold或分配失败,则往旧生代复制,如仍然复制失败,则取决于HandlePromotionFailure,如不需要处理,直接抛出OOM,并退出vm,如需处理,则保持这些新生代对象不动; 99. 新生代可用GC—PS 完整内存分配策略 1、先在TLAB上分配,分配失败则直接在eden上分配; 2、当eden上分配失败时,检查需要分配的大小是否 >= eden space 的一半,如是,则直接在旧生代分配; 3、如分配仍然失败,且gc已超过频率,则抛出OOM; 4、进入基本分配策略失败的模式; 5、执行PS GC,在eden上分配; 6、执行非最大压缩的full gc,在eden上分配; 7、在旧生代上分配; 8、执行最大压缩full gc,在eden上分配; 9、在旧生代上分配; 10、如还失败,回到2。 最悲惨的情况,分配触发多次PS GC和多次Full GC,直到OOM。 100. 新生代可用GC—PS 完整内存回收策略 1、如gc所执行的时间超过,直接结束; 2、先调用invoke_nopolicy 2.1 先检查是不是要尝试scavenge; 2.1.1 to space必须为空,如不为空,则返回false; 2.1.2 获取之前所有minor gc晋级到old的平均大小,并对比目前eden+from已使用的大小,取更小的一个值,如old剩余空间 小于此值,则返回false,如大于则返回true; 2.2 如不需要尝试scavenge,则返回false,否则继续; 2.3 多线程扫描活的对象,并基于copying算法回收,回收时相应的晋升对象到旧生代; 2.4 如UseAdaptiveSizePolicy,那么重新计算to space和tenuringThreshold的值,并调整。 3、如invoke_nopolicy返回的是false,或之前所有minor gc晋级到old的平均大小 > 旧生代的剩余空间,那么继续下面的步骤,否则结束; 4、如UseParallelOldGC,则执行PSParallelCompact,如不是UseParallelOldGC,则执行PSMarkSweep。 101. 旧生代可用的GC 串行GC (Serial MSC) 并行 MSC GC (Parallel MSC) 并发GC (CMS) 并行 Compacting GC (Parallel Compacting) 103. 旧生代可用GC—串行 内存回收策略 1、基于Mark Sweep Compact实现; 2、如CollectGen0First为true(默认为false),则先执行minor GC; 回收过程分为四个阶段完成: 1、标记哪些对象是活的; 2、计算新的地址; 3、更新指针指向新的地址; 4、将对象移到新的地址。 111. 旧生代可用GC—并发 Preclean 重新扫描上一步过程中新创建的对象和引用关系改变了的对象的引用关系;此步什么时候执行,以及执行到什么时候再触发后续动作,取决于两个值:-XX: CMSScheduleRemarkEdenSizeThreshold、 -XX: CMSScheduleRemarkEdenPenetration 第一个值默认为2,第二个值默认为50%,代表着当eden space使用超过2M时,执行此步,当使用超过50%时,触发remark;上面这个步骤有些时候有可能会引发bug,有对象需要在old分配空间,但由于remark总是没执行,导致old空间不足,默认此步的超时时间为5秒,可通过-XX: CMSMaxAbortablePrecleanTime设置,单位为毫秒。 112. 旧生代可用GC—并发 Final Marking(Stop-the-world)处理Mod Union Table和Card Table中dirty的对象,重新mark。 Concurrent Sweeping并发回收。 113. 旧生代可用GC—并发 优缺点 大部分时候和应用并发进行,因此只会造成很短的暂停时间; 浮动垃圾,没办法,so内存空间要稍微大一点; 内存碎片,-XX:+UseCMSCompactAtFullCollection来解决; 争抢CPU,这GC方式就这样; 多次remark,所以总的gc时间会比并行的长; 内存分配,free list方式,so性能稍差,对minor GC会有一点影响; 和应用并发,有可能分配和回收同时,产生竞争,引入了锁,JVM分配优先。 115. References 1、GC Tuning in the Hotspot 2、Our Collectors 3、CMS GC 4、why now 5、JDK 6.0 gc tuning 6、memory management in hotspot whitepaper 7、JVM内存管理和垃圾回收