成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

性能優(yōu)化(三)看完這篇文章,至少解決 APP 中 90 % 的內(nèi)存異常問(wèn)題

Elle / 1971人閱讀

摘要:不能滿足被回收的條件,盡管調(diào)用也還是不能得到回收這就造成了內(nèi)存泄漏。種解決單例中的內(nèi)存泄漏將引用置為銷毀監(jiān)聽使用弱引用將監(jiān)聽器放入弱引用中從弱引用中取出回調(diào)通過(guò)第七小點(diǎn)就能完美的解決單例中回調(diào)引起的內(nèi)存泄漏。

我們?yōu)槭裁匆獌?yōu)化內(nèi)存

在 Android 中我們寫的 .java 文件,最終會(huì)編譯成 .class 文件, class 又由類裝載器加載后,在 JVM 中會(huì)形成一份描述 class 結(jié)構(gòu)的元信息對(duì)象,通過(guò)該元信息對(duì)象可以知道 class 的結(jié)構(gòu)信息 (構(gòu)造函數(shù)、屬性、方法)等。JVM 會(huì)把描述類的數(shù)據(jù)從 class 文件加載到內(nèi)存,Java 有一個(gè)很好的管理內(nèi)存的機(jī)制,垃圾回收機(jī)制 GC 。為什么 Java 都給我們提供了垃圾回收機(jī)制,程序有時(shí)還會(huì)導(dǎo)致內(nèi)存泄漏,內(nèi)存溢出 OOM,甚至導(dǎo)致程序 Crash 。接下來(lái)我們就對(duì)實(shí)際開發(fā)中出現(xiàn)的這些內(nèi)存問(wèn)題,來(lái)進(jìn)行優(yōu)化。

JAVA 虛擬機(jī)

我們先來(lái)大概了解一下 Java 虛擬機(jī)里面運(yùn)行時(shí)的數(shù)據(jù)區(qū)域有哪些,如果想深入了解 Java 虛擬機(jī) 建議可以購(gòu)買<<深入理解 Java 虛擬機(jī)>> 或者直接點(diǎn)擊我這里的 PDF 版本 密碼: jmnf

線程獨(dú)占區(qū)

程序計(jì)數(shù)器

相當(dāng)于一個(gè)執(zhí)行代碼的指示器,用來(lái)確認(rèn)下一行執(zhí)行的地址

每個(gè)線程都有一個(gè)

沒有 OOM 的區(qū)

虛擬機(jī)棧

我們平時(shí)說(shuō)的棧就是這塊區(qū)域

java 虛擬機(jī)規(guī)范中定義了 OutOfMemeory , stackoverflow 異常

本地方法棧

java 虛擬機(jī)規(guī)范中定義了 OutOfMemory ,stackoverflow 異常

注意

在 hotspotVM 中把虛擬機(jī)棧和本地方法棧合為了一個(gè)棧區(qū)

線程共享區(qū)

方法區(qū)

ClassLoader 加載類信息

常量、靜態(tài)變量

編譯后的代碼

會(huì)出現(xiàn) OOM

運(yùn)行時(shí)常量池

public static final

符號(hào)引用類、接口全名、方法名

java 堆 (本次需要優(yōu)化的地方)

虛擬機(jī)能管理的最大的一塊內(nèi)存 GC 主戰(zhàn)場(chǎng)

會(huì)出現(xiàn) OOM

對(duì)象實(shí)例

數(shù)據(jù)的內(nèi)容

JAVA GC 如何確定內(nèi)存回收

隨著程序的運(yùn)行,內(nèi)存中的實(shí)例對(duì)象、變量等占據(jù)的內(nèi)存越來(lái)越多,如果不及時(shí)進(jìn)行回收,會(huì)降低程序運(yùn)行效率,甚至引發(fā)系統(tǒng)異常。

目前虛擬機(jī)基本都是采用可達(dá)性分析算法,為什么不采用引用計(jì)數(shù)算法呢?下面就說(shuō)說(shuō)引用計(jì)數(shù)法是如果統(tǒng)計(jì)所有對(duì)象的引用計(jì)數(shù)的,再對(duì)比可達(dá)性分析算法是如何解決引用計(jì)數(shù)算法的不足。下面就來(lái)看下這 2 個(gè)算法:

引用計(jì)數(shù)算法

每個(gè)對(duì)象有一個(gè)引用計(jì)數(shù)器,當(dāng)對(duì)象被引用一次則計(jì)數(shù)器加一,當(dāng)對(duì)象引用一次失效一次則計(jì)數(shù)器減一,對(duì)于計(jì)數(shù)器為 0 的時(shí)候就意味著是垃圾了,可以被 GC 回收。

下面通過(guò)一段代碼來(lái)實(shí)際看下

public class GCTest {
    private Object instace = null;

    public static void onGCtest() {
        //step 1
        GCTest gcTest1 = new GCTest();
        //step 2
        GCTest gcTest2 = new GCTest();
        //step 3
        gcTest1.instace = gcTest2;
        //step 4
        gcTest2.instace = gcTest1;
        //step 5
        gcTest1 = null;
        //step 6
        gcTest2 = null;

    }

    public static void main(String[] arg) {
        onGCtest();
    }
}

分析代碼

//step 1 gcTest1 引用 + 1 = 1
//step 2 gcTest2 引用 + 1 = 1
//step 3 gcTest1 引用 + 1 = 2
//step 4 gcTest2 引用 + 1 = 2
//step 5 gcTest1 引用 - 1 = 1
//step 6 gcTest2 引用 - 1 = 1

很明顯現(xiàn)在 2 個(gè)對(duì)象都不能用了都為 null 了,但是 GC 確不能回收它們,因?yàn)樗鼈儽旧淼囊糜?jì)數(shù)不為 0 。不能滿足被回收的條件,盡管調(diào)用 System.gc() 也還是不能得到回收, 這就造成了 內(nèi)存泄漏 。當(dāng)然,現(xiàn)在虛擬機(jī)基本上都不采用此方式。

可達(dá)性分析算法

從 GC Roots 作為起點(diǎn)開始搜索,那么整個(gè)連通圖中額對(duì)象邊都是活對(duì)象,對(duì)于 GC Roots 無(wú)法到達(dá)的對(duì)象便成了垃圾回收的對(duì)象,隨時(shí)可能被 GC 回收。

可以作為 GC Roots 的對(duì)象

虛擬機(jī)棧正在運(yùn)行使用的引用

靜態(tài)屬性 常量

JNI 引用的對(duì)象

GC 是需要 2 次掃描才回收對(duì)象,所以我們可以使用 finalize 去救活丟失的引用

 @Override
    protected void finalize() throws Throwable {
        super.finalize();
        instace = this;
    }

到了這里,相信大家已經(jīng)能夠弄明白這 2 個(gè)算法的區(qū)別了吧?反正對(duì)于對(duì)象之間循環(huán)引用的情況,引用計(jì)數(shù)算法無(wú)法回收這 2 個(gè)對(duì)象,而可達(dá)性是從 GC Roots 開始搜索,所以能夠正確的回收。

不同引用類型的回收狀態(tài)

強(qiáng)引用

Object strongReference = new Object()

如果一個(gè)對(duì)象具有強(qiáng)引用,那垃圾回收器絕不會(huì)回收它,當(dāng)內(nèi)存空間不足, Java 虛擬機(jī)寧愿拋出 OOM 錯(cuò)誤,使程序異常 Crash ,也不會(huì)靠隨意回收具有強(qiáng)引用的對(duì)象來(lái)解決內(nèi)存不足的問(wèn)題.如果強(qiáng)引用對(duì)象不再使用時(shí),需要弱化從而使 GC 能夠回收,需要:

strongReference = null; //等 GC 來(lái)回收

還有一種情況,如果:

public void onStrongReference(){
    Object strongReference = new Object()
}

在 onStrongReference() 內(nèi)部有一個(gè)強(qiáng)引用,這個(gè)引用保存在 java 棧 中,而真正的引用內(nèi)容 (Object)保存在 java 堆中。當(dāng)這個(gè)方法運(yùn)行完成后,就會(huì)退出方法棧,則引用對(duì)象的引用數(shù)為 0 ,這個(gè)對(duì)象會(huì)被回收。

但是如果 mStrongReference 引用是全局時(shí),就需要在不用這個(gè)對(duì)象時(shí)賦值為 null ,因?yàn)?強(qiáng)引用 不會(huì)被 GC 回收。

軟引用 (SoftReference)

如果一個(gè)對(duì)象只具有軟引用,則內(nèi)存空間足夠,垃圾回收器就不會(huì)回收它;如果內(nèi)存空間不足了,就會(huì)回收這些對(duì)象的內(nèi)存,只要垃圾回收器沒有回收它,該對(duì)象就可以被程序使用。軟引用可用來(lái)實(shí)現(xiàn)內(nèi)存敏感的高速緩存。

軟引用可以和一個(gè)引用隊(duì)列(ReferenceQueue)聯(lián)合使用,如果軟引用所引用的對(duì)象被垃圾回收器回收, java 虛擬機(jī)就會(huì)把這個(gè)軟引用加入到與之關(guān)聯(lián)的引用隊(duì)列中。

注意: 軟引用對(duì)象是在 jvm 內(nèi)存不夠的時(shí)候才會(huì)被回收,我們調(diào)用 System.gc() 方法只是起通知作用, JVM 什么時(shí)候掃描回收對(duì)象是 JVM 自己的狀態(tài)決定的。就算掃描到了 str 這個(gè)對(duì)象也不會(huì)回收,只有內(nèi)存不足才會(huì)回收。

弱引用 (WeakReference)

弱引用與軟引用的區(qū)別在于: 只具有弱引用的對(duì)象擁有更短暫的生命周期。在垃圾回收器線程掃描它所管轄的內(nèi)存區(qū)域的過(guò)程中,一旦發(fā)現(xiàn)了只具有弱引用的對(duì)象,不管當(dāng)前內(nèi)存空間足夠與否,都會(huì)回收它的內(nèi)存。不過(guò)由于垃圾回收器是一個(gè)優(yōu)先級(jí)很低的線程,因此不一定會(huì)很快發(fā)現(xiàn)那些只具有弱引用的對(duì)象。

弱引用可以和一個(gè)引用隊(duì)列聯(lián)合使用,如果弱引用所引用的對(duì)象被垃圾回收,Java 虛擬機(jī)就會(huì)把這個(gè)弱引用加入到與之關(guān)聯(lián)的引用隊(duì)列中。

可見 weakReference 對(duì)象的生命周期基本由 GC 決定,一旦 GC 線程發(fā)現(xiàn)了弱引用就標(biāo)記下來(lái),第二次掃描到就直接回收了。

注意這里的 referenceQueuee 是裝的被回收的對(duì)象。

虛引用 (PhantomReference)

    @Test
    public void onPhantomReference()throws InterruptedException{
        String str = new String("123456");
        ReferenceQueue queue = new ReferenceQueue();
        // 創(chuàng)建虛引用,要求必須與一個(gè)引用隊(duì)列關(guān)聯(lián)
        PhantomReference pr = new PhantomReference(str, queue);
        System.out.println("PhantomReference:" + pr.get());
        System.out.printf("ReferenceQueue:" + queue.poll());
    }

虛引用顧名思義,就是形同虛設(shè),與其他幾種引用都不同,虛引用并不會(huì)決定對(duì)象的生命周期。如果一個(gè)對(duì)象僅持有虛引用,那么它就和沒有任何引用一樣,在任何時(shí)候都可能被垃圾回收器回收。

虛引用主要用來(lái)跟蹤對(duì)象被垃圾回收器回收的活動(dòng)。虛引用與軟引用和弱引用的一個(gè)區(qū)別在于: 虛引用必須和引用隊(duì)列 (ReferenceQueue) 聯(lián)合使用。當(dāng)垃圾回收器準(zhǔn)備回收一個(gè)對(duì)象時(shí),如果發(fā)現(xiàn)它還有虛引用,就會(huì)在回收對(duì)象的內(nèi)存之前,把這個(gè)虛引用加入到與之關(guān)聯(lián)的引用隊(duì)列中。

總結(jié)

引用類型 調(diào)用方式 GC 是否內(nèi)存泄漏
強(qiáng)引用 直接調(diào)用 不回收
軟引用 .get() 視內(nèi)存情況回收
弱引用 .get() 回收 不可能
虛引用 null 任何時(shí)候都可能被回收,相當(dāng)于沒有引用一樣
分析內(nèi)存常用工具

工具很多,掌握原理方法,工具隨意挑選使用。

top/procrank meinfo Procstats DDMS MAT Finder - Activity LeakCanary LeakInspector 內(nèi)存泄漏

產(chǎn)生的原因: 一個(gè)長(zhǎng)生命周期的對(duì)象持有一個(gè)短生命周期對(duì)象的引用,通俗點(diǎn)講就是該回收的對(duì)象,因?yàn)橐脝?wèn)題沒有被回收,最終會(huì)產(chǎn)生 OOM。

下面我們來(lái)利用 Profile 來(lái)檢查項(xiàng)目是否有內(nèi)存泄漏

怎么利用 profile 來(lái)查看項(xiàng)目中是否有內(nèi)存泄漏

    在 AS 中項(xiàng)目以 profile 運(yùn)行

    在 MEMORY 界面中選擇要分析的一段內(nèi)存,右鍵 export

    Allocations: 動(dòng)態(tài)分配對(duì)象個(gè)數(shù)

    Deallocation: 解除分配的對(duì)象個(gè)數(shù)

    Total count: 對(duì)象的總數(shù)

    Shalow Size: 對(duì)象本身占用的內(nèi)存大小

    Retained Size: GC 回收能收走的內(nèi)存大小

    轉(zhuǎn)換 profile 文件格式

    將 export 導(dǎo)出的 dprof 文件轉(zhuǎn)換為 Mat 的 dprof 文件

    cd /d 進(jìn)入到 Android sdk/platform-tools/hprof-conv.exe

    //轉(zhuǎn)換命令 hprof-conv -z src des
    D:AndroidAndroidDeveloper-sdkandroid-sdk-windowsplatform-tools>hprof-conv -z D:	emp_	emp_6.hprof D:	emp_memory6.hprod
    

    下載 Mat 工具

    打開 MemoryAnalyzer.exe 點(diǎn)擊左上角 File 菜單中的 Open Heap Dupm

    查看內(nèi)存泄漏中的 GC Roots 強(qiáng)引用

    這里我們得知是一個(gè) ilsLoginListener 引用了 LoginView,我們來(lái)看下代碼最后怎么解決的。

    代碼中我們找到了 LoginView 這個(gè)類,發(fā)現(xiàn)是一個(gè)單例中的回調(diào)引起的內(nèi)存泄漏,下面怎么解決勒,請(qǐng)看第七小點(diǎn)。

    2種解決單例中的內(nèi)存泄漏

      將引用置為 null

      /**
           * 銷毀監(jiān)聽
           */
          public void unRemoveRegisterListener(){
              mMessageController.unBindListener();
          }
          public void unBindListener(){
              if (listener != null){
                  listener = null;
              }
          }
      

      使用弱引用

      //將監(jiān)聽器放入弱引用中
      WeakReference listenerWeakReference = new WeakReference<>(listener);
      
      //從弱引用中取出回調(diào)
      listenerWeakReference.get();
      

    通過(guò)第七小點(diǎn)就能完美的解決單例中回調(diào)引起的內(nèi)存泄漏。

Android 中常見的內(nèi)存泄漏經(jīng)典案例及解決方法

    單例

    示例 :

    public class AppManager {
    
        private static AppManager sInstance;
        private CallBack mCallBack;
        private Context mContext;
    
        private AppManager(Context context) {
            this.mContext = context;
        }
    
        public static AppManager getInstance(Context context) {
            if (sInstance == null) {
                sInstance = new AppManager(context);
            }
            return sInstance;
        }
        
        public void addCallBack(CallBack call){
            mCallBack = call;
        }
    }
    

      通過(guò)上面的單列,如果 context 傳入的是 Activity , Service 的 this,那么就會(huì)導(dǎo)致內(nèi)存泄漏。

      以 Activity 為例,當(dāng) Activity 調(diào)用 getInstance 傳入 this ,那么 sInstance 就會(huì)持有 Activity 的引用,當(dāng) Activity 需要關(guān)閉的時(shí)候需要 回收的時(shí)候,發(fā)現(xiàn) sInstance 還持有 沒有用的 Activity 引用,導(dǎo)致 Activity 無(wú)法被 GC 回收,就會(huì)造成內(nèi)存泄漏

      addCallBack(CallBack call) 這樣寫看起來(lái)是沒有毛病的。但是當(dāng)這樣調(diào)用在看一下勒。

      //在 Activity 中實(shí)現(xiàn)單例的回調(diào)
      AppManager.getInstance(getAppcationContext()).addCallBack(new CallBack(){
          @Override
          public void onStart(){
              
          }
      });
      

      這里的 new CallBack() 匿名內(nèi)部類 默認(rèn)持有外部的引用,造成 CallBack 釋放不了,那么怎么解決了,請(qǐng)看下面解決方法

    解決方法:

      getInstance(Context context) context 都傳入 Appcation 級(jí)別的 Context,或者實(shí)在是需要傳入 Activity 的引用就用 WeakReference 這種形式。

      匿名內(nèi)部類建議大家多帶帶寫一個(gè)文件或者

      public void addCallBack(CallBack call){
              WeakReference mCallBack= new WeakReference(call);
          }
      

    Handler

    示例:

    //在 Activity 中實(shí)現(xiàn) Handler
    class MyHandler extends Handler{
        private Activity m;
        public MyHandler(Activity activity){
            m=activity;
        }
    
    //    class.....
    }
    

    這里的 MyHandler 持有 activity 的引用,當(dāng) Activity 銷毀的時(shí)候,導(dǎo)致 GC 不會(huì)回收造成 內(nèi)存泄漏。

    解決方法:

    1.使用靜態(tài)內(nèi)部類 + 弱引用
    2.在 Activity onDestoty() 中處理  removeCallbacksAndMessages() 
        @Override
        protected void onDestroy() {
            super.onDestroy();
        if(null != handler){
              handler.removeCallbacksAndMessages(null);
              handler = null;
        }
     }
    

    靜態(tài)變量

    示例:

    public class MainActivity extends AppCompatActivity {
    
        private static Police sPolice;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            if (sPolice != null) {
                sPolice = new Police(this);
            }
        }
    }
    
    class Police {
        public Police(Activity activity) {
        }
    }
    

    這里 Police 持有 activity 的引用,會(huì)造成 activity 得不到釋放,導(dǎo)致內(nèi)存泄漏。

    解決方法:

    //1. sPolice 在 onDestory()中 sPolice = null;
    //2. 在 Police 構(gòu)造函數(shù)中 將強(qiáng)引用 to 弱引用;
    

    非靜態(tài)內(nèi)部類

    參考 第二點(diǎn) Handler 的處理方式

    匿名內(nèi)部類

    示例:

    public class MainActivity extends AppCompatActivity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
          	new Thread(){
                 @Override
                 public void run() {
                    super.run();
                            }
                        };
        }
    }
    

    很多初學(xué)者都會(huì)像上面這樣新建線程和異步任務(wù),殊不知這樣的寫法非常地不友好,這種方式新建的子線程ThreadAsyncTask都是匿名內(nèi)部類對(duì)象,默認(rèn)就隱式的持有外部Activity的引用,導(dǎo)致Activity內(nèi)存泄露。

    解決方法:

    //靜態(tài)內(nèi)部類 + 弱引用
    //多帶帶寫一個(gè)文件 + onDestory  = null;
    

    未取消注冊(cè)或回調(diào)

    示例:

    public class MainActivity extends AppCompatActivity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            registerReceiver(mReceiver, new IntentFilter());
        }
    
        private BroadcastReceiver mReceiver = new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                // TODO ------
            }
        };
    }
    

    在注冊(cè)觀察則模式的時(shí)候,如果不及時(shí)取消也會(huì)造成內(nèi)存泄露。比如使用Retrofit + RxJava注冊(cè)網(wǎng)絡(luò)請(qǐng)求的觀察者回調(diào),同樣作為匿名內(nèi)部類持有外部引用,所以需要記得在不用或者銷毀的時(shí)候取消注冊(cè)。

    解決方法:

    //Activity 中實(shí)現(xiàn) onDestory()反注冊(cè)廣播得到釋放
        @Override
        protected void onDestroy() {
            super.onDestroy();
            this.unregisterReceiver(mReceiver);
        }
    

    定時(shí)任務(wù)

    示例:

    public class MainActivity extends AppCompatActivity {
    
        /**模擬計(jì)數(shù)*/
        private int mCount = 1;
        private Timer mTimer;
        private TimerTask mTimerTask;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            init();
            mTimer.schedule(mTimerTask, 1000, 1000);
        }
    
        private void init() {
            mTimer = new Timer();
            mTimerTask = new TimerTask() {
                @Override
                public void run() {
                    MainActivity.this.runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            addCount();
                        }
                    });
                }
            };
        }
    
        private void addCount() {
          mCount += 1;
        }
    }
    

    當(dāng)我們Activity銷毀的時(shí),有可能Timer還在繼續(xù)等待執(zhí)行TimerTask,它持有Activity 的引用不能被 GC 回收,因此當(dāng)我們 Activity 銷毀的時(shí)候要立即cancelTimerTimerTask,以避免發(fā)生內(nèi)存泄漏。

    解決方法:

    //當(dāng) Activity 關(guān)閉的時(shí)候,停止一切正在進(jìn)行中的定時(shí)任務(wù),避免造成內(nèi)存泄漏。
        private void stopTimer() {
            if (mTimer != null) {
                mTimer.cancel();
                mTimer = null;
            }
            if (mTimerTask != null) {
                mTimerTask.cancel();
                mTimerTask = null;
            }
        }
    
        @Override
        protected void onDestroy() {
            super.onDestroy();
            stopTimer();
        }
    

    資源未關(guān)閉

    示例:

    ArrayList,HashMap,IO,File,SqLite,Cursor 等資源用完一定要記得 clear remove 等關(guān)閉一系列對(duì)資源的操作。
    

    解決方法:

    用完即刻銷毀
    

    屬性動(dòng)畫

    示例:

    動(dòng)畫同樣是一個(gè)耗時(shí)任務(wù),比如在 Activity 中啟動(dòng)了屬性動(dòng)畫 (ObjectAnimator) ,但是在銷毀的時(shí)候,沒有調(diào)用 cancle 方法,雖然我們看不到動(dòng)畫了,但是這個(gè)動(dòng)畫依然會(huì)不斷地播放下去,動(dòng)畫引用所在的控件,所在的控件引用 Activity ,這就造成 Activity 無(wú)法正常釋放。因此同樣要在Activity 銷毀的時(shí)候 cancel 掉屬性動(dòng)畫,避免發(fā)生內(nèi)存泄漏。
    

    解決方法:

    @Override
    protected void onDestroy() {
        super.onDestroy();
        //當(dāng)關(guān)閉 Activity 的時(shí)候記得關(guān)閉動(dòng)畫的操作
        mAnimator.cancel();
    }
    

    Android 源碼或者第三方 SDK

    示例:

    //如果在開發(fā)調(diào)試中遇見 Android 源碼或者 第三方 SDK 持有了我們當(dāng)前的 Activity 或者其它類,那么現(xiàn)在怎么辦了。
    

    解決方法:

    //當(dāng)前是通過(guò) Java 中的反射找到某個(gè)類或者成員,來(lái)進(jìn)行手動(dòng) = null 的操作。
    

內(nèi)存抖動(dòng) 什么是內(nèi)存抖動(dòng)

內(nèi)存頻繁的分配與回收,(分配速度大于回收速度時(shí)) 最終產(chǎn)生 OOM 。

也許下面的錄屏更能解釋什么是內(nèi)存抖動(dòng)

可以看出當(dāng)我點(diǎn)擊了一下 Button 內(nèi)存就頻繁的創(chuàng)建并回收(注意看垃圾桶)。

那么我們找出代碼中具體那一塊出現(xiàn)問(wèn)題了勒,請(qǐng)看下面一段錄屏

  
mButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                imPrettySureSortingIsFree();
            }
        });

/**
     * 排序后打印二維數(shù)組,一行行打印
     */
    public void imPrettySureSortingIsFree() {
        int dimension = 300;
        int[][] lotsOfInts = new int[dimension][dimension];
        Random randomGenerator = new Random();
        for (int i = 0; i < lotsOfInts.length; i++) {
            for (int j = 0; j < lotsOfInts[i].length; j++) {
                lotsOfInts[i][j] = randomGenerator.nextInt();
            }
        }
        


        for (int i = 0; i < lotsOfInts.length; i++) {
            String rowAsStr = "";
            //排序
            int[] sorted = getSorted(lotsOfInts[i]);
            //拼接打印
            for (int j = 0; j < lotsOfInts[i].length; j++) {
                rowAsStr += sorted[j];
                if (j < (lotsOfInts[i].length - 1)) {
                    rowAsStr += ", ";
                }
            }
            Log.i("ricky", "Row " + i + ": " + rowAsStr);
        }
    }

最后我們之后是 onClick 中的 imPrettySureSortingIsFree() 函數(shù)里面的 rowAsStr += sorted[j]; 字符串拼接造成的 內(nèi)存抖動(dòng) ,因?yàn)槊看纹唇右粋€(gè) String 都會(huì)申請(qǐng)一塊新的堆內(nèi)存,那么怎么解決這個(gè)頻繁開辟內(nèi)存的問(wèn)題了。其實(shí)在 Java 中有 2 個(gè)更好的 API 對(duì) String 的操作很友好,相信應(yīng)該有人猜到了吧。沒錯(cuò)就是將 此處的 String 換成 StringBuffer 或者 StringBuilder,就能很完美的解決字符串拼接造成的內(nèi)存抖動(dòng)問(wèn)題。

修改后

        /**
         * 打印二維數(shù)組,一行行打印
         */
        public void imPrettySureSortingIsFree() {
            int dimension = 300;
            int[][] lotsOfInts = new int[dimension][dimension];
            Random randomGenerator = new Random();
            for(int i = 0; i < lotsOfInts.length; i++) {
                for (int j = 0; j < lotsOfInts[i].length; j++) {
                    lotsOfInts[i][j] = randomGenerator.nextInt();
                }
            }

            // 使用StringBuilder完成輸出,我們只需要?jiǎng)?chuàng)建一個(gè)字符串即可,				不需要浪費(fèi)過(guò)多的內(nèi)存
            StringBuilder sb = new StringBuilder();
            String rowAsStr = "";
            for(int i = 0; i < lotsOfInts.length; i++) {
                // 清除上一行
                sb.delete(0, rowAsStr.length());
                //排序
                int[] sorted = getSorted(lotsOfInts[i]);
                //拼接打印
                for (int j = 0; j < lotsOfInts[i].length; j++) {
                    sb.append(sorted[j]);
                    if(j < (lotsOfInts[i].length - 1)){
                        sb.append(", ");
                    }
                }
                rowAsStr = sb.toString();
                Log.i("jason", "Row " + i + ": " + rowAsStr);
            }
        }

這里可以看見沒有垃圾桶出現(xiàn),說(shuō)明內(nèi)存抖動(dòng)解決了。

注意: 實(shí)際開發(fā)中如果在 LogCat 中發(fā)現(xiàn)有這些 Log 說(shuō)明也發(fā)生了 內(nèi)存抖動(dòng) (Log 中出現(xiàn) concurrent copying GC freed ....)

回收算法

ps:我覺得這個(gè)只是為了應(yīng)付面試,那么可以參考這里,我也只了解概念這里就不用在多寫了,點(diǎn)擊看這個(gè)帖子吧

也可以參考掘金的這一篇 GC 回收算法

標(biāo)記清除算法 Mark-Sweep 復(fù)制算法 Copying 標(biāo)記壓縮算法 Mark-Compact 分代收集算法 總結(jié) (只要養(yǎng)成這樣的習(xí)慣,至少可以避免 90 % 以上不會(huì)造成內(nèi)存異常)

    數(shù)據(jù)類型: 不要使用比需求更占用空間的基本數(shù)據(jù)類型

    循環(huán)盡量用 foreach ,少用 iterator, 自動(dòng)裝箱也盡量少用

    數(shù)據(jù)結(jié)構(gòu)與算法的解度處理 (數(shù)組,鏈表,棧樹,樹,圖)

    數(shù)據(jù)量千級(jí)以內(nèi)可以使用 Sparse 數(shù)組 (Key為整數(shù)),ArrayMap (Key 為對(duì)象) 雖然性能不如 HashMap ,但節(jié)約內(nèi)存。

    枚舉優(yōu)化

    缺點(diǎn):

    每一個(gè)枚舉值都是一個(gè)單例對(duì)象,在使用它時(shí)會(huì)增加額外的內(nèi)存消耗,所以枚舉相比與 Integer 和 String 會(huì)占用更多的內(nèi)存

    較多的使用 Enum 會(huì)增加 DEX 文件的大小,會(huì)造成運(yùn)行時(shí)更多的 IO 開銷,使我們的應(yīng)用需要更多的空間

    特別是分 Dex 多的大型 APP,枚舉的初始化很容易導(dǎo)致 ANR

    優(yōu)化后的代碼:可以直接限定傳入的參數(shù)個(gè)數(shù)

    public class SHAPE {
        public static final int TYPE_0=0;
        public static final int TYPE_1=1;
        public static final int TYPE_2=2;
        public static final int TYPE_3=3;
    
    
        @IntDef(flag=true,value={TYPE_0,TYPE_1,TYPE_2,TYPE_3})
        @Target({ElementType.PARAMETER,ElementType.METHOD,ElementType.FIELD})
        @Retention(RetentionPolicy.SOURCE)
        public @interface Model{
    
        }
    
        private @Model int value=TYPE_0;
        public void setShape(@Model int value){
            this.value=value;
        }
        @Model
        public int getShape(){
            return this.value;
        }
    }
    
    

    static , static final 的問(wèn)題

    static 會(huì)由編譯器調(diào)用 clinit 方法進(jìn)行初始化

    static final 不需要進(jìn)行初始化工作,打包在 dex 文件中可以直接調(diào)用,并不會(huì)在類初始化申請(qǐng)內(nèi)存

    基本數(shù)據(jù)類型的成員,可以全寫成 static final

    字符串的拼接盡量少用 +=

    重復(fù)申請(qǐng)內(nèi)存問(wèn)題

    同一個(gè)方法多次調(diào)用,如遞歸函數(shù) ,回調(diào)函數(shù)中 new 對(duì)象

    不要在 onMeause() onLayout() ,onDraw() 中去刷新UI(requestLayout)

    避免 GC 回收將來(lái)要重新使用的對(duì)象 (內(nèi)存設(shè)計(jì)模式對(duì)象池 + LRU 算法)

    Activity 組件泄漏

    非業(yè)務(wù)需要不要把 activity 的上下文做參數(shù)傳遞,可以傳遞 application 的上下文

    非靜態(tài)內(nèi)部類和匿名內(nèi)部?jī)?nèi)會(huì)持有 activity 引用(靜態(tài)內(nèi)部類 或者 多帶帶寫文件)

    單例模式中回調(diào)持有 activity 引用(弱引用)

    handler.postDelayed() 問(wèn)題

    如果開啟的線程需要傳入?yún)?shù),用弱引接收可解決問(wèn)題

    handler 記得清除 removeCallbacksAndMessages(null)

    Service 耗時(shí)操作盡量使用 IntentService,而不是 Service

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/7402.html

相關(guān)文章

  • 2017文章總結(jié)

    摘要:歡迎來(lái)我的個(gè)人站點(diǎn)性能優(yōu)化其他優(yōu)化瀏覽器關(guān)鍵渲染路徑開啟性能優(yōu)化之旅高性能滾動(dòng)及頁(yè)面渲染優(yōu)化理論寫法對(duì)壓縮率的影響唯快不破應(yīng)用的個(gè)優(yōu)化步驟進(jìn)階鵝廠大神用直出實(shí)現(xiàn)網(wǎng)頁(yè)瞬開緩存網(wǎng)頁(yè)性能管理詳解寫給后端程序員的緩存原理介紹年底補(bǔ)課緩存機(jī)制優(yōu)化動(dòng) 歡迎來(lái)我的個(gè)人站點(diǎn) 性能優(yōu)化 其他 優(yōu)化瀏覽器關(guān)鍵渲染路徑 - 開啟性能優(yōu)化之旅 高性能滾動(dòng) scroll 及頁(yè)面渲染優(yōu)化 理論 | HTML寫法...

    dailybird 評(píng)論0 收藏0
  • 2017文章總結(jié)

    摘要:歡迎來(lái)我的個(gè)人站點(diǎn)性能優(yōu)化其他優(yōu)化瀏覽器關(guān)鍵渲染路徑開啟性能優(yōu)化之旅高性能滾動(dòng)及頁(yè)面渲染優(yōu)化理論寫法對(duì)壓縮率的影響唯快不破應(yīng)用的個(gè)優(yōu)化步驟進(jìn)階鵝廠大神用直出實(shí)現(xiàn)網(wǎng)頁(yè)瞬開緩存網(wǎng)頁(yè)性能管理詳解寫給后端程序員的緩存原理介紹年底補(bǔ)課緩存機(jī)制優(yōu)化動(dòng) 歡迎來(lái)我的個(gè)人站點(diǎn) 性能優(yōu)化 其他 優(yōu)化瀏覽器關(guān)鍵渲染路徑 - 開啟性能優(yōu)化之旅 高性能滾動(dòng) scroll 及頁(yè)面渲染優(yōu)化 理論 | HTML寫法...

    hellowoody 評(píng)論0 收藏0
  • 2017文章總結(jié)

    摘要:歡迎來(lái)我的個(gè)人站點(diǎn)性能優(yōu)化其他優(yōu)化瀏覽器關(guān)鍵渲染路徑開啟性能優(yōu)化之旅高性能滾動(dòng)及頁(yè)面渲染優(yōu)化理論寫法對(duì)壓縮率的影響唯快不破應(yīng)用的個(gè)優(yōu)化步驟進(jìn)階鵝廠大神用直出實(shí)現(xiàn)網(wǎng)頁(yè)瞬開緩存網(wǎng)頁(yè)性能管理詳解寫給后端程序員的緩存原理介紹年底補(bǔ)課緩存機(jī)制優(yōu)化動(dòng) 歡迎來(lái)我的個(gè)人站點(diǎn) 性能優(yōu)化 其他 優(yōu)化瀏覽器關(guān)鍵渲染路徑 - 開啟性能優(yōu)化之旅 高性能滾動(dòng) scroll 及頁(yè)面渲染優(yōu)化 理論 | HTML寫法...

    wwolf 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<