乡下人产国偷v产偷v自拍,国产午夜片在线观看,婷婷成人亚洲综合国产麻豆,久久综合给合久久狠狠狠9

  • <output id="e9wm2"></output>
    <s id="e9wm2"><nobr id="e9wm2"><ins id="e9wm2"></ins></nobr></s>

    • 分享

      【原創(chuàng)】Android 系統(tǒng)穩(wěn)定性

       老匹夫 2014-02-22

       文章都為原創(chuàng),轉(zhuǎn)載請(qǐng)注明出處,未經(jīng)允許而盜用者追究法律責(zé)任。 

      很久之前寫的了,留著有點(diǎn)浪費(fèi),共享之。 

      編寫者:李文棟

       

      2.1.1 什么是內(nèi)存溢出

       

      2.1.2 為什么會(huì)有內(nèi)存溢出

      Android 主要應(yīng)用在嵌入式設(shè)備當(dāng)中,而嵌入式設(shè)備由于一些眾所周知的條件限制,通常都不會(huì)有很高的配置,特別是內(nèi)存比較有限。如果我們編寫的代碼當(dāng)中有太多的對(duì)內(nèi)存使用不當(dāng)?shù)牡胤?,難免會(huì)使得我們的設(shè)備運(yùn)行緩慢,甚至是死機(jī)。為了能夠使系統(tǒng)安全且快速的運(yùn)行,Android 的每個(gè)應(yīng)用程序都運(yùn)行在單獨(dú)的進(jìn)程中,這個(gè)進(jìn)程是由 Zygote 進(jìn)程孵化出來的,每個(gè)應(yīng)用進(jìn)程中都有且僅有一個(gè)虛擬機(jī)實(shí)例。如果程序在運(yùn)行過程中出現(xiàn)了內(nèi)存泄漏的問題,只會(huì)影響自己的進(jìn)程,不會(huì)直接影響其他進(jìn)程。

      Java雖然有自己的垃圾回收機(jī)制,但并不是說用Java編寫的程序就不會(huì)內(nèi)存溢出了。Java程序運(yùn)行在虛擬機(jī)中,虛擬機(jī)初始化時(shí)會(huì)設(shè)定它的堆內(nèi)存的上限值,在Android中這個(gè)上限值默認(rèn)是“16m”,而你可以根據(jù)實(shí)際的硬件配置來調(diào)整這個(gè)上限值,調(diào)整的方法是在系統(tǒng)啟動(dòng)時(shí)加載的某個(gè)配置文件中設(shè)置一個(gè)系統(tǒng)屬性:

      dalvik.vm.heapsize=24m

      當(dāng)然也可以設(shè)置成更大的值(例如“32m”)。這樣Android中每個(gè)應(yīng)用進(jìn)程的DalvikVM實(shí)例的堆內(nèi)存上限值就變成了24MB,也就是說一個(gè)應(yīng)用進(jìn)程中可以同時(shí)存在更多的Java數(shù)據(jù)對(duì)象了。有一些大型的應(yīng)用程序(例如游戲)運(yùn)行時(shí)需要比較多的內(nèi)存,heapsize太小的話根本無法運(yùn)行,此時(shí)就需要考慮調(diào)整heapsize的大小了。heapsize的大小是同時(shí)對(duì)整個(gè)系統(tǒng)生效的,原生代碼中無法單獨(dú)的調(diào)整某一個(gè)Java進(jìn)程的heapsize(除非我們自己修改源碼,不過我們從來沒這么做過)。

      當(dāng)代碼中的缺陷造成內(nèi)存泄漏時(shí),泄漏的內(nèi)存無法在虛擬機(jī)GC的時(shí)候被釋放,因?yàn)檫@些內(nèi)存被一些數(shù)據(jù)對(duì)象占用著,而這些數(shù)據(jù)對(duì)象之所以沒有被釋放,可以歸結(jié)為兩類情況:

      a) 被強(qiáng)引用著

      例如被一個(gè)正在運(yùn)行的線程、一個(gè)類中的static變量強(qiáng)引用著,或者當(dāng)前對(duì)象被注冊(cè)進(jìn)了framework中的一些接口中。

      b) JNI中的指針引用著

      Framework中的一些類經(jīng)常會(huì)在Java層創(chuàng)建一個(gè)對(duì)象,同時(shí)也在C++層創(chuàng)建一個(gè)對(duì)象,然后通過JNI讓這兩個(gè)對(duì)象相互引用(保存對(duì)方的地址),BinderProxy對(duì)象就是一個(gè)很典型的例子,在這種情況下,Java層的對(duì)象同樣不會(huì)被釋放。

      當(dāng)泄漏的內(nèi)存隨著程序的運(yùn)行越來越多時(shí),最終就會(huì)達(dá)到heapsize設(shè)定的上限值,此時(shí)虛擬機(jī)就會(huì)拋出OutOfMemoryError錯(cuò)誤,內(nèi)存溢出了。

      2.2 容易引起內(nèi)存泄漏的常見問題

      2.2.1 Cursor對(duì)象未正確關(guān)閉

      關(guān)于此類問題其實(shí)已經(jīng)是老生常談了,但是由于Android應(yīng)用源碼中的缺陷和使用的場合比較復(fù)雜,所以還是會(huì)時(shí)常出現(xiàn)這類問題。

      1. 問題舉例

      Cursor cursor = getContentResolver().query(...);

              if (cursor.moveToNext()) {

              ... ...

      }

      2. 問題修正

      Cursor cursor = null;

      try {

              cursor = getContentResolver().query(...);

              if (cursor != null && cursor.moveToNext()) {

              ... ...

              }

      } catch (Exception e) {

              ... ...

      } finally {

              if (cursor != null) {

                      cursor.close();

              }

      }

      3. 引申內(nèi)容

      (1) 實(shí)際在使用的時(shí)候代碼的邏輯通常會(huì)比上述示例要復(fù)雜的多,但總的原則是一定要在使用完畢Cursor以后正確的關(guān)閉。

      (2) 如果你的Cursor需要在Activity的不同的生命周期方法中打開和關(guān)閉,那么一般可以這樣做:

      onCreate()中打開,在onDestroy()中關(guān)閉;

      onStart() 中打開,在onStop() 中關(guān)閉;

      onResume()中打開,在onPause() 中關(guān)閉;

      即要在成對(duì)的生命周期方法中打開/關(guān)閉。

      (3) 如果程序中使用了CursorAdapter(例如Music),那么可以使用它的changeCursor(Cursor cursor)方法同時(shí)完成關(guān)閉舊Cursor使用新Cursor的操作。

      (4) 至于在cursor.close時(shí)需不需要try...catchcursor非空時(shí)),其實(shí)在close時(shí)做的工作就是釋放資源,包括通過Binder跨進(jìn)程注銷ContentObserver時(shí)已經(jīng)捕獲了RemoteException異常,所以其實(shí)可以不用try...catch

      (5) 關(guān)于deactiveclose,deactive不等同于close,看他們的API comments就能知道,如果deactive了一個(gè)Cursor,說明以后還是會(huì)用到它(利用requery方法),這個(gè)Cursor會(huì)釋放一部分資源,但是并沒有完全釋放;如果確認(rèn)不再使用這個(gè)Cursor了,一定要close。

      (6)除了Cursor有時(shí)我們也會(huì)對(duì)Database對(duì)象做操作,例如要修正MediaProvider中的一個(gè)attachVolume方法,在每次檢測到attach的是一個(gè)externalvolume時(shí)就重新建立一個(gè)數(shù)據(jù)庫,而不是采用以前的,那么在remove舊的數(shù)據(jù)庫對(duì)象的時(shí)候不要忘記關(guān)閉它。<!-- 第6點(diǎn)關(guān)于Database是否考慮去掉 -->

      4. 影響范圍

      如果沒有關(guān)閉Cursor,在測試次數(shù)足夠多的情況下,就會(huì)出現(xiàn):

      (1) 內(nèi)存泄漏

      我們先簡單的看一下Cursor的結(jié)構(gòu),這樣會(huì)更好理解。數(shù)據(jù)庫操作涉及到服務(wù)端的ContentProvider和客戶端程序,客戶端通常會(huì)通過ContentResolver.query函數(shù)查詢并獲取一個(gè)結(jié)果集的Cursor對(duì)象。而這個(gè)Cursor對(duì)象實(shí)際上也只是一個(gè)代理,因?yàn)橐紤]到客戶端和服務(wù)端在不同進(jìn)程的情況,所以Cursor的使用本身也是利用了Binder機(jī)制的,而客戶端和服務(wù)端的數(shù)據(jù)共享是利用共享內(nèi)存來實(shí)現(xiàn)的,如下圖所示。



       

      客戶端和服務(wù)端使用的Cursor經(jīng)過了層層封裝,顯得十分臃腫,但它們的工作其實(shí)可以簡單的從控制流和數(shù)據(jù)流兩個(gè)方面來看。在控制流方面,客戶端為了能和遠(yuǎn)端的服務(wù)端通信,使用實(shí)現(xiàn)了IBulkCursor接口的BulkCursorProxyCusorToBulkCursorAdapter對(duì)象,例如要獲取結(jié)果集數(shù)據(jù)時(shí),客戶端通過BulkCursoryProxy.onMove函數(shù)調(diào)用到CursorToBulkCursorAdapter.onMove函數(shù),然后再調(diào)用到SQLiteCursor.onMove函數(shù)來填充數(shù)據(jù)的。在數(shù)據(jù)流方面,服務(wù)端的SQLiteCursor將從數(shù)據(jù)庫中查詢到的結(jié)果集寫入到共享內(nèi)存中,然后Binder調(diào)用返回到客戶端,客戶端就可以從共享內(nèi)存中獲取到想要的數(shù)據(jù)了??蛻舳说目刂屏骱蛿?shù)據(jù)流的訪問由BulkCursorToCursorAdapter負(fù)責(zé),服務(wù)端則是分別由CursorToBulkCursorAdapterSQLiteCursor負(fù)責(zé)。

      如果Cursor沒有正常關(guān)閉,那么客戶端和服務(wù)端的CursorWindow對(duì)象和申請(qǐng)的那塊共享內(nèi)存都不會(huì)被回收,盡管其他相關(guān)的Java對(duì)象可能由于沒有強(qiáng)引用而被回收,但是真正占用內(nèi)存的通常是存放結(jié)果集數(shù)據(jù)的共享內(nèi)存。大量的Cursor沒有關(guān)閉的話,你可能會(huì)看到以下類型的異常信息:

      • 創(chuàng)建新的Java對(duì)象時(shí)發(fā)現(xiàn)沒有足夠的內(nèi)存,拋出內(nèi)存溢出錯(cuò)誤:OutOfMemoryError

      • 創(chuàng)建新的CursorWindow時(shí)無法申請(qǐng)到足夠的內(nèi)存,可能的異常信息有:
        RuntimeException: No memory for native window object
        IllegalStateException: Couldn't init cursor window
        CursorWindow heap allocation failed
        failed to create the CursorWindow heap

      (2) 文件描述符泄漏

      當(dāng)然有可能很幸運(yùn),每次查詢的結(jié)果集都很小,做幾千次查詢都不會(huì)內(nèi)存溢出,但是AndroidLinux內(nèi)核還有另外一個(gè)限制,就是文件描述符的上限,這個(gè)上限默認(rèn)是1024

      文件描述符本身是一個(gè)整數(shù),用來表示每一個(gè)被進(jìn)程所打開的文件和Socket,第一個(gè)打開的文件是0,第二個(gè)是1,依此類推。而Linux給每個(gè)進(jìn)程能打開的文件數(shù)量設(shè)置了一個(gè)上限,可以使用命令“ulimit -n”查看。另外,操作系統(tǒng)還有一個(gè)系統(tǒng)級(jí)的限制。

      每次創(chuàng)建一個(gè)Cursor對(duì)象,都會(huì)向內(nèi)核申請(qǐng)創(chuàng)建一塊共享內(nèi)存,這塊內(nèi)存以文件形式提供給應(yīng)用進(jìn)程,應(yīng)用進(jìn)程會(huì)獲得這個(gè)文件的描述符,并將其映射到自己的進(jìn)程空間中。如果有大量的Cursor對(duì)象沒有正常關(guān)閉,可想而知就會(huì)有大量的共享內(nèi)存的文件描述符無法關(guān)閉,同時(shí)再加上應(yīng)用進(jìn)程中的其他文件描述符,就很容易達(dá)到1024這個(gè)上限,一旦達(dá)到,進(jìn)程就掛掉了。



      提示:可以到系統(tǒng)的“/proc/進(jìn)程號(hào)/fd”目錄中查看進(jìn)程所有的文件描述符。

       

      (3) GREF has increased to 2001

      先說明一下“死亡代理”的概念。利用Binder做進(jìn)程間通信時(shí),允許對(duì)Binder的客戶端代理設(shè)置一個(gè)DeathRecipient對(duì)象,它只有一個(gè)名為binderDied的函數(shù)。當(dāng)Binder的服務(wù)端進(jìn)程死掉了,binder驅(qū)動(dòng)會(huì)通知客戶端進(jìn)程,最終回調(diào)DeathRecipient對(duì)象的binderDied函數(shù),客戶端進(jìn)程可以借此做一些清理工作。

      需要注意的是,“死亡代理”的概念只對(duì)進(jìn)程間通信有效,對(duì)進(jìn)程內(nèi)通信沒有意義;另外,Binder的客戶端和服務(wù)端的概念是相對(duì)的,例如BulkCursorProxyCursorToBulkCursorAdapter的客戶端,而后者又有一個(gè)IContentObserver的客戶端,其對(duì)應(yīng)的服務(wù)端在BulkCursorToCursorAdaptergetObserver函數(shù)中創(chuàng)建。這里需要關(guān)注的就是在CursorToBulkCursorAdapter對(duì)象被創(chuàng)建時(shí),會(huì)同時(shí)將該對(duì)象注冊(cè)為IContentObserver的客戶端對(duì)象的“死亡代理”,代碼如下:

      CursorToBulkCursorAdaptor的內(nèi)部類ContentObserverProxy的構(gòu)造函數(shù)中

      public ContentObserverProxy(IContentObserver remoteObserver, DeathRecipient recipient) {

              super(null);

              mRemote = remoteObserver;

              try {

                      //此處的recipient就是CursorToBulkCursorAdapter對(duì)象

                      remoteObserver.asBinder().linkToDeath(recipient, 0);

              } catch (RemoteException e) {

              }

      }

       

      死亡代理”對(duì)象的引用會(huì)被Native層的Binder代理對(duì)象的mObituaries集合引用,所以“死亡代理”對(duì)象及其關(guān)聯(lián)對(duì)象由于被強(qiáng)引用而不會(huì)被垃圾回收掉,同時(shí)JNI在實(shí)現(xiàn)linkToDeath函數(shù)的過程中也創(chuàng)建了一些具有全局性的引用,被稱作“Global Reference(簡寫為GREF)”,每一個(gè)GREF都會(huì)被記錄到虛擬機(jī)中維護(hù)的一個(gè)“全局引用表”中。

      eng模式下,JNI全局引用計(jì)數(shù)(GREF)有一個(gè)上限值為2000,如果大量Cursor對(duì)象沒有被正常關(guān)閉,服務(wù)端進(jìn)程就會(huì)因?yàn)椤八劳龃怼睂?duì)象的創(chuàng)建使得虛擬機(jī)中的全局引用計(jì)數(shù)增多,當(dāng)超過2000時(shí),虛擬機(jī)就會(huì)拋出異常,導(dǎo)致進(jìn)程掛掉,典型的異常信息就是“GREF has increased to 2001”。



      提示:全局引用計(jì)數(shù)的上限2000已經(jīng)是一個(gè)比較大的值,正常情況下很難達(dá)到。Androideng模式下開啟這項(xiàng)檢查,就是為了能夠在開發(fā)階段發(fā)現(xiàn)Native層的內(nèi)存泄漏問題。在usr模式下這項(xiàng)檢查會(huì)被禁用,此時(shí)如果有內(nèi)存泄漏就只有等到拋出內(nèi)存溢出錯(cuò)誤或者文件描述符超出上限等其他異常時(shí)才能發(fā)現(xiàn)了。

      Cursor未正常關(guān)閉是導(dǎo)致GREF越界的原因之一,后續(xù)會(huì)在其他章節(jié)中詳細(xì)討論。

      2.2.2 釋放對(duì)象的引用

      內(nèi)存的問題是Bugzilla中的???,經(jīng)常會(huì)在不經(jīng)意間遺留一些對(duì)象沒有釋放或銷毀。

      1. 靜態(tài)成員變量

      有時(shí)因?yàn)橐恍┰?/span>(比如希望節(jié)省Activity初始化時(shí)間等),將一些對(duì)象設(shè)置為static的,比如:

      private static TextView mTv;

      ... ...

      mTv = (TextView) findViewById(...);

      而且沒有在Activity退出時(shí)釋放mTv的引用,那么此時(shí)mTv本身,和與mTv相關(guān)的那個(gè)Activity的對(duì)象也不會(huì)在GC時(shí)被釋放掉,Activity強(qiáng)引用的其他對(duì)象也無法被釋放掉,這樣就造成了內(nèi)存泄漏。如果沒有充分的理由,或者不能夠清楚的控制這樣做帶來的影響,請(qǐng)不要這樣寫代碼。

      2. 正確注冊(cè)/注銷監(jiān)聽器對(duì)象

      經(jīng)常要用到一些XxxListener對(duì)象,或者是XxxObserver、XxxReceiver對(duì)象,然后用registerXxx方法注冊(cè),用unregisterXxx方法注銷。本身用法也很簡單,但是從一些實(shí)際開發(fā)中的代碼來看,仍然會(huì)有一些問題:

      (1) registerXxxunregisterXxx方法的調(diào)用通常也和Cursor的打開/關(guān)閉類似,在Activity的生命周期中成對(duì)的出現(xiàn)即可:

      onCreate() register,在 onDestroy() unregitster

      onStart() register,在 onStop() unregitster

      onResume() register,在 onPause() unregitster;

      (2) 忘記unregister

      以前看到過一段代碼,在Activity中定義了一個(gè)PhoneStateListener的對(duì)象,將其注冊(cè)到TelephonyManager中:

      TelephonyManager.listen(lPhoneStateListener.LISTEN_SERVICE_STATE);

      但是在Activity退出的時(shí)候注銷掉這個(gè)監(jiān)聽,即沒有調(diào)用以下方法:

      TelephonyManager.listen(lPhoneStateListener.LISTEN_NONE);

      因?yàn)?/span>PhoneStateListener的成員變量callback,被注冊(cè)到了TelephonyRegistry中,TelephonyRegistry是后臺(tái)的一個(gè)服務(wù)會(huì)一直運(yùn)行著。所以如果不注銷,則callback對(duì)象無法被釋放,PhoneStateListener對(duì)象也就無法被釋放,最終導(dǎo)致Activity對(duì)象無法被釋放。

      3. 適當(dāng)?shù)氖褂?/strong>SoftReference、WeakReference

      如果要寫一個(gè)緩存之類的類(例如圖片緩存),建議使用SoftReference,而不要直接用強(qiáng)引用,例如:

      private final ConcurrentHashMap<Long, SoftReference<Bitmap>> mBitmapCache = new ConcurrentHashMap<Long, SoftReference<Bitmap>>();

      當(dāng)加載的圖片過多,應(yīng)用可用堆內(nèi)存不足的時(shí)候,就可以自動(dòng)的釋放這些緩存的Bitmap對(duì)象。

      關(guān)于Java中的強(qiáng)引用、軟引用、弱引用和虛引用是一些比較重要的概念,在Android開發(fā)中經(jīng)常會(huì)用到。

      2.2.3 構(gòu)造 Adapter 時(shí),沒有使用緩存的 convertView

      以構(gòu)造 ListView BaseAdapter 為例,在 BaseAdapter 中提供了以下方法:

      public View getView(int positionView convertView,ViewGroup parent)

      來向 ListView 提供每一個(gè) item 所需要的 view 對(duì)象。初始時(shí) ListView 會(huì)從 BaseAdapter 中根據(jù)當(dāng)前的屏幕布局實(shí)例化一定數(shù)量的 view 對(duì)象,同時(shí) ListView 會(huì)將這些 view 對(duì)象緩存起來 。當(dāng)向上滾動(dòng)ListView 時(shí),原先位于最上面的 list item view 對(duì)象會(huì)被回收,然后被用來構(gòu)造新出現(xiàn)的最下面的 listitem。這個(gè)構(gòu)造過程就是由 getView()方法完成的,getView()的第二個(gè)形參 View convertView 就是被緩存起來的 list item view 對(duì)象(初始化時(shí)緩存中沒有 view對(duì)象則 convertView null)。由此可以看出,如果我們不去使用 convertView,而是每次都在 getView()中重新實(shí)例化一個(gè) View 對(duì)象的話,即浪費(fèi)資源也浪費(fèi)時(shí)間,也會(huì)使得內(nèi)存占用越來越大ListView 回收listitem view 對(duì)象的過程可以查看:android.widget.AbsListView類中的addScrapView(View scrap) 方法。

      示例代碼:

      public View getView(int positionView convertView,ViewGroup parent) {

              View view = new Xxx(...);

              ... ...

              return view;

      }

      修正示例代碼:

      public View getView(int positionView convertView,ViewGroup parent) {

              View view = null;

              if (convertView != null) {

                      view = convertView;

                      populate(view,getItem(position));

                      ...

              } else {

                      view = new Xxx(...);

                      ...

              }

              return view;

      }

      2.2.4 Bitmap 對(duì)象不再使用時(shí)調(diào)用 recycle()釋放內(nèi)存

      有時(shí)我們會(huì)自己操作 Bitmap 對(duì)象,如果一個(gè) Bitmap 對(duì)象比較占內(nèi)存,當(dāng)它不再被使用的時(shí)候,可以調(diào)用 Bitmap.recycle()方法回收此對(duì)象的像素所占用的內(nèi)存,但這不是必須的 ,視情況而定??梢钥匆幌麓a中的注釋:



      /**

      * Free up the memory associated with this bitmap's pixels,and mark the

      * bitmap as "dead"meaning it will throw an exception if getPixels() or

      * setPixels() is called,and will draw nothing. This operation cannot be

      * reversedso it should only be called if you are sure there are no

      * further uses for the bitmap. This is an advanced call,and normally need

      * not be called,since the normal GC process will free up this memory when

      * there are no more references to this bitmap.

      */

       

        本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
        轉(zhuǎn)藏 分享 獻(xiàn)花(0

        0條評(píng)論

        發(fā)表

        請(qǐng)遵守用戶 評(píng)論公約

        類似文章 更多