性能優(yōu)化(一)堆內(nèi)存分析

題圖.png
題圖.png

前言

通過Android Studio的Memory Monitor工具拌屏,對各種數(shù)據(jù)類型罚攀,如:boolean党觅,int,float斋泄,long杯瞻,SparseArray,HashMap等在內(nèi)存的占用情況進行分析是己。對一些特定場景下的代碼編寫又兵,如:String拼接,OnClickListener等所消耗的內(nèi)存情況進行分析卒废。通過分析沛厨,更好的了解了不同情況下堆內(nèi)存是如何分配的,也確切驗證了以往諸多的代碼經(jīng)驗摔认,為高效合理的利用內(nèi)存奠定基礎(chǔ)逆皮。

Memory Monitor的基本使用

  • 新建MainActivity,啟動APP
public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}
  • 在 Android Monitor -> Monitors -> Memory 中参袱,點擊"initiate GC"电谣,先手動GC一次秽梅,把沒用的內(nèi)存進行回收。


    step1_gc.png
    step1_gc.png
  • 點擊"Dump Java Heap"剿牺,生成.hprof(hprof文件為特定時間點企垦,Java進程的內(nèi)存快照)


    step2_dump.png
    step2_dump.png

以下是根據(jù).hprof文件生成的內(nèi)存分析表,本文主要關(guān)注Shallow Size和Retained Size晒来,其他column含義可以參考官方-HPROF Viewer and Analyzer

heap_nothing.png
heap_nothing.png

Shallow Size和Retained Size

Shallow Size:對象自身占用的內(nèi)存大小钞诡,不包括它引用的對象
Retained Size:對象自身占用的內(nèi)存大小,加上它直接或間接引用的對象大小
Dominating Size:管轄的內(nèi)存大小湃崩,大部分情況和Retained一致

shalow_and_retain.png
shalow_and_retain.png

因為可以通過GC Roots直接訪問荧降,所以左圖的obj3不是藍色節(jié)點;而右圖卻是藍色攒读,因為它已經(jīng)被包含在 Retained size 中朵诫。

Shallow Size Retained Size(左) Retained Size(右)
obj1 obj1 obj1+obj2+obj4 obj1+obj2+obj3+obj4
obj2 obj2 obj2+obj4 obj2+obj3+obj4

案例分析

如圖heap_nothing.png,在MainActivity在新建的時候薄扁,初始占用內(nèi)存1776(以下案例分析基于紅米note3機型)剪返。

  • case 1:空對象TestModel+未初始化。
public class TestModel {
}

public class MainActivity extends AppCompatActivity {
    private TestModel mModel;
    ...onCreate()
}
case1_TestModel.png
case1_TestModel.png

只定義TestModel成員變量的情況下泌辫,內(nèi)存占用1780=初始內(nèi)存+引用類型(4)随夸。所以在項目發(fā)版前,要把一些沒有使用到的變量都清理一遍震放,積少成多,免得造成內(nèi)存浪費驼修。

  • case 2:空對象TestModel+初始化殿遂。
public class MainActivity extends AppCompatActivity {
    private TestModel mModel = new TestModel();
    ...onCreate()
}
case2_TestModel.png
case2_TestModel.png

內(nèi)存占用1788=case1+類信息(8),說明調(diào)用new時乙各,即使是空對象墨礁,也需要8字節(jié)左右的堆空間用于描述該對象的類信息《停基于Java是在new的時候才去申請堆空間的特性恩静,在開發(fā)中,可以考慮對象的延遲初始化蹲坷,養(yǎng)成個好習(xí)慣驶乾,在使用到的時候才去new。

  • case3:TestModel以局部變量的方式進行定義循签。
public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        TestModel mModel = new TestModel();
    }
}
case3_TestModel.png
case3_TestModel.png

內(nèi)存占用未變化级乐,還是初始值1776,說明局部變量生命周期只存在于方法內(nèi)部县匠,方法結(jié)束后风科,即可被gc回收撒轮。除非必須,能使用局部變量的情況贼穆,就避免定義成員變量题山。

  • case4:boolean基礎(chǔ)類型。
public class MainActivity extends AppCompatActivity {
   private boolean mBoolean;
    ...onCreate()
}
case4_boolean.png
case4_boolean.png

內(nèi)存占用1777=初始狀態(tài)+1故痊,說明基礎(chǔ)類型boolean的引用類型占用1字節(jié)臀蛛。

  • case5:Boolean封裝類型。
public class MainActivity extends AppCompatActivity {
   private Boolean mBoolean;
    ...onCreate()
}
case5_Boolean.png
case5_Boolean.png

內(nèi)存占用1780=初始狀態(tài)+4崖蜜,裝箱類型Boolean本質(zhì)上也是一個對象浊仆,由case1可以推導(dǎo)出引用類型占用4字節(jié)。

  • case6:Boolean封裝類型+初始化豫领。
public class MainActivity extends AppCompatActivity {
   private Boolean mBoolean = new Boolean(true);
    ...onCreate()
}
case6_Boolean.png
case6_Boolean.png
Boolean_source.png
Boolean_source.png

內(nèi)存占用1789=case5+9抡柿,如圖,Boolean的源碼中有個boolean基礎(chǔ)類型的字段value等恐,當調(diào)用"new Boolean(true)"的時候洲劣,根據(jù)case2可以推導(dǎo),類描述信息8字節(jié)课蔬,根據(jù)case4可以推導(dǎo)囱稽,value基礎(chǔ)類型占用1字節(jié),所以總共增加9字節(jié)二跋。

同理战惊,可以推導(dǎo)出以下表格:

boolean/byte short/char int/float/String/引用類型/數(shù)組引用 long/double/類信息
內(nèi)存占用 1 2 4 8
  • case7:TestModel內(nèi)部類。
public class MainActivity extends AppCompatActivity {
    private TestModel mModel = new TestModel();
    ...onCreate()
    public class TestModel {
    }
}
case7_TestModel.png
case7_TestModel.png

占用內(nèi)存1792=case1(1780)+類信息(8)+this引用(4)扎即。

  • case8:TestModel靜態(tài)內(nèi)部類吞获。
public class MainActivity extends AppCompatActivity {
    private TestModel mModel = new TestModel();
    ...onCreate()
    public static class TestModel {
    }
}
case8_TestModel.png
case8_TestModel.png

占用內(nèi)存1788=case1(1780)+類信息(8),靜態(tài)內(nèi)部類由于沒有外部類的匿名this引用谚鄙,少占用4字節(jié)各拷。

  • case9:HashMap和SparseArray的對比。
public class MainActivity extends AppCompatActivity {
    private Map<Integer, Integer> mMap = new HashMap<>();
    private SparseArray<Integer> mSparseArray = new SparseArray();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        for (int i = 0; i < 1000; i++) {
            mMap.put(i, i);
            mSparseArray.put(i, i);
        }
    }
}
case9_map.png
case9_map.png

各添加1000條數(shù)據(jù)闷营,HashMap占用53168烤黍,SparseArray占用18653,說明使用SparseArray替代HashMap更節(jié)省內(nèi)存傻盟。

  • case10:OnClickListener三種寫法的對比速蕊。從節(jié)省內(nèi)存的角度考慮,通過方式3接口回調(diào)設(shè)置OnClickListener為最優(yōu)莫杈。

寫法1:匿名類

public class MainActivity extends AppCompatActivity {
    private Button mButton;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mButton = (Button) findViewById(R.id.button);
        mButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(MainActivity.this, "hello", Toast.LENGTH_SHORT).show();
            }
        });
    }
}
case10_1_listener.png
case10_1_listener.png

內(nèi)存占用=MainActivity(1780)+MainActivity$1(12)=1792互例。

寫法2:成員變量類

public class MainActivity extends AppCompatActivity {
    private Button mButton;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mButton = (Button) findViewById(R.id.button);
        mButton.setOnClickListener(mOnClickListener);
    }

    private View.OnClickListener mOnClickListener = new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            Toast.makeText(MainActivity.this, "hello", Toast.LENGTH_SHORT).show();
        }
    };
}
case10_2_listener.png
case10_2_listener.png

內(nèi)存占用=MainActivity(1784,包含4字節(jié)的成員變量)+MainActivity$1(12)=1796筝闹。

寫法3:接口回調(diào)

public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    private Button mButton;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mButton = (Button) findViewById(R.id.button);
        mButton.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        Toast.makeText(MainActivity.this, "hello", Toast.LENGTH_SHORT).show();
    }
}
case10_3_listener.png
case10_3_listener.png

內(nèi)存占用=1780(減少1個成員變量媳叨,避免通過new創(chuàng)建新的對象腥光,內(nèi)存占用最少)。

  • case11:String的初始化糊秆。

case11_1:

public class MainActivity extends AppCompatActivity {
   private String mStr = "aaaaa";
    ...onCreate()
}
case11_1_String.png
case11_1_String.png

case11_2:

public class MainActivity extends AppCompatActivity {
   private String mStr = new String("aaaaa");
    ...onCreate()
}
case11_2_String.png
case11_2_String.png
  • "aaaaa"這個String為何占用26字節(jié)武福?按以上方式分析,至少占用內(nèi)存30=類信息(8)+count(4)+hashCode(4)+char[]引用(4)+char[]數(shù)組(10)痘番,為何少了4字節(jié)捉片?

  • 直接賦值的方式會將"aaaaa"加入到字符串常量池,不占用堆空間汞舱;而case11_2的內(nèi)存占用為 1806=case11_1+26伍纫,說明通過new String方式創(chuàng)建的字符串會在堆內(nèi)存開辟空間。

  • case12:String的拼接昂芜。

case12_1:基于case11_1莹规,作字符串"+"拼接。

public class MainActivity extends AppCompatActivity {
   private String mStr = "aaaaa";
   protected void onCreate(Bundle savedInstanceState) {
       ...
      mStr += "c";
   }
}
case12_1_String.png
case12_1_String.png

可以發(fā)現(xiàn)泌神,拼接后內(nèi)存占用1808=case11_1(1780)+28良漱,而這28的空間正好是"aaaaac"的內(nèi)存大小,也就是說在"+"拼接的時候欢际,產(chǎn)生了一個臨時的變量用于存儲"aaaaac"的結(jié)果母市,并賦值給mStr。印證了《Effective in Java》的第51條中所說"由于字符串不可變损趋,當2個字符串被連接在一起時患久,他們的內(nèi)容都要被拷貝"。同時在淺談StringBuilder這篇文章中也講到了"+"拼接的時候舶沿,會轉(zhuǎn)化為StringBuilder墙杯,再通過toString創(chuàng)建一個新的String對象。

case12_2:用StringBuilder進行字符串拼接括荡。

case12_2_1:初始化1個空的StringBuilder

public class MainActivity extends AppCompatActivity {
  private StringBuilder mStringBuilder = new StringBuilder();
    ...onCreate()
}
case12_2_1_StringBuilder.png
case12_2_1_StringBuilder.png

一個空的StringBuilder就占49字節(jié),類信息(8)+count(4)+shared(1)+value引用(4)+value[]數(shù)組(32)=49溉旋。value這個字符數(shù)組占用了32字節(jié)畸冲,而我們最多也就添加"aaaaac"6個字符,所以這里可以通過new StringBuilder(6)初始化字符數(shù)組的大小观腊,避免浪費邑闲。

case12_2_2:使用StringBuilder進行"aaaaa"+"c"的字符串拼接。

public class MainActivity extends AppCompatActivity {
    private StringBuilder mStringBuilder = new StringBuilder(6);
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ...
        mStringBuilder.append("aaaaa");
        mStringBuilder.append("c");
    }
}
case12_2_2_StringBuilder.png
case12_2_2_StringBuilder.png

首先在StringBuilder初始化的時候設(shè)置了字符數(shù)組大小為6梧油,所以StringBuilder的初始內(nèi)存占用就變小了苫耸,而在完成append("aaaaa"),append("c")之后儡陨,只要當前字符數(shù)組的容量夠用褪子,就不會繼續(xù)擴容量淌,避免了String拼接時,內(nèi)存浪費的問題嫌褪。當然前提是控制好StringBuilder的char[]初始容量呀枢,不然擴容后也會空余一些閑置內(nèi)存。

總結(jié)

1.謹慎創(chuàng)建成員變量:不管有用沒用笼痛,非基礎(chǔ)類型的成員變量只要定義了裙秋,至少需要4字節(jié),基礎(chǔ)類型成員變量占用大小各不一樣缨伊。盡量使用局部變量摘刑,縮短變量生命周期,促使GC更快回收刻坊。
2.謹慎new:如case2的TestModel枷恕,不管該對象是否為空,至少8字節(jié)的類信息占用紧唱。如case10的Listener活尊,盡量避免不必要的new÷┮妫考慮對象的延遲初始化蛹锰,只有真正使用的時候才new。
3.除非必要绰疤,否則盡量使用基礎(chǔ)類型铜犬,避免使用裝箱類型。
4.少用內(nèi)部類:內(nèi)部類如果不需要訪問到外部類的成員時轻庆,可以抽取成獨立外部類癣猾,或加static,減少一個this引用(4字節(jié))余爆,也可以避免內(nèi)存泄漏纷宇。
5.使用google推薦的數(shù)據(jù)集合類型SparseArray,ArrayMap替代HashMap蛾方。
6.從節(jié)省內(nèi)存的角度考慮像捶,通過接口回調(diào)的方式設(shè)置OnClickListener為最優(yōu)。
7.通過StringBuilder替代String進行字符串拼接桩砰,最好預(yù)先設(shè)置好StringBuilder的容量拓春。

參考

官方-HPROF Viewer and Analyzer

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市亚隅,隨后出現(xiàn)的幾起案子硼莽,更是在濱河造成了極大的恐慌,老刑警劉巖煮纵,帶你破解...
    沈念sama閱讀 219,490評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件懂鸵,死亡現(xiàn)場離奇詭異偏螺,居然都是意外死亡,警方通過查閱死者的電腦和手機矾瑰,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,581評論 3 395
  • 文/潘曉璐 我一進店門砖茸,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人殴穴,你說我怎么就攤上這事凉夯。” “怎么了采幌?”我有些...
    開封第一講書人閱讀 165,830評論 0 356
  • 文/不壞的土叔 我叫張陵劲够,是天一觀的道長。 經(jīng)常有香客問我休傍,道長征绎,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,957評論 1 295
  • 正文 為了忘掉前任磨取,我火速辦了婚禮人柿,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘忙厌。我一直安慰自己凫岖,他們只是感情好,可當我...
    茶點故事閱讀 67,974評論 6 393
  • 文/花漫 我一把揭開白布逢净。 她就那樣靜靜地躺著哥放,像睡著了一般。 火紅的嫁衣襯著肌膚如雪爹土。 梳的紋絲不亂的頭發(fā)上甥雕,一...
    開封第一講書人閱讀 51,754評論 1 307
  • 那天,我揣著相機與錄音胀茵,去河邊找鬼社露。 笑死,一個胖子當著我的面吹牛琼娘,可吹牛的內(nèi)容都是我干的呵哨。 我是一名探鬼主播,決...
    沈念sama閱讀 40,464評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼轨奄,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了拒炎?” 一聲冷哼從身側(cè)響起挪拟,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎击你,沒想到半個月后玉组,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體谎柄,經(jīng)...
    沈念sama閱讀 45,847評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,995評論 3 338
  • 正文 我和宋清朗相戀三年惯雳,在試婚紗的時候發(fā)現(xiàn)自己被綠了朝巫。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,137評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡石景,死狀恐怖劈猿,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情潮孽,我是刑警寧澤揪荣,帶...
    沈念sama閱讀 35,819評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站往史,受9級特大地震影響仗颈,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜椎例,卻給世界環(huán)境...
    茶點故事閱讀 41,482評論 3 331
  • 文/蒙蒙 一挨决、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧订歪,春花似錦脖祈、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,023評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至掏秩,卻和暖如春或舞,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背蒙幻。 一陣腳步聲響...
    開封第一講書人閱讀 33,149評論 1 272
  • 我被黑心中介騙來泰國打工映凳, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人邮破。 一個月前我還...
    沈念sama閱讀 48,409評論 3 373
  • 正文 我出身青樓诈豌,卻偏偏與公主長得像,于是被迫代替她去往敵國和親抒和。 傳聞我的和親對象是個殘疾皇子矫渔,可洞房花燭夜當晚...
    茶點故事閱讀 45,086評論 2 355

推薦閱讀更多精彩內(nèi)容

  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法,類相關(guān)的語法摧莽,內(nèi)部類的語法庙洼,繼承相關(guān)的語法,異常的語法,線程的語...
    子非魚_t_閱讀 31,644評論 18 399
  • (一)Java部分 1油够、列舉出JAVA中6個比較常用的包【天威誠信面試題】 【參考答案】 java.lang;ja...
    獨云閱讀 7,111評論 0 62
  • 引子和思路 runloop是每次循環(huán)就會渲染屏幕上所有的點蚁袭,當我們在做一個table上顯示很多張圖片時,拖動tab...
    黑白灰的綠i閱讀 1,477評論 2 5
  • 剛到昆明沒幾天删性,趁周末閑著,去曲靖看一位好朋友厦章,時間有些趕镇匀,買了站票,擠上了火車袜啃,在擁堵的人群里汗侵,我來回切換中英文...
    不四小姐閱讀 1,481評論 0 0
  • 下一個春天,再不想錯過第一縷帶有暖意的風(fēng)群发,第一抹開始變成亮黃色的陽光晰韵,第一個不再清冷的傍晚 ...
    Sunny1101閱讀 142評論 0 0