JAVA 垃圾回收分析与实战

JAVA 垃圾回收分析与实战

本文主要介绍JAVA垃圾回收的一些方法和实际使用的案例

1垃圾回收算法介绍

1.1基本算法

1.1.1引用计数法

给对象引用添加一个计数器,如果有对象引用他,计数器加一;当引用失效时,计数器减一;任何时刻计数器为0就是不在被使用的。

但是JAVA虚拟机中没有使用此算法,主要原因是不能解决相互引用的问题。

/**

 * -verbose.gc -Xloggc:D:/gc.log -XX:+PrintGCDateStamps -XX:+PrintGCDetails

 * @author Administrator

 *

 */

public classReferenceCountingGC {

   

    public Object instance = null;

   

    private static int _1MB = 1024 * 1024;

   

    private byte[] bigsize = new byte[ReferenceCountingGC._1MB];

   

    public static void main(String arg[])

    {

        ReferenceCountingGCobj1= newReferenceCountingGC();

        ReferenceCountingGCobj2= newReferenceCountingGC();

        obj1.instance = obj2;

        obj2.instance = obj1;

       

        obj1 = null;

        obj2 = null;

       

        System.gc();

       

    }

 

}

GC日志结果

Java HotSpot(TM) Client VM (25.144-b01) forwindows-x86 JRE (1.8.0_144-b01), built on Jul 21 2017 21:58:05 by"java_re" with MS VC++ 10.0 (VS2010)

Memory: 4k page, physical 3216576k(1066076kfree), swap 6431404k(4116800k free)

CommandLine flags:-XX:InitialHeapSize=16777216 -XX:MaxHeapSize=268435456 -XX:+PrintGC-XX:+PrintGCDateStamps -XX:+PrintGCDetails -XX:+PrintGCTimeStamps-XX:-UseLargePagesIndividualAllocation

2017-10-22T13:28:39.802+0800: 0.132: [FullGC (System.gc()) 2017-10-22T13:28:39.803+0800: 0.133: [Tenured:0K->505K(10944K), 0.0025180 secs] 2932K->505K(15872K), [Metaspace:1783K->1783K(4480K)], 0.0178342 secs] [Times: user=0.00 sys=0.00, real=0.02secs]

Heap

 defnew generation   total 4992K, used 45K[0x10200000, 0x10760000, 0x15750000)

 eden space 4480K,   1% used[0x10200000, 0x1020b4a8, 0x10660000)

 from space 512K,   0% used[0x10660000, 0x10660000, 0x106e0000)

 to   space 512K,   0% used [0x106e0000, 0x106e0000, 0x10760000)

 tenured generation   total 10944K, used 505K [0x15750000,0x16200000, 0x20200000)

  the space 10944K,   4% used[0x15750000, 0x157ce488, 0x157ce600, 0x16200000)

 Metaspace      used 1787K, capacity 2242K, committed 2368K, reserved 4480K

显示JAVA使用的不是引用计数发

1.1.2可达性性分析算法

在主流的商用程序语言中(Java和C#),都是使用可达性分析算法判断对象是否存活的。这个算法的基本思路就是通过一系列名为"GC Roots"的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的,下图对象object5, object6, object7虽然有互相判断,但它们到GC Roots是不可达的,所以它们将会判定为是可回收对象

GC Root对象包括如下

*)虚拟机栈(栈帧的本地变量表)中引用的对象

*)方法区中静态属性引用的对象

*)方法区中常量引用的对象

*)本地方法栈中JNI(Native方法)引用的对象

1.1.3判断对象是否存活的方式

即使在可达性分析算法中不可达的对象,也并非是“非死不可”的,这时候它们暂时处于“缓刑”阶段,要真正宣告一个对象死亡,至少要经历再次标记过程。

    标记的前提是对象在进行可达性分析后发现没有与GC Roots相连接的引用链。

  1).第一次标记并进行一次筛选。

    筛选的条件是此对象是否有必要执行finalize()方法。

    当对象没有覆盖finalize方法,或者finzlize方法已经被虚拟机调用过,虚拟机将这两种情况都视为“没有必要执行”,对象被回收。

  2).第二次标记

    如果这个对象被判定为有必要执行finalize()方法,那么这个对象将会被放置在一个名为:F-Queue的队列之中,并在稍后由一条虚拟机自动建立的、低优先级的Finalizer线程去执行。这里所谓的“执行”是指虚拟机会触发这个方法,但并不承诺会等待它运行结束。这样做的原因是,如果一个对象finalize()方法中执行缓慢,或者发生死循环(更极端的情况),将很可能会导致F-Queue队列中的其他对象永久处于等待状态,甚至导致整个内存回收系统崩溃。

Finalize()方法是对象脱逃死亡命运的最后一次机会,稍后GC将对F-Queue中的对象进行第二次小规模标记,如果对象要在finalize()中成功拯救自己----只要重新与引用链上的任何的一个对象建立关联即可,譬如把自己赋值给某个类变量或对象的成员变量,那在第二次标记时它将移除出“即将回收”的集合。如果对象这时候还没逃脱,那基本上它就真的被回收了。

/**

 * 此代码演示了两点 1、对象可以在被GC时自我拯救 2、这种自救的机会只有一次,因为一个对象的finalize()方法最多只能被系统自动调用一次。

 */

public classFinalizeEscapeGC

{

    public static FinalizeEscapeGC SAVE_HOOK = null;

 

    public void isAlive()

    {

        System.out.println("yes, I am still alive");

    }

 

    protected void finalize() throws Throwable

    {

        super.finalize();

        System.out.println("finalize method executed!");

        FinalizeEscapeGC.SAVE_HOOK = this;

    }

 

    public static void main(String[] args) throws InterruptedException

    {

        SAVE_HOOK = new FinalizeEscapeGC();

 

        // 对象第一次成功拯救自己

        SAVE_HOOK = null;

        System.gc();

 

        // 因为finalize方法优先级很低,所有暂停0.5秒以等待它

        Thread.sleep(500);

        if (SAVE_HOOK != null)

        {

            SAVE_HOOK.isAlive();

        }else

        {

            System.out.println("no ,I am dead QAQ!");

        }

 

        // -----------------------

        // 以上代码与上面的完全相同,但这次自救却失败了!!!

        SAVE_HOOK = null;

        System.gc();

 

        // 因为finalize方法优先级很低,所有暂停0.5秒以等待它

        Thread.sleep(500);

        if (SAVE_HOOK != null)

        {

            SAVE_HOOK.isAlive();

        }else

        {

            System.out.println("no ,I am dead QAQ!");

        }

    }

}

在实际使用中不建议使用finalize方法

1.1.4回收方法区

废弃常量的回收:假如一个字符串进入了常量池,但是当前系统中没有任何一个String对象叫做这个常量的,那么此时发生常量回收,是有可能回收掉这个常量的

无用的类回收:

*)加载该类的所有实例都已经回收

*)加载该类的ClassLoader已经被回收

*)该类对应的java.lang.Class对象没有任何地方被引用,无法在任何地方通过反射来访问该方法

-verbose:class

输出虚拟机装入的类的信息,显示的信息格式如下: [Loaded java.io.FilePermission$1 from shared objects file] 当虚拟机报告类找不到或类冲突时可用此参数来诊断来查看虚拟机从装入类的情况

–verbose:gc

在虚拟机发生内存回收时在输出设备显示信息,格式如下: [Full GC 268K->168K(1984K), 0.0187390 secs] 该参数用来监视虚拟机内存回收的情况

–verbose:jni

在虚拟机调用native方法时输出设备显示信息,格式如下: [Dynamic-linking native method HelloNative.sum ... JNI] 该参数用来监视虚拟机调用本地方法的情况,在发生jni错误时可为诊断提供便利

-XX:+TraceClassLoading

监控类的加载和-verbose:class类似

1.1.5垃圾收集算法

*)标记-清除算法

1. 标记阶段:找到所有可访问的对象,做个标记

2. 清除阶段:遍历堆,把未被标记的对象回收

优点

- 是可以解决循环引用的问题

- 必要时才回收(内存不足时)

缺点:

- 回收时,应用需要挂起,也就是stopthe world。

- 标记和清除的效率不高,尤其是要扫描的对象比较多的时候

- 会造成内存碎片(会导致明明有内存空间,但是由于不连续,申请稍微大一些的对象无法做到)

*)复制算法

新生代使用这种算法来回收Survivor

*)标记-整理算法

*)分代收集算法

把JVA堆分成新生代和老年代,新生代使用复制算法,老年代使用标记-清除或者标记-复制算法

1.2HotSpot算法实现

1.2.1枚举根节点

可达性分析会有一定时间的stop the world

1.2.2安全点

是否具有让程序长时间执行的特征,例如方法调用、循环跳转、异常跳转

让GC时所有的线程跑到安全点的方式:抢先式中断、主动式中断(GC需要中断线程时,设置一个标志,各个线程执行时主动去轮询这个标志)

1.2.3安全区域

之在一段区域中,引用关系不会发生变化,在这个区域的任意地方开始GC都是安全的

2.垃圾收集器介绍

2.1Serial收集器

单线程、stop the world、简单而高效

运行在Client模式下的虚拟机中首选的新生代收集器

2.2ParNew收集器

多线程、除了Serial之外能和CMS收集器配合工作

运行在Server模式下虚拟机中首选的新生代收集器

2.3Parallel Scavenge收集器

新生代收集器、复制算法、关注吞吐量(运行用户代码时间/(运行用户代码时间+垃圾收集时间))

2.4Serial Old收集器

老年代收集器、单线程、Client下的虚拟机使用、JDK1.5以前和Parallel Sacvenge搭配使用、CMS收集器的后备预案

2.5Parallel Old收集器

老年代收集器,多线程、标记-整理算法

吞吐量优先以及CPU敏感场合,可使用Parallel Scavenge+Parallel Old

2.6CMS收集器

一种以获取最短回收停顿时间为目标的收集器

*初始标记

*并发标记

*重新标记

*并发清除

并发收集、低停顿

缺点:对CPU资源敏感、无法处理浮动垃圾、标记-清除算法产生大量空间碎片

-XX:UseCompactAtFullCollection

默认开启 启动这个功能后,默认每次执行Full GC的时候会进行整理

-XX:CMSFullGCsBeforeCompaction=n

制定多少次Full GC之后来执行整理

-XX:CMSInitiatingOccupancyFraction

旧生代或者持久代已经使用的空间达到设定的百分比时

Cms实例:http://itindex.net/detail/47030-cms-gc-%E9%97%AE%E9%A2%98

2.7G1收集器

面向服务端的收集器、标记整理

优点:并行与并发、分代收集、空间整合、可预测的停顿

3内存分配和回收策略

3.1对象优先在Eden分配

    private static final int _1MB = 1024 * 1024;

 

    /**

     * -Xloggc:D:/gc.log -verbose:class -Xmx20M -Xmn10M -Xms20M -XX:+PrintGCDetails -XX:SurvivorRatio=8

     */

    public static void testAllocation()

    {

        byte[] allocation1, allocation2, allocation3, allocation4;

        allocation1 = new byte[2 * TestMinorGC._1MB];

 

        allocation2 = new byte[2 * TestMinorGC._1MB];

 

        allocation3 = new byte[2 * TestMinorGC._1MB];

 

        allocation4 = new byte[2 * TestMinorGC._1MB];

 

    }

运行结果:

Java HotSpot(TM) Client VM (25.144-b01) forwindows-x86 JRE (1.8.0_144-b01), built on Jul 21 2017 21:58:05 by"java_re" with MS VC++ 10.0 (VS2010)

Memory: 4k page, physical 3216576k(903764kfree), swap 6431404k(3914492k free)

CommandLine flags:-XX:InitialHeapSize=20971520 -XX:MaxHeapSize=20971520 -XX:MaxNewSize=10485760-XX:NewSize=10485760 -XX:+PrintGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps-XX:SurvivorRatio=8 -XX:+TraceClassLoading -XX:+TraceClassUnloading-XX:-UseLargePagesIndividualAllocation

0.174: [GC (Allocation Failure) 0.174:[DefNew: 7127K->505K(9216K), 0.0059995 secs] 7127K->6649K(19456K),0.0061766 secs] [Times: user=0.02 sys=0.00, real=0.01 secs]

Heap

 def newgeneration   total 9216K, used 2636K [0x03e00000,0x04800000, 0x04800000)

 eden space 8192K,  26% used[0x03e00000, 0x04014938, 0x04600000)

 from space 1024K,  49% used[0x04700000, 0x0477e7c8, 0x04800000)

 to   space 1024K,   0% used [0x04600000, 0x04600000, 0x04700000)

 tenured generation   total 10240K, used 6144K [0x04800000,0x05200000, 0x05200000)

   the space10240K,  60% used [0x04800000,0x04e00030, 0x04e00200, 0x05200000)

 Metaspace      used 1787K, capacity 2242K, committed 2368K, reserved 4480K

3.2大对象直接进入老年代

    /**

     * -Xloggc:D:/gc.log -verbose:class -Xmx20M -Xmn10M -Xms20M -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:PretenureSizeThreshold=314578

     */

    public static voidtestPreterureSizeThread()

    {

        byte[] allocation = new byte[4 * TestMinorGC._1MB];

    }

运行结果

Java HotSpot(TM) Client VM (25.144-b01) forwindows-x86 JRE (1.8.0_144-b01), built on Jul 21 2017 21:58:05 by"java_re" with MS VC++ 10.0 (VS2010)

Memory: 4k page, physical 3216576k(889940kfree), swap 6431404k(3903664k free)

CommandLine flags:-XX:InitialHeapSize=20971520 -XX:MaxHeapSize=20971520 -XX:MaxNewSize=10485760-XX:NewSize=10485760 -XX:PretenureSizeThreshold=314578 -XX:+PrintGC-XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:SurvivorRatio=8-XX:+TraceClassLoading -XX:+TraceClassUnloading-XX:-UseLargePagesIndividualAllocation

Heap

 defnew generation   total 9216K, used 1148K[0x03c00000, 0x04600000, 0x04600000)

 eden space 8192K,  14% used[0x03c00000, 0x03d1f000, 0x04400000)

 from space 1024K,   0% used[0x04400000, 0x04400000, 0x04500000)

 to   space 1024K,   0% used [0x04500000, 0x04500000, 0x04600000)

 tenured generation   total 10240K, used 4096K [0x04600000,0x05000000, 0x05000000)

   the space 10240K,  40% used [0x04600000, 0x04a00010,0x04a00200, 0x05000000)

 Metaspace      used 1787K, capacity 2242K, committed 2368K, reserved 4480K

3.3长期存活对象进入老年代

    /**

     * -Xloggc:D:/gc.log -verbose:class -Xmx20M -Xmn10M -Xms20M -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=1

     */

    public static void testTenuringThreshold()

    {

        byte[] allocation1, allocation2, allocation3;

        allocation1 = new byte[TestMinorGC._1MB / 4];

 

        allocation2 = new byte[4 * TestMinorGC._1MB];

 

        allocation3 = new byte[4 * TestMinorGC._1MB];

       

        allocation3 = null;

       

        allocation3 = new byte[4 * TestMinorGC._1MB];

 

    }

运行结果:

Java HotSpot(TM) Client VM (25.144-b01) forwindows-x86 JRE (1.8.0_144-b01), built on Jul 21 2017 21:58:05 by"java_re" with MS VC++ 10.0 (VS2010)

Memory: 4k page, physical 3216576k(995048kfree), swap 6431404k(4018836k free)

CommandLine flags:-XX:InitialHeapSize=20971520 -XX:InitialTenuringThreshold=1-XX:MaxHeapSize=20971520 -XX:MaxNewSize=10485760 -XX:MaxTenuringThreshold=1-XX:NewSize=10485760 -XX:+PrintGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps-XX:SurvivorRatio=8 -XX:+TraceClassLoading -XX:+TraceClassUnloading-XX:-UseLargePagesIndividualAllocation

0.170: [GC (Allocation Failure) 0.170:[DefNew: 5335K->762K(9216K), 0.0040218 secs] 5335K->4858K(19456K),0.0041945 secs] [Times: user=0.00 sys=0.02, real=0.00 secs]

0.176: [GC (Allocation Failure) 0.176:[DefNew: 4858K->0K(9216K), 0.0011812 secs] 8954K->4857K(19456K),0.0012454 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]

Heap

 defnew generation   total 9216K, used 4178K[0x03c00000, 0x04600000, 0x04600000)

 eden space 8192K,  51% used[0x03c00000, 0x04014938, 0x04400000)

 from space 1024K,   0% used[0x04400000, 0x04400000, 0x04500000)

 to   space 1024K,   0% used [0x04500000, 0x04500000, 0x04600000)

 tenured generation   total 10240K, used 4857K [0x04600000,0x05000000, 0x05000000)

   the space 10240K,  47% used [0x04600000, 0x04abe5a0,0x04abe600, 0x05000000)

 Metaspace      used 1787K, capacity 2242K, committed 2368K, reserved 4480K

3.4动态对象年龄判断

如果Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于等于该年龄的对象就可以直接进入老年代

   

    /**

     * -Xloggc:D:/gc.log -verbose:class -Xmx20M -Xmn10M -Xms20M -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=15

     */

    public static voidtestTenuringThreshold2()

    {

        byte[] allocation1 = new byte[TestMinorGC._1MB / 4];

 

        byte[] allocation2 = new byte[TestMinorGC._1MB / 4];

 

        byte[] allocation3 = new byte[4 * TestMinorGC._1MB];

       

        byte[] allocation4 = new byte[4 * TestMinorGC._1MB];

 

       

        allocation3 = null;

       

        allocation3 = new byte[4 * TestMinorGC._1MB];

 

    }

运行结果

Java HotSpot(TM) Client VM (25.144-b01) forwindows-x86 JRE (1.8.0_144-b01), built on Jul 21 2017 21:58:05 by"java_re" with MS VC++ 10.0 (VS2010)

Memory: 4k page, physical 3216576k(973840kfree), swap 6431404k(4000372k free)

CommandLine flags: -XX:InitialHeapSize=20971520-XX:MaxHeapSize=20971520 -XX:MaxNewSize=10485760 -XX:MaxTenuringThreshold=15-XX:NewSize=10485760 -XX:+PrintGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps-XX:SurvivorRatio=8 -XX:+TraceClassLoading -XX:+TraceClassUnloading-XX:-UseLargePagesIndividualAllocation

0.162: [GC (Allocation Failure) 0.162:[DefNew: 5591K->1018K(9216K), 0.0040892 secs] 5591K->5114K(19456K),0.0042587 secs] [Times: user=0.02 sys=0.00, real=0.00 secs]

0.167: [GC (Allocation Failure) 0.167:[DefNew: 5114K->0K(9216K), 0.0034930 secs] 9210K->9209K(19456K),0.0035635 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]

Heap

 defnew generation   total 9216K, used 4178K[0x03e00000, 0x04800000, 0x04800000)

 eden space 8192K,  51% used[0x03e00000, 0x04214938, 0x04600000)

 from space 1024K,   0% used[0x04600000, 0x04600000, 0x04700000)

 to   space 1024K,   0% used [0x04700000, 0x04700000, 0x04800000)

 tenured generation   total 10240K, used 9209K [0x04800000,0x05200000, 0x05200000)

   thespace 10240K,  89% used [0x04800000,0x050fe670, 0x050fe800, 0x05200000)

 Metaspace      used 1788K, capacity 2242K, committed 2368K, reserved 4480K

3.5空间担保分配

再发生Minor GC之前,虚拟机会检查老年代最大可用的连续空间是否大于新生代所有对象之和,如果这个条件成立,那么Minor GC可以确保是安全的。如果不成立,则虚拟机会查看HandlePromotionFailure设置值是否担保失败,如果允许,则继续检查老年代最大可用的连续空间是否大于历次晋升到老年代的平均大小,如果大于,则尝试进行一次Minor GC,尽快这次是由风险的;如果小于,或者HandlePromotionFailure设置不允许冒险,那这时要改为进行一次Full GC。

4相关启动参数介绍

参考http://www.cnblogs.com/f-zhao/p/6160656.html

http://www.cnblogs.com/rwxwsblog/p/6248205.html

猜你喜欢

转载自blog.csdn.net/a_ust/article/details/78310406