A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

Java内存管理分为内存分配和内存回收,都不需要程序员负责,垃圾回收的机制主要是看对象是否有引用指向该对象。

Java对象的引用包括:强引用,软引用,弱引用,虚引用,Java中提供这四种引用类型主要有两个目的:

可以让程序员通过代码的方式决定某些对象的生命周期。
有利于JVM进行垃圾回收。


目录:

强引用(StrongReference)
软引用(SoftReference)
弱引用(WeakReference)
虚引用(PhantomReference)
引用队列(ReferenceQueue)




1. 强引用(StrongReference)



1.1 概念
强引用是指创建一个对象并它赋值给一个引用,引用是存在JVM中的栈中的。

例如:

    private static void testStrongReference() {
        Object object = new Object();
        Object[] objects = new Object[1000];
    }
可以看到,testStrongReference()方法中,创建了一个对象和一个对象数组,当testStrongReference()执行完,JVM会自动回收它们(属于局部变量)。但是因为它们都是强引用,所以当执行到testStrongRefenerce()时,如果内存不足,不足够在堆中分配这些内存,那么JVM将抛出OutOfMemory,因为强引用的对象,只要有引用变量指向它们的时候,它们将不会被垃圾回收。

如果想中断强引用和某个对象之间的关联,可以显示地将引用赋值为null,这样一来的话,JVM在合适的时间就会回收该对象。



1.2 例子
看看Vector类的清理方法:

    protected Object[] elementData;

    public synchronized void removeAllElements() {
        modCount++;
        // Let gc do its work
        for (int i = 0; i < elementCount; i++)
            elementData = null;

        elementCount = 0;
    }
在清除数据的时候,将数组中的每个元素都置为null,中断强引用与对象之间的关系,让GC的时候能够回收这些对象的内存。







2. 软引用(SoftReference)

我做Android时,第一次接触引用这个概念就是从软引用开始的,那是13年的时候,当时还没有那么多的第三方图片加载框架,图片加载都得自己写。当时的实现方法中,内存中缓存的Bitmap使用的就是软引用。



2.1 概念
如果一个对象具有软引用,只要内存空间足够,垃圾回收器就不会回收它。如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。

软引用可用来实现内存敏感的高速缓存,比如网页缓存、图片缓存等。使用软引用能防止内存泄露,增强程序的健壮性。   
SoftReference的特点是它的一个实例保存对一个Java对象的软引用, 该软引用的存在不妨碍垃圾收集线程对该Java对象的回收。也就是说,一旦SoftReference保存了对一个Java对象的软引用后,在垃圾线程对这个Java对象回收前,SoftReference类所提供的get()方法返回Java对象的强引用。一旦垃圾线程回收该Java对象之 后,get()方法将返回null。



2.2 例子
分享一个平时开发中的例子,就是上面说的图片缓存。

public class ImageMemoryCache {
        /**
         * 从内存读取数据速度是最快的,为了更大限度使用内存,这里使用了两层缓存。 硬引用缓存不会轻易被回收,用来保存常用数据,不常用的转入软引用缓存。
         */
        private static final int SOFT_CACHE_SIZE = 20; // 软引用缓存容量
        private static LruCache<String, Bitmap> mLruCache; // 硬引用缓存
        private static LinkedHashMap<String, SoftReference<Bitmap>> mSoftCache; // 软引用缓存

        public ImageMemoryCache(Context context) {
                int memClass = ((ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE)).getMemoryClass();
                int cacheSize = 1024 * 1024 * memClass / 4; // 硬引用缓存容量,为系统可用内存的1/4
                mLruCache = new LruCache<String, Bitmap>(cacheSize) {
                        @Override
                        protected int sizeOf(String key, Bitmap value) {
                                if (value != null)
                                        return value.getRowBytes() * value.getHeight();
                                else
                                        return 0;
                        }

                        @Override
                        protected void entryRemoved(boolean evicted, String key, Bitmap oldValue, Bitmap newValue) {
                                if (oldValue != null)
                                        // 硬引用缓存容量满的时候,会根据LRU算法把最近没有被使用的图片转入此软引用缓存
                                        mSoftCache.put(key, new SoftReference<Bitmap>(oldValue));
                        }
                };
                mSoftCache = new LinkedHashMap<String, SoftReference<Bitmap>>(SOFT_CACHE_SIZE, 0.75f, true) {
                        private static final long serialVersionUID = 6040103833179403725L;

                        @Override
                        protected boolean removeEldestEntry(Entry<String, SoftReference<Bitmap>> eldest) {
                                if (size() > SOFT_CACHE_SIZE) {
                                        return true;
                                }
                                return false;
                        }
                };
        }

        /**
         * 从缓存中获取图片
         */
        public Bitmap getBitmapFromCache(String url) {
                Bitmap bitmap;
                // 先从硬引用缓存中获取
                synchronized (mLruCache) {
                        bitmap = mLruCache.get(url);
                        if (bitmap != null) {
                                // 如果找到的话,把元素移到LinkedHashMap的最前面,从而保证在LRU算法中是最后被删除
                                mLruCache.remove(url);
                                mLruCache.put(url, bitmap);
                                return bitmap;
                        }
                }
                // 如果硬引用缓存中找不到,到软引用缓存中找
                synchronized (mSoftCache) {
                        SoftReference<Bitmap> bitmapReference = mSoftCache.get(url);
                        if (bitmapReference != null) {
                                bitmap = bitmapReference.get();
                                if (bitmap != null) {
                                        // 将图片移回硬缓存
                                        mLruCache.put(url, bitmap);
                                        mSoftCache.remove(url);
                                        return bitmap;
                                } else {
                                        mSoftCache.remove(url);
                                }
                        }
                }
                return null;
        }

        /**
         * 添加图片到缓存
         */
        public void addBitmapToCache(String url, Bitmap bitmap) {
                if (bitmap != null) {
                        synchronized (mLruCache) {
                                mLruCache.put(url, bitmap);
                        }
                }
        }
       
        public void removeBitmap(String url) {
                if (url != null) {
                        synchronized (mLruCache) {
                                mLruCache.remove(url);
                        }
                        synchronized (mSoftCache) {
                                mSoftCache.remove(url);
                        }
                }
        }

        public void clearCache() {
                mSoftCache.clear();
        }
}






3. 弱引用(WeakReference)



3.1 概念
弱引用也是用来描述非必需对象的,当JVM进行垃圾回收时,无论内存是否充足,都会回收被弱引用关联的对象。其实,上面图片缓存的框架用弱引用也可以做,特别是在移动设备上,内存容量有限。



3.2 例子
还是以平时开发中遇到的问题为例:

(1) Handler内存泄漏问题

Android中使用Handler可以实现在不同线程间通信,如果在Activity界面使用Handler,由于Handler的机制(这边就不细说),当Activity退出后,处理不当,Handler会一直持有该Activity的引用,那引起的内存泄漏是很大的。如何解决呢?

private static class NoLeakHandler extends Handler{

        private WeakReference<Activity> mActivityRef;

        public NoLeakHandler(Activity activity){
            mActivityRef = new WeakReference<>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            if (mActivityRef.get() != null) {
                 // 处理相应的逻辑
            }
        }
    }
}
使用静态内部类(脱离外部类引用的限制) + 弱引用的方式解决这种内存泄漏的问题。当然解决源头还是在Activity退出的时候,移除Handler内部消息队列的数据。



(2) ViewPager适配器

Android中的ViewPager + Fragment是很多项目界面采用的组合,但是Fragment用的不好,性能上会相差很大,造成内存占用大,卡顿的问题。我一般会这么写ViewPager的适配器:

    private static final class TabPagerAdapter extends FragmentPagerAdapter {

        private final WeakReference<Fragment>[] data;

        @SuppressWarnings("unchecked")
        TabPagerAdapter(FragmentManager fm) {
            super(fm);
            data = (WeakReference<Fragment>[]) Array.newInstance(WeakReference.class, getCount());
        }

        @Override
        public Fragment getItem(int position) {
            if (position < 0 || position >= getCount()) {
                return new Fragment();
            }
            Fragment f = null;
            if (!Requires.isNull(data[position])) {
                f = data[position].get();
            } else {
                if (position == 0) {
                    f = HomeFragment.newInstance();
                } else if (position == 1) {
                    f = DappFragment.newInstance();
                } else if (position == 2) {
                    f = SettingsFragment.newInstance();
                }
                data[position] = new WeakReference<>(f);
            }
            return f;
        }

        @Override
        public int getCount() {
            return 3;
        }
    }
第一是懒加载方式,滑动到某个界面的时候,再构造对应的Fragment,同时Fragment使用弱引用包装。







4. 虚引用(PhantomReference)



4.1 概念

虚引用和前面的软引用、弱引用不同,它并不影响对象的生命周期。如果一个对象与虚引用关联,则跟没有引用与之关联一样,在任何时候都可能被垃圾回收器回收。

要注意的是,虚引用必须和引用队列关联使用,当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会把这个虚引用加入到与之关联的引用队列中。程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。如果程序发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动。



4.2 例子

    public static void main(String[] args) {
        Object object = new Object();
        ReferenceQueue<Object> referenceQueue = new ReferenceQueue<>();
        PhantomReference<Object> phantomReference = new PhantomReference<>(object, referenceQueue);
        // 一直返回null,PhantomReference的get()结果必定为null
        System.out.println("phantomReference.get() = " + phantomReference.get());
        Reference<?> reference;
        while ((reference = referenceQueue.poll()) != null) {
            if (reference.equals(phantomReference)) {
                System.out.println("被回收了");
            }
        }
    }
注意:PhantomReference的get()结果必定为null。







5. 引用队列(ReferenceQueue)



5.1 概念
上面在虚引用中说到了引用队列,再举个例子,作为一个Java对象,SoftReference对象除了具有保存软引用的特殊性之外,也具有Java对象的一般性。所以,当软可及对象被回收之后,虽然这个SoftReference对象的get()方法返回null,但这个SoftReference对象已经不再具有存在的价值,需要一个适当的清除机制,避免大量SoftReference对象带来的内存泄漏。在java.lang.ref包里还提供了ReferenceQueue。如果在创建SoftReference对象的时候,使用了一个ReferenceQueue对象作为参数提供给SoftReference的构造方法:

ReferenceQueue queue = new ReferenceQueue();  
SoftReference  ref = new SoftReference(object, queue);  
那么当这个SoftReference所软引用的对象被垃圾收集器回收的同时,ref所强引用的SoftReference对象被列入ReferenceQueue。也就是说,ReferenceQueue中保存的对象是Reference对象,而且是已经失去了它所软引用的对象的Reference对象。另外从ReferenceQueue这个名字也可以看出,它是一个队列,当我们调用它的poll()方法的时候,如果这个队列中不是空队列,那么将返回队列前面的那个Reference对象。

在任何时候,我们都可以调用ReferenceQueue的poll()方法来检查是否有它所关心的非强可及对象被回收。如果队列为空,将返回一个null,否则该方法返回队列中前面的一个Reference对象。利用这个方法,我们可以检查哪个SoftReference所软引用的对象已经被回收,于是我们可以把这些失去所软引用的对象的SoftReference对象清除掉。

SoftReference ref = null;
while ((ref = (EmployeeRef) q.poll()) != null) {
    // 清除ref
}
再来看看几种引用的源码定义:

public class SoftReference<T> extends Reference<T> {

    static private long clock;

    private long timestamp;

    public SoftReference(T referent) {
        super(referent);
        this.timestamp = clock;
    }

    public SoftReference(T referent, ReferenceQueue<? super T> q) {
        super(referent, q);
        this.timestamp = clock;
    }

    public T get() {
        T o = super.get();
        if (o != null && this.timestamp != clock)
            this.timestamp = clock;
        return o;
    }
}
public class WeakReference<T> extends Reference<T> {

    public WeakReference(T referent) {
        super(referent);
    }

    public WeakReference(T referent, ReferenceQueue<? super T> q) {
        super(referent, q);
    }
}
public class PhantomReference<T> extends Reference<T> {

    // get()只会返回null
    public T get() {
        return null;
    }

    public PhantomReference(T referent, ReferenceQueue<? super T> q) {
        super(referent, q);
    }
}
可以看到,几种引用都提供了传入引用队列的构造器。



Reference类
public abstract class Reference<T> {

    private T referent;

    volatile ReferenceQueue<? super T> queue;

    @SuppressWarnings("rawtypes")
    Reference next;

    transient private Reference<T> discovered;

    static private class Lock { }
    private static Lock lock = new Lock();

    private static Reference<Object> pending = null;

    private static class ReferenceHandler extends Thread {

        private static void ensureClassInitialized(Class<?> clazz) {
            try {
                Class.forName(clazz.getName(), true, clazz.getClassLoader());
            } catch (ClassNotFoundException e) {
                throw (Error) new NoClassDefFoundError(e.getMessage()).initCause(e);
            }
        }

        static {
            ensureClassInitialized(InterruptedException.class);
            ensureClassInitialized(Cleaner.class);
        }

        ReferenceHandler(ThreadGroup g, String name) {
            super(g, name);
        }

        public void run() {
            while (true) {
                tryHandlePending(true);
            }
        }
    }

    static boolean tryHandlePending(boolean waitForNotify) {
        Reference<Object> r;
        Cleaner c;
        try {
            synchronized (lock) {
                if (pending != null) {
                    r = pending;
                    c = r instanceof Cleaner ? (Cleaner) r : null;
                    pending = r.discovered;
                    r.discovered = null;
                } else {
                    if (waitForNotify) {
                        lock.wait();
                    }
                    return waitForNotify;
                }
            }
        } catch (OutOfMemoryError x) {
            Thread.yield();
            return true;
        } catch (InterruptedException x) {
            return true;
        }

        if (c != null) {
            c.clean();
            return true;
        }

        ReferenceQueue<? super Object> q = r.queue;
        if (q != ReferenceQueue.NULL) q.enqueue(r);
        return true;
    }

    static {
        ThreadGroup tg = Thread.currentThread().getThreadGroup();
        for (ThreadGroup tgn = tg;
             tgn != null;
             tg = tgn, tgn = tg.getParent());
        Thread handler = new ReferenceHandler(tg, "Reference Handler");
        handler.setPriority(Thread.MAX_PRIORITY);
        handler.setDaemon(true);
        handler.start();

        SharedSecrets.setJavaLangRefAccess(new JavaLangRefAccess() {
            @Override
            public boolean tryHandlePendingReference() {
                return tryHandlePending(false);
            }
        });
    }

    public T get() {
        return this.referent;
    }

    public void clear() {
        this.referent = null;
    }

    public boolean isEnqueued() {
        return (this.queue == ReferenceQueue.ENQUEUED);
    }

    public boolean enqueue() {
        return this.queue.enqueue(this);
    }

    Reference(T referent) {
        this(referent, null);
    }

    Reference(T referent, ReferenceQueue<? super T> queue) {
        this.referent = referent;
        this.queue = (queue == null) ? ReferenceQueue.NULL : queue;
    }
}
大致解释下:

Reference主要是负责内存的一个状态,当然它还和java虚拟机,垃圾回收器打交道。Reference类首先把内存分为4种状态Active,Pending,Enqueued,Inactive。

一般来说内存一开始被分配的状态都是Active。
Pending是指快要被放进队列的对象,也就是马上要回收的对象。
Enqueued就是对象的内存已经被回收了,我们已经把这个对象放入到一个队列中,方便以后我们查询某个对象是否被回收。
Inactive就是最终的状态,不能再变为其它状态。
几个变量:

referent表示其引用的对象,即在构造的时候需要被包装在其中的对象。
queue是对象即将被回收时所要通知的队列。当对象即将被回收时,整个reference对象,而不仅仅是被回收的对象,会被放到queue里面,然后外部程序即可通过监控这个queue即可拿到相应的数据了。
next即当前引用节点所存储的下一个即将被处理的节点。但next仅在放到queue中才会有意义,因为只有在enqueue的时候,会将next设置为下一个要处理的Reference对象。为了描述相应的状态值,在放到队列当中后,其queue就不会再引用这个队列了。而是引用一个特殊的 ENQUEUED(内部定义的一个空队列)。因为已经放到队列当中,并且不会再次放到队列当中。
discovered表示要处理的对象的下一个对象。即可以理解要处理的对象也是一个链表,通过discovered进行排队,这边只需要不停地拿到pending,然后再通过discovered不断地拿到下一个对象赋值给pending即可,直到取到了最有一个。它是被JVM使用的。
pending是等待被入队的引用列表。JVM收集器会添加引用到这个列表,直到Reference-handler线程移除了它们。这个列表使用discovered 字段来连接它下一个元素(即 pending 的下一个元素就是discovered对象。r = pending; pending = r.discovered)。
当 Refrence类被加载的时候,会执行静态代码块。在静态代码块里面,会启动ReferenceHandler线程,并设置线程的级别为最大级别:Thread.MAX_PRIORITY。

在 tryHandlePending()方法里面,检查 pending是否为null,如果pending不为null,则将pending进行enqueue,否则线程进入 wait状态。简单来说,垃圾回收器会把 References添加进入,Reference-handler thread会移除它,即discovered和pending 是由垃圾回收器进行赋值的。

总结一下:

Refrence和引用队列ReferenceQueue联合使用时,如果Refrence持有的对象被垃圾回收,Java虚拟机就会把这个引用加入到与之关联的引用队列中。Android中著名的内存泄漏检测工具LeakCannary就是用这种方式做的,后面的Android第三方库源码分析会有一篇LeakCannary的源码分析。
---------------------
【转载,仅作分享侵删】
作者:况众文
原文:https://blog.csdn.net/u014294681/article/details/86511451


1 个回复

倒序浏览
今天也要加油鸭
回复 使用道具 举报
您需要登录后才可以回帖 登录 | 加入黑马