GC

作者: Richard_80ec | 来源:发表于2017-12-13 12:08 被阅读0次

    查看GC日志时需要用到的虚拟机参数:

    -XX:+PrintGC 输出GC日志
    -XX:+PrintGCDetails 输出GC的详细日志
    -XX:+PrintGCTimeStamps 输出GC的时间戳(以基准时间的形式)
    -XX:+PrintGCDateStamps 输出GC的时间戳(以日期的形式,如2017-12-13T12:07:59.234+0800)
    -XX:+PrintHeapAtGC 在进行GC的前后打印出堆的信息
    -Xloggc:../logs/gc.log 日志文件的输出路径

    1.引用计数算法

    给对象中添加一个医用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1;任何时刻计数器为0的对象就是不可能再被使用的。
    测试:

    /**
     * VM Args:-XX:+PrintGCDetails
     */
    public class ReferenceCountingGC {
    
        public Object instance = null;
    
        private static final int _1MB = 1024*1024;
    
        /**
         * 这个成员属性的唯一意义就是占点内存,以便能在GC日志中看清楚是否被回收过
         */
        private byte[] bigSize = new byte[2*_1MB];
    
        public static void testGc(){
            ReferenceCountingGC objA = new ReferenceCountingGC();
            ReferenceCountingGC objB = new ReferenceCountingGC();
            objA.instance = objB;
            objB.instance = objA;
    
            objA = null;
            objB = null;
    
            System.gc();
        }
    
        public static void main(String[] args) {
            ReferenceCountingGC.testGc();
        }
    }
    

    运行结果:

    [GC [PSYoungGen: 6759K->712K(38400K)] 6759K->712K(124928K), 0.0011986 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
    [Full GC [PSYoungGen: 712K->0K(38400K)] [ParOldGen: 0K->610K(86528K)] 712K->610K(124928K) [PSPermGen: 2910K->2909K(21504K)], 0.0099723 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
    Heap
     PSYoungGen      total 38400K, used 2330K [0x00000007d5c00000, 0x00000007d8680000, 0x0000000800000000)
      eden space 33280K, 7% used [0x00000007d5c00000,0x00000007d5e46810,0x00000007d7c80000)
      from space 5120K, 0% used [0x00000007d7c80000,0x00000007d7c80000,0x00000007d8180000)
      to   space 5120K, 0% used [0x00000007d8180000,0x00000007d8180000,0x00000007d8680000)
     ParOldGen       total 86528K, used 610K [0x0000000781400000, 0x0000000786880000, 0x00000007d5c00000)
      object space 86528K, 0% used [0x0000000781400000,0x0000000781498b00,0x0000000786880000)
     PSPermGen       total 21504K, used 2927K [0x000000077c200000, 0x000000077d700000, 0x0000000781400000)
      object space 21504K, 13% used [0x000000077c200000,0x000000077c4dbf70,0x000000077d700000)
    

    GC日志分析:

    [GC [PSYoungGen(使用PSYoungGen作为年轻代的垃圾回收器): 6759K(年轻代垃圾回收前的大小)->712K(年轻代垃圾回收以后的大小)(38400K)(年轻带的总大小)] 6759K(堆区垃圾回收前的大小)->712K(堆取垃圾回收后的大小)(124928K)(堆区总大小), 0.0011986 secs(回收时间)] [Times: user=0.00(Young GC用户耗时) sys=0.00(Young GC系统耗时), real=0.00 secs(Young GC实际耗时)] 
    [Full GC [PSYoungGen(年轻代): 712K->0K(38400K)] [ParOldGen(年老代): 0K->610K(86528K)] 712K->610K(124928K) [PSPermGen(持久代): 2910K->2909K(21504K)], 0.0099723 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
    下面则是堆的具体信息,暂时笔者还未能解读
    

    从上述的运行结果可以看到,进行GC后,年轻代的空间被GC进行了极大的清理,则说明objA和objB之间的循环引用并没有影响到GC回收,说明虚拟机采用的并不是引用计数算法。

    2、可达性分析算法

    在可达性分析算法中,一个很重要的概念就是“GC Roots”,"GC Root"顾名思义即是GC进行的根,GC会判断堆中的对象是否与“GC Roots”能够通过某条线路连接到。如下图所示:


    gc-root.png

    图中object1~object4都与GC Roots能够连接到,因此不能回收,可以存活,而object5~object7无法连接,因此是可以回收的。
    可以作为GC Roots的对象:
    1、栈中引用的对象。根据内存模型知道,栈中是正在运行的线程存放的数据,因此当正在运行的程序在引用的对象不能回收,不然会造成问题。
    2、方法区中静态属性引用的对象。静态属性会在堆中存一份对象数据,等待调用,这个也是不允许回收的。
    3、方法区中常量引用的对象。方法区中有运行时常量池,在常量池中引用的对象也是不允许回收的。
    4、本地方法栈中引用的对象。

    3、强、软、弱、虚引用

    无论用引用计数算法判断对象的引用数量,还是可达性分析算法判断引用链是否可达,都需要判断对象的引用。JDK1.2后,将引用分为四类:强引用(Strong Reference),软引用(SoftReference),弱引用(WeakReference),虚引用(PhantomReference)。这四种引用强度依次减弱。
    强引用:普通的引用方法。例如new一个对象,Object obj = new Object();只有在这种引用关系失效的时候,GC才会考虑回收这个对象。
    测试代码:

    /**
     * VM args:-XX:+PrintGCDetails -XX:+PrintAssembly
     */
    public class StrongReferenceTest {
    
        public static void main(String[] args) {
            Object referent = new Object();
    
            /**
             * 通过赋值创建strongReference,此时new Object对象同事被两个变量引用到
             */
            Object strongReference = referent;
    
            System.out.println(referent);
            System.out.println(referent.equals(strongReference));
            referent = null;
    
            System.gc();
    
            System.out.println(strongReference);
        }
    }
    

    测试结果:

    java.lang.Object@4b1210ee
    true
    [Full GC (System.gc()) [Tenured: 0K->712K(87424K), 0.0029994 secs] 2795K->712K(126720K), [Metaspace: 3210K->3210K(1056768K)], 0.0030517 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] 
    java.lang.Object@4b1210ee
    Heap
     def new generation   total 39424K, used 1754K [0x0000000081400000, 0x0000000083ec0000, 0x00000000ab800000)
      eden space 35072K,   5% used [0x0000000081400000, 0x00000000815b6850, 0x0000000083640000)
      from space 4352K,   0% used [0x0000000083640000, 0x0000000083640000, 0x0000000083a80000)
      to   space 4352K,   0% used [0x0000000083a80000, 0x0000000083a80000, 0x0000000083ec0000)
     tenured generation   total 87424K, used 712K [0x00000000ab800000, 0x00000000b0d60000, 0x0000000100000000)
       the space 87424K,   0% used [0x00000000ab800000, 0x00000000ab8b2078, 0x00000000ab8b2200, 0x00000000b0d60000)
     Metaspace       used 3232K, capacity 4494K, committed 4864K, reserved 1056768K
      class space    used 354K, capacity 386K, committed 512K, reserved 1048576K
    

    软引用:比强引用稍弱的引用方法。当某个对象只有这一个软引用存在,且内存不足的时候,GC会考虑回收这部分资源。
    如:

    Object obj = new Object();
    SoftReference<Object> softReference = new SoftReference<>(obj);
    

    当使obj = null时,就只有softReference引用了Object对象,如果内存不足,则GC就会释放这部分资源,当内存充足时,这部分资源会依然存在。
    测试代码:

    /**
     * VM args:-XX:+PrintGCDetails
     */
    public class SoftReferenceTest {
    
        public static void main(String[] args) {
    
            Object referent = new Object();
    
            /**
             * 通过赋值创建softReference
             */
            SoftReference<Object> softReference = new SoftReference<Object>(referent);
    
            System.out.println(referent.equals(softReference.get()));
            referent = null;
    
            System.gc();
            System.out.println(softReference.get());
        }
    }
    

    测试结果:

    true
    java.lang.Object@4b1210ee
    

    弱引用:比软引用强度更弱。不管内存够不够用,在下一次GC时,发现某对象只有弱引用时,GC会清理释放这部分资源。
    测试代码:

    /**
     *
     */
    public class WeakReferenceTest {
    
        public static void main(String[] args) {
            Object referent = new Object();
    
            /**
             * 通过赋值创建weakReference
             */
            WeakReference<Object> weakReference = new WeakReference<Object>(referent);
    
            System.out.println(referent.equals(weakReference.get()));
            referent = null;
    
            System.gc();
    
            System.out.println(weakReference.get());
        }
    }
    

    测试结果:

    true
    null
    

    虚引用:是最弱的一种引用类型。GC回收时对于虚引用会当做没有引用存在一样。
    测试代码:

    /**
     * phantom reference 的 get 方法永远返回 null
     * PhantomReference 唯一的用处就是跟踪 referent何时被 enqueue 到 ReferenceQueue 中.
     * 当一个 WeakReference 开始返回 null 时, 它所指向的对象已经准备被回收,
     * 这时可以做一些合适的清理工作. 将一个 ReferenceQueue 传给一个 Reference 的构造函数,
     * 当对象被回收时, 虚拟机会自动将这个对象插入到 ReferenceQueue 中,
     * WeakHashMap 就是利用 ReferenceQueue 来清除 key 已经没有强引用的 entries.
     */
    public class PhantomReferenceTest {
        public static void main(String[] args) {
    
            Object referent = new Object();
    
            /**
             * 通过赋值创建phantomReference
             */
            PhantomReference<Object> phantomReference = new PhantomReference<>(referent,new ReferenceQueue<Object>());
    
            /**
             * phantom reference 的 get 方法永远返回 null
             */
            System.out.println(referent.equals(phantomReference.get()));
            referent = null;
    
            System.gc();
            System.out.println(phantomReference.get());
        }
    }
    

    测试结果:

    false
    null
    

    引用的用途

    WeakReference:当对对象结构和拓扑不是很清晰的时候,可以通过弱引用,可以合理的释放对象,而不会造成不必要的内存泄漏。
    如:

    A a = new A();
    WeakReference wr = new WeakReference(a);
    //B b = new B(a);
    

    引用关系如下图:


    image.png

    当使a=null时,A就只有弱引用依赖,因此GC会立刻回收A这个对象。
    SoftReference:软引用有个很重要的特性就是在内存充足时会保留资源,当内存不足时会释放资源,因此很适合用来做缓存处理。
    摘取网络上的一个应用softReference的代码:

    public class ImageLoader {  
          
        private Map<String,SoftReference<Bitmap>> cacheImage = new HashMap<String,SoftReference<Bitmap>>();  
          
        public void loadImage(final String path,final Callback callback){  
            SoftReference<Bitmap> softReference = cacheImage.get(path);  
            if(softReference!=null){  
                Bitmap bm = softReference.get();  
                if(bm!=null){  
                    callback.execute(bm);  
                    return;  
                }  
            }  
            new Thread(new Runnable() {  
               public void run() {  
                HttpClient client = new DefaultHttpClient();  
                try {  
                    HttpResponse response = client.execute(new HttpGet(path));  
                    HttpEntity entity = response.getEntity();  
                    byte []bs= EntityUtils.toByteArray(entity);  
                    final Bitmap bm = BitmapFactory.decodeByteArray(bs, 0,bs.length);  
                    SoftReference<Bitmap> reference = new SoftReference<Bitmap>(bm);   
                    cacheImage.put(path,reference);  
                    callback.execute(bm);  
                    entity.consumeContent();  
                } catch (Exception e) {  
                    e.printStackTrace();  
                }  
                }  
            }).start();  
        }  
          
        public static abstract class Callback{  
            abstract void execute(Bitmap bm);  
        }  
    }
    

    这个就是用软引用来达到缓存处理的方法,这个在andriod中应用得较多,然而有人发现这个方法处理缓存并不是很理想,因此慢慢也在被人抛弃。因为当有10个对象只有软引用时,GC不知道到底clear哪几个或者keep哪几个,甚至,gc会不知道到底是clear资源还是扩展heap。
    PhantomReference:虚引用要和ReferenceQueue搭配使用。
    在构造Reference对象时,有两种构造函数可供选择:

    /* -- Constructors -- */
    
        Reference(T referent) {
            this(referent, null);
        }
    
        Reference(T referent, ReferenceQueue<? super T> queue) {
            this.referent = referent;
            this.queue = (queue == null) ? ReferenceQueue.NULL : queue;
        }
    

    一种是带ReferenceQueue的,另一种不带此参数。
    根据http://blog.csdn.net/u012332679/article/details/57489179所说:

    ReferenceQueue这个类有什么用呢,它跟Reference有什么关系呢,关系主要体现在这几个方面,首先Reference这个类里面在构造函数的时候有两种选择,一种是给它传入一个ReferenceQueue,一种是不传,如果不传的话,等这个对象的内存被回收了,直接从Active变为Inactive状态,如果我们传入了ReferenceQueue,那么当对象的内存回收的时候会经历一个过程,从Active->Pending->Enqueued->Inactive。pending状态就是等待着进入ReferenceQueue队列的这样一个状态,说白了它目前还没被回收,只是对象的引用(用户代码中的引用)被移除了,pending保存了这个引用,回收的过程中,ReferenceHandler这个线程会把该对象的引用(pending)放入到我们在构造函数时传入的那个队列里面
    

    ReferenceQueue就是当一个对象gc调后,对象的信息会再保存一段时间,以便我们能够进行额外的操作。
    有一个问题:当在ReferenceQueue中发现SoftReference或WeakReference对象时,并不能确定该对象引用的对象已被销毁,此时的对象只是进入了Finalizable状态,然而如果使用的是PhantomReference,在ReferenceQueue中发现了PhantomReference对象,此时referent则已经销毁了。

    相关文章

      网友评论

          本文标题:GC

          本文链接:https://www.haomeiwen.com/subject/ofyaixtx.html