精通安卓性能优化-第四章(五)

来源:互联网 发布:js object转array 编辑:程序博客网 时间:2024/06/05 04:06

引用

尽管释放内存是垃圾回收器的一个重要功能,它还可以做的更多,是一个完整的内存管理系统。每个写过Java代码的人都听过引用,一个对象怎样被引用或者不被引用。然而,很少有人知道引用的多种类型。实际上Java定义了4种类型的引用:
(1) Strong
(2) Soft
(3) Weak
(4) Phantom

Strong References

strong reference是Java开发者最熟悉的。创建这样一个引用在Java中随处可以看到,如Listing 4-18所示。实际上,他们是应用程序大多数时间使用到得。这里创建了两个strong reference,一个是Integer对象,另一个是BigInteger对象。

Listing 4-18 强引用

public void printTwoStrings (int n) {    BigInteger bi = BigInteger.valueOf(n);  // 强引用    Integer i = new Integer(n);   // 强引用    System.out.println(i.toString());    i = null;  // 新创建的Integer对象现在可以被GC回收    System.out.println(bi.toString());    bi = null; // BigInteger可能不可以被GC回收}

这里需要注意的一个重要事情是当把i设为null使Integer对像对GC可消除,而bi设为null不一定。因为BigInteger.valueOf()方法可能会返回一个预分配的对象(比如,BigInteger.ZERO),设置bi为null仅仅移除了BigInteger的一个强引用,但是同样一个对象的更强的引用可能依然存在。这个方法中还创建了两个strong reference,它们可能不如其他的明显:i.toString()的调用和bi.toString()的调用,每个都创建了String对象的强引用。

NOTE:严格的说,你需要知道Integer构造器的实现,保证没有其他strong reference指向新创建的Integer对象,这样保证设置i为null使得对象对GC来说是可消除的。

就像之前讨论的,保持对象的strong reference可能会导致内存泄露。我们已经合适的使用“strong reference”许多次,现在是时间去说Java不是定义了这么一个短语或者类。strong reference是“正常”的引用,被简单的叫做引用。

Soft, Weak, and Phantom References

Soft和weak reference在本质上是相似的,它们是不足以保持一个对象不被删除或者回收的引用。它们的不同在于GC尝试回收它们的程度。
一个对象是softly reachable,即存在一个soft引用而不是Strong引用,如果还有足够的空间去保持这个对象,当GC发生时会被留下。然而,如果GC决定需要回收更多的内存,它可以去回收soft reachable对象。这种类型的引用是cache首选的,可以自动的移除它的entry。

TIP:当使用一个cache,保证你理解它使用什么样的引用。比如,Android的LRUCache使用strong reference。

Weakly reachable对象,即一个对象存在一个weak引用而不是strong或者soft引用,可能在下次GC的时候立即被回收。换句话说,GC将更加侵略性的去回收weakly reachable对象。这个类型的引用是mappings的候选,当key不再被引用的时候,可以被自动的移除。WeakHashMap类就是为了这个目的。

NOTE:GC有多大的侵略性依赖于实际的实现。

Phantom引用是最弱的引用,很少被使用到。当你的应用需要知道一个对象什么时候被回收,需要在那个时间点执行一些清理,它是有用的。要确实有用的话,phantom引用需要被注册一个引用队列。
Soft,weak, phantom引用本身是实际的对象,提供其他对象的一个指向。比如,你可以创建一个phantom引用到一个soft引用到一个weak引用。实际上,你经常创建soft, weak, phantom应用到"strong"引用。Listing4-19给出了一个soft和weak引用的示例,每个联系着一个不同的引用队列。

Listing 4-19 引用和引用队列

private Integer strongRef;private SoftReference<Integer> softRef;private WeakReference<Integer> weakRef;private ReferenceQueue<Integer> softRefQueue = new ReferenceQueue<Integer>();private ReferenceQueue<Integer> weakRefQueue = new ReferenceQueue<Integer>();public void reset () {    strongRef = new Integer(1);    softRef = new SoftReference<Integer>(strongRef, softRefQueue);    weakRef = new WeakReference<Integer>(strongRef, weakRefQueue);}public void clearStrong () {    strongRef = null; // 没有了strong引用,但是weak和soft引用可能依然存在}public void clearSoft () {    softRef = null;  // 没有了soft引用,但是strong和weak引用可能仍然存在}public void clearWeak () {    weakRef = null;   // 没有了weak引用,但是strong和soft引用可能仍然存在}public void pollAndPrint() {    Reference<? extends Integer> r;    if ((r = softRefQueue.poll) != null) {        do {            Log.i(TAG, "Soft reference: " + r);        } while ((r = softRefQueue.poll()) != null);    } else {        Log.i(TAG, "Soft reference queue empty");    }    if ((r == weakRefQueue.poll()) != null) {        do {            Log.i(TAG, "Weak reference: " + r);        } while ((r = weakRefQueue.poll()) != null);    } else {        Log.i(TAG, "Weak reference queue empty");    }}public void gc () {    System.gc();}

这段代码为了看引用什么时候进入队列,这将怎么影响你的应用。要完全利用GC的内存管理能力,利用回收器内存管理的优势,理解引用很重要。当使用cache或者map的时候,你不需要尝试去实现一个相似的内存管理系统,。你想去达到的大多数数目标可能只需要认真的去使用垃圾回收器。

Garbage Collection

垃圾回收可以发生在不同的时间,当它发生的时候,你几乎无法控制。你可以通过调用System.gc()给Android一些暗示,但是最终Dalvik虚拟机决定gc实际发生的时间。五种情况促使gc发生,我将通过他们的Log信息提到他们,你可以通过logcat看到:
(1) GC_FOR_MALLOC:发生在当Heap不足以分配内存的时候,在内存分配可以被执行之前内存必须被回收
(2) GC_CONCURRENT: 当一个collection kicks in(可能是部分), 存在足够被回收的对象
(3) GC_EXPLICIT:当你调用System.gc()去显式的请求垃圾回收
(4) GC_EXTERNAL_ALLOC:在Honeycomb或者之后不会再发生(所有的东西会在heap分配)
(5) GC_HPROF_DUMP_HEAP:当创建一个HPROF文件的时候发生

Listing 4-20给出一些垃圾回收器的Log信息。

Listing 4-20 垃圾回收信息

GC_CONCURRENT freed 103K, 69% free 320K/1024K, external OK/OK, paused 1ms+1ms GC_EXPLICIT freed 2k, 55% free 2532K/5511K, external 1625K/2137K, paused 55ms

因为GC需要时间,减少你分配后马上释放的对象的数量可以提升性能。特别是在Android 2.2和更早的版本,因为GC发生在应用程序的主线程,可能导致严重的响应和性能问题。比如,实时游戏的一些帧可能会丢失,因为太多的时间用来做GC。从Android 2.3开始变得好起来,因为大多数的GC在一个单独的线程。GC发生的时候,仍然会影响主线程(5毫秒的暂停或者更少),但是比之前的Android版本少很多。一个完整的GC需要50毫秒是很正常的。供参考,一个30帧每秒的游戏需要33毫秒去渲染和显示每一帧,所以很容易可以看到为什么在Android 2.3之前的版本可能会出现问题。

APIS

安卓定义了几个API,可以用来去了解系统上多少内存是可用的,已经使用了多少:
(1) ActivityManager的getMemoryInfo()
(2) ActivityManager的getMemoryClass()
(3) ActivityManager的getLargeMemoryClass()
(4) Debug的dumpHprofData()
(5) Debug的getMemoryInfo()
(6) Debug的getNativeHeapAllocatedSize()
(7) Debug的getNativeHeapSize()

TIP: 在应用的manifest文件设置android:largeHeap为true,表示要使用大的heap。这个属性在Android 3.0引入。不能保证large heap会比regular heap大。你需要尽量去防止应用依赖于这个设置。

Listing 4-21给出了如何去使用两个getMemoryInfo()方法。

Listing 4-21 调用getMemoryInfo()

ActivityManager am = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);ActivityManager.MemoryInfo memInfo = new ActivityManager.MemoryInfo();am.getMemoryInfo(memInfo);// 使用memInfo的信息Debub.MemoryInfo debugMemInfo = new Debug.MemoryInfo();Debug.getMemoryInfo(debugMemInfo);// 使用debugMemInfo信息

Low Memory

你的应用不是孤立的。它需要去和许多其他的应用还有系统共享资源。结果,很多时候会存在没有足够的内存给每个应用,Android会要求应用和他的组件(比如Activity或者Fragement)去系紧他们的腰带。
CompoCallbacks接口定义了onLowMemory() API,对所有的应用组件是通用的。当它被调用,通常要求去释放组件不实际需要的对象。通常,onLowMemory()的实现将释放:
(1) Caches或者Cache entries(比如,LruCache因为它使用强引用)
(2) 可以在有需求的时候重新产生的Bitmap对象
(3) 看不到的Layout
(4) 数据库对象
需要注意重建对象耗时的。然而,不释放足够的内存可能导致安卓去杀掉进程,甚至你的应用。如果应用被杀掉,下次用户希望使用的时候需要从头开始启动。这样,你的应用需要释放尽量多的资源,因为不止有利于其他的应用同样还有本身。在你的代码中使用lazy初始化一是个好的习惯:允许你稍后实现onLowMemory()而不需要去修改其他代码。

Summary

在嵌入式设备中,内存是稀缺资源。尽管现在的手机和平板有越来越多的内存,这些设备同样运行越来越复杂的系统和应用。有效的使用内存不止可以允许你的应用运行在老的设备上,同样可以运行更快。记住,如果你给应用内存,他将要求更多。

0 0