1.15k likes | 1.36k Views
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. Java :自动内存管理 为什么还 需要学习 GC ?. OOM? GC 成为支撑更高并发量的瓶颈 ?. only 介绍 使用 通常问题查找 Tuning 实现. GC : Garbage Collector. 不是只负责内存回收. 还决定了内存分配. 使用.
E N D
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
Java:自动内存管理 为什么还需要学习GC? OOM? GC成为支撑更高并发量的瓶颈?
only介绍 使用 通常问题查找 Tuning 实现
GC:Garbage Collector 不是只负责内存回收 还决定了内存分配
使用 Hotspot是如何分配内存的 Hotspot什么时候回收内存
内存结构 -Xss PC寄存器 本地方法栈 局部变量区 -XX:PermSize –XX:MaxPermSize 操作数栈 JVM方法区 栈帧 JVM堆 JVM方法栈 -Xms -Xmx 备注:在Hotspot中本地方法栈和JVM方法栈是同一个,因此也可用-Xss控制
内存分配 1、堆上分配 大多数情况在eden上分配,偶尔会直接在old上分配细节取决于GC的实现这里最重要的优化是TLAB 2、栈上分配 原子类型的局部变量或基于EA后标量替换转变为原子类型的局部变量 3、堆外分配 DirectByteBuffer 或直接使用Unsafe.allocateMemory,但不推荐这种方式
内存回收(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中释放,会通知。
内存回收 经IBM研究,通常运行的程序有98%的对象是临时对象,因此 Sun Hotspot对JVM堆采用了分代的方式来管理,以提升GC的 效率。
JVM堆:分代 -Xmn New Generation Eden S0 S1 Old Generation -XX:SurvivorRatio 备注:通常将对新生代进行的回收称为Minor GC;对旧生代进行的回收称为Major GC,但由于 Major GC除并发GC外均需对整个堆进行扫描和回收,因此又称为Full GC。
新生代可用GC 串行GC (Serial Copying) 并行GC (ParNew) 并行回收GC (Parallel Scavenge) 我该用哪个呢?
新生代可用GC—串行 1. client模式下默认GC方式,也可通过-XX:+UseSerialGC来强制指定; 2. eden、s0、s1的大小通过-XX:SurvivorRatio来控制,默认为8,含义 为eden:s0的比例,启动后可通过jmap –heap [pid]查看。
新生代可用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
新生代可用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
新生代可用GC—串行 上面示例之所以会是触发一次minor和一次full,在于Serial GC的这个规则: 在回收前Serial GC会先检测之前每次Minor GC时晋升到旧生代的平均大小是否大 于旧生代的剩余空间,如大于,则直接触发full,如小于,则取决于 HandlePromotionFailure的设置。
新生代可用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
新生代可用GC—串行 上面示例在两个参数时执行效果之所以不同,在于Serial GC的这个规则: 触发Minor GC时: 之前Minor GC晋级到old的平均大小 < 旧生代剩余空间 < eden+from使用空间 当HandlePromotionFailure为true,则仅触发minor gc,如为false,则触发full。
新生代可用GC—串行 新生代对象晋升到旧生代的规则 1、经历多次minor gc仍存活的对象,可通过以下参数来控制: 以MaxTenuringThreshold值为准,默认为15。 2、to space放不下的,直接放入旧生代;
新生代可用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
新生代可用GC—串行 把上面代码中的object1修改为如下: SerialGCMemoryObject object1=new SerialGCMemoryObject(2); -Xms20M –Xmx20M –Xmn10M –XX:+UseSerialGC
新生代可用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。
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]
新生代可用GC—ParNew CMS GC时默认采用,也可采用-XX:+UseParNewGC强制指定; eden、s0、s1的大小通过-XX:SurvivorRatio来控制,默认为8,含义 为eden:s0的比例。 默认情况下其内存分配和回收和Serial完全相同,只是回收的时候为多线程 而已,但一旦开启-XX:+UseAdaptiveSizePolicy则有些不同。
新生代可用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]
新生代可用GC—PS server模式时默认的GC方式,也可采用-XX:+UseParallelGC强制指定; eden、s0、s1的大小可通过-XX:SurvivorRatio来控制,但默认情况下以-XX:InitialSurivivorRatio为准,此值默认为8,代表的为新生代大小 : s0,这点要特别注意。
新生代可用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
新生代可用GC—PS 上面示例中的bytes4之所以会直接在旧生代分配,在于PS GC的这个规则: 当TLAB、eden上分配都失败时,判断需要分配的内存大小是否 >= eden space的一半大小,如是就直接在旧生代分配。
新生代可用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
新生代可用GC—PS 上面示例之所以会是触发一次minor和两次full,在于PS GC的这个规则: 1、在回收前PS GC会先检测之前每次PS GC时晋升到旧生代的平均大小是否大于 旧生代的剩余空间,如大于,则直接触发full; 2、在回收后,也会按上面规则进行检测。
新生代可用GC—PS 新生代对象晋升到旧生代的规则 1、经历多次minor gc仍存活的对象,可通过以下参数来控制: AlwaysTenure,默认false,表示只要minor GC时存活,就晋升到旧生代; NeverTenure,默认false,表示永不晋升到旧生代; 上面两个都没设置的情况下,如UseAdaptiveSizePolicy,启动时以 InitialTenuringThreshold值作为存活次数的阈值,在每次psgc后会动态调整如不使用UseAdaptiveSizePolicy,则以MaxTenuringThreshold为准。 2、to space放不下的,直接放入旧生代;
新生代可用GC—PS 在回收后,如UseAdaptiveSizePolicy,PS GC会根据运行状况动态调整eden、to 以及TenuringThreshold的大小。 不希望动态调整可设置-XX:-UseAdaptiveSizePolicy。 如希望跟踪每次的变化情况,可在启动参数上增加:PrintAdaptiveSizePolicy
新生代可用GC—PS • [GC [PSYoungGen: 11509K->1184K(14336K)] 11509K->1184K(38912K), 0.0113360 secs] • [Times: user=0.03 sys=0.01, real=0.01 secs]
旧生代可用的GC 串行GC (Serial MSC) 并行 MS GC (Parallel MSC) 并行 Compacting GC (Parallel Compacting) 并发GC (CMS) 我该用哪个呢?
旧生代可用GC—串行 client方式下默认GC方式,可通过-XX:+UseSerialGC强制指定。
旧生代可用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;
旧生代可用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]
旧生代可用GC—并行MSC server模式下默认GC方式,可通过-XX:+UseParallelGC强制指定; 并行的线程数 cpu core<=8 ? cpu core : 3+(cpu core*5)/8 或通过-XX:ParallelGCThreads=x来强制指定。
旧生代可用GC—并行MSC 触发机制和串行完全相同 ps: 如ScavengeBeforeFullGC为true(默认值),则先执行minor GC;
旧生代可用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]
旧生代可用GC—并行Compacting 可通过-XX:+UseParallelOldGC强制指定; 并行的线程数 cpu core<=8 ? cpu core : 3+(cpu core*5)/8 或通过-XX:ParallelGCThreads=x来强制指定。
旧生代可用GC—并行Compacting 触发机制和并行MSC完全相同
旧生代可用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]
旧生代可用GC—并发 可通过-XX:+UseConcMarkSweepGC来强制指定,并发的线程数 默认为:( 并行GC线程数+3)/4,也可通过ParallelCMSThreads指定;
旧生代可用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
旧生代可用GC—并发 触发机制 3、Hotspot根据成本计算决定是否需要执行CMS GC; 可通过-XX:+UseCMSInitiatingOccupancyOnly来去掉这个动态执行的策略。 4、外部调用了System.gc,且设置了ExplicitGCInvokesConcurrent; 需要注意,在JDK 6中,在这种情况下如应用同时使用了NIO,可能会出现bug。
旧生代可用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
旧生代可用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。
旧生代可用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解读