目的:熟悉Visual GC动态展现GC的效果,而且简单熟悉年轻代与老年代的内存大小分配对彼此产生的影响。java
环境:JDK八、IDEA2017(包含VisualVM插件)、windows 64位4核系统windows
测试代码:(用来不停地建立对象,而且一直运行)工具
public class GcTest { public static void main(String[] args) throws InterruptedException { while(true){ GcTestMethod g = new GcTestMethod(); g.testAllocation(); } } } class GcTestMethod{ public void testAllocation() {} }
首先,设定初始堆大小、最大堆大小都为50M,年轻代采用默认分配方式(观察来看默认为整个堆内存大小的1/3)测试
VM Option:-Xms50M -Xmx50Mspa
经过上面的参数运行一段时间后,咱们来观察Java VisualVM 中 Visual GC的动态走势以下,能够看到仍是很规律的。插件
经过jstat观察JVM内存的分配状况,咱们能够看到年轻代分配16896kb(16.5m),老年代分配34304kb(33.5m),年轻代是按照默认1/3堆大小来分的3d
运行一段时间后,咱们经过jstat观察程序的GC状况:日志
年轻代GC 10727次,总耗时18.554s Full GC 7次 总耗时0.443scode
年轻代GC平均耗时 0.001729654s Full GC平均耗时0.063285714s对象
而后,咱们在原来的运行参数基础上,将年轻代大小固定为10m,整个堆内存大小不变,让咱们来看一下减少年轻代大小后对整个GC的动态走势有什么影响。
VM Option:-Xms50M -Xmx50M -Xmn10M
经过上面的参数运行一段时间后,咱们来观察Java VisualVM 中 Visual GC的动态走势以下,能够看到和一开始同样,仍是很规律,区别可能就是GC的频率和效率了。
经过jstat观察JVM内存的分配状况,咱们能够看到年轻代分配10240kb(10m),老年代分配40960kb(40m),是按照咱们设置的参数来运行的。
运行一段时间后,咱们经过jstat观察程序的GC状况:
年轻代GC 14927次,总耗时22.951s Full GC 8次 总耗时0.559s
年轻代GC平均耗时 0.001537549s Full GC平均耗时0.069875s
和前面的例子相比能够看到,当咱们缩小了年轻代的空间后,年轻代GC次数明显上升,也就验证了年轻代太小所引起的年轻代GC频繁的问题。
刚才咱们经过固定年轻代的大小为10m来间接缩小年轻代大小,经过观察咱们发现总体上并无影响GC的走势(可能参数不够小不明显),可是GC的频率确定比以前要大了。
此次咱们调整整个堆内存为20m,年轻代大小固定10m。经过这种方式缩减老年代的大小,再来看看GC的走势如何。
VM Option:-Xms20M -Xmx20M -Xmn10M
经过运行一段时间咱们再来观察Java VisualVM 中 Visual GC的GC走势,发现老年代的空间很快就被填满了,而且内存始终释放不出太多,年老代始终处于饱和状态。
前面咱们年老代的内存为40m,如今为10m,经过和上面的GC走势图对比,咱们能够猜测,是否是因为年老代的缩小,致使程序中一直长期存在的对象在年老代中一直没法释放,而且及时Full GC 也不能释放太多,而且很快又被填满。这样下去的结果将是一直的Full GC,年老代一直释放不出足够的空间。
经过jstat观察JVM内存的分配状况,咱们能够看到年轻代分配10240kb(10m),老年代分配10240kb(10m),是按照咱们设置的参数来运行的。
运行一段时间后,咱们经过jstat观察程序的GC状况:
年轻代GC 1967次,总耗时11.595s Full GC 740次 总耗时44.944s
年轻代GC平均耗时 0.005894764s Full GC平均耗时0.060735135s
这个和前例比较能够明显看出,当咱们缩小了老年代的空间后(由50m缩减至10m),Full GC的频次爆炸性的上升了,缘由也很明显,老年代的空间不足始终处于饱和状态,所以频繁的Full Gc。
经过上面的例子,咱们看到了年老代的空间太小致使的诟病,年老代始终被填满,一直进行Full GC。在刚才的例子中,咱们再次缩小年轻代的空间,由上面的10m缩减为5m,总体堆内存大小不变20m
VM Option:-Xms20M -Xmx20M -Xmn5M
经过运行一段时间的观察咱们发现,减少了年轻代的大小后,年老代的空间占用问题的带了缓解,再也不被始终塞满,可是GC的频率明显增长。
经过jstat观察JVM内存的分配状况,咱们能够看到年轻代分配5120kb(5m),老年代分配15360kb(10m),是按照咱们设置的参数来运行的。
运行一段时间后,咱们经过jstat观察程序的GC状况:
年轻代GC 20379次,总耗时35.923s Full GC 72次 总耗时3.376s
年轻代GC平均耗时 0.001762746s Full GC平均耗时0.046888889s
再次咱们之前一个例子相比,缩减年轻代空间,提高了老年代空间后,相比以前的数据,年轻代GC频率提升,Full GC频率降低。
这几个例子中的平均耗时我的感受没有什么特别多的参考价值,缘由是代码都同样,年轻代的GC效率影响因素本例中主要就是年轻代空间的大小,Full GC的影响效率影响因素本例中主要就是整个堆空间的大小。
经过本例的实验,咱们能够大体得出结论,合理控制堆内存的大小,而且合理分配年轻代与年老代的占比。年轻代太小,将致使年轻代的频繁GC,年轻代中存活的对象将更快速的进入老年代(年轻代中默认年龄计数超过15的对象将分配进入老年代),而且可能致使年轻代对象直接进入老年代,若是此时老年代满了,会触发Full GC。老年代太小将致使频繁的老年代GC以及可以stop the word 的Full GC,所以咱们又会有新的疑问,如何合理的分配年轻代与老年代的占比?答案是不固定的,只有根据自身项目的状况来合理的进行分配,而且经过不断地实验,才能最终得出最合理的分配。
---------------------------------------------------------------------------------------------------------------------------------
对象与基本类型,那个更加GC友好?
public static void main(String[] args) { //封装类型 while(true){ Long n = 99999999999999L; } }
public static void main(String[] args) { while(true){ //基本类型 long n = 99999999999999L; } }
从两种代码的GC走势来看,不难发现基本类型针对GC更加友好,没有频繁的GC发生,反观封装类型,却出现了频繁的GC,所以不可贵出结论,基本类型相比于封装类型针对GC更加友好。
那么为何基本类型GC更加友好呢?
GC的本质就是垃圾回收,回收的是咱们代码运行时没用的对象,既然GC的行为针对的是对象,那么越少对象的生成,对堆空间的占用就越少,GC的执行次数就越少,对GC就越是友好,咱们的代码效率就越高(不会由于GC的执行(YGC、FGC)而影响程序的执行)
---------------------------------------------------------------------------------------------------------------------------------
经过Eclipse Memory Analyzer工具分析OutOfMemoryError异常
VM Option:
-Xms20M -Xmx20M -XX:+PrintGCDetails -Xloggc:D:\workSpace\58QF\JavaInAction\gc.log -XX:+HeapDumpOnOutOfMemoryError
将堆内存设置的小一点,而且打印GC信息以及输出GC日志,当发生OutOfMemoryError时生成Dump信息。
public class GcTest { public static void main(String[] args) { List<String> list = new ArrayList<String>(); for(int i=0;i<10000000;i++){ String str = new String(); list.add(str); } } }
运行一段时间后:
生成日志以下
gc.log最后几行
43.132: [Full GC (Ergonomics) [PSYoungGen: 5632K->5632K(6144K)] [ParOldGen: 13568K->13567K(13824K)] 19200K->19199K(19968K), [Metaspace: 3023K->3023K(1056768K)], 0.0919624 secs] [Times: user=0.31 sys=0.00, real=0.09 secs]
43.226: [Full GC (Ergonomics) [PSYoungGen: 5632K->5632K(6144K)] [ParOldGen: 13571K->13569K(13824K)] 19203K->19201K(19968K), [Metaspace: 3052K->3052K(1056768K)], 0.1015494 secs] [Times: user=0.31 sys=0.00, real=0.10 secs]
43.546: [Full GC (Ergonomics) [PSYoungGen: 5632K->0K(6144K)] [ParOldGen: 13572K->595K(13824K)] 19204K->595K(19968K), [Metaspace: 3093K->3093K(1056768K)], 0.0061648 secs] [Times: user=0.05 sys=0.00, real=0.01 secs]
Heap
PSYoungGen total 6144K, used 300K [0x00000000ff980000, 0x0000000100000000, 0x0000000100000000)
eden space 5632K, 5% used [0x00000000ff980000,0x00000000ff9cb020,0x00000000fff00000)
from space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000)
to space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)
ParOldGen total 13824K, used 595K [0x00000000fec00000, 0x00000000ff980000, 0x00000000ff980000)
object space 13824K, 4% used [0x00000000fec00000,0x00000000fec94d68,0x00000000ff980000)
Metaspace used 3259K, capacity 4486K, committed 4864K, reserved 1056768K
class space used 349K, capacity 386K, committed 512K, reserved 1048576K
使用Eclipse Memory Analyzer分析Dump文件:
能够看到String对象实例已经占据了堆内存97.4%的空间
咱们能够发现,是java.lang.String这个对象致使的OutOfMemoryError,对比代码,仍是很直观的