Java初识 VisualVM

目的:熟悉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,对比代码,仍是很直观的

相关文章
相关标签/搜索