Dalvik虛擬機內存碎片測試分析

《移動App性能評測與優化》第一章1.3.3介紹了優化Dalvik內存碎片。文中列舉了一段代碼,可能會在GC後引起內存碎片問題,代碼如下:

private Object result[] = new Object[100];
void fool(){
    for (int i = 0;i < 100; i++){
        byte[] tmp = new byte[2000];
        result[i] = new byte[4];
    }
}

文中大意是:result[]對象數組每一個對象在分配內存之前都會先分配一個臨時的byte[[2000],因此實際給result[]對象數組每一個對象分配的byte[4]所在的內存地址是不連續的。當GC之後,臨時對象tmp申請的內存被釋放,留下了碎片化的內存。

提出問題:

1.GC之前result[]數組中每個成員的內存地址分佈情況如何?

2.Dalvik GC會對內存碎片做整理麼?

3.GC之後result[]數組中每個成員的內存地址分佈情況如何?

準備工作:

Android Studio 3.2.1

Android Device Monitor,工具所在目錄:Android\Sdk\tools\lib\monitor-x86_64\monitor.exe

[Memory Analyzer] (https://www.eclipse.org/mat/downloads.php)

程序編碼:

package com.peterzhang.oom;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
public class MainActivity extends AppCompatActivity {
    private Object result[] = new Object[10000];
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    public void buttonOnclick(View view){
        testMemoryFragmentation();
    }

    private void testMemoryFragmentation(){
        for (int i = 0;i < 10000; i++){
            byte[] tmp = new byte[2000];
            result[i] = new byte[4];
        }
    }
}

點擊運行,通過DeviceMonitor觀察內存分配狀態
beforeallocate.png

Heap Allocated 大小爲12.558M

通過DeviceMonitor左上角 "Dump HPROF File"導出此時的內存信息二進制文件a.hprof

此時生成的hprof文件還無法直接通過MAT工具打開需要利用AppData\Local\Android\Sdk\hprof-conv 工具轉換格式,命令如下:

hprof-conv  C:\Users\112106101\Desktop\mat\nexus4-android4.0.3\a.hprof C:\Users\112106101\Desktop\mat\nexus4-android4.0.3\a-standard.hprof

備註:此時導出來的二進制內存文件除了包含app自身佔用的內存,還包括了App共享的系統資源佔用的內存。如果期望只過濾出App自身的內存可以使用如下命令:

hprof-conv -z exclude non-app C:\Users\112106101\Desktop\mat\nexus4-android4.0.3\1.hprof C:\Users\112106101\Desktop\mat\nexus4-android4.0.3\1-android.hprof

然後打開MAT工具,選擇open a-standard.hprof

2.png

可以看到,MainActivity一共佔用了42312byte

點擊“分配對象”按鈕,執行testMemoryFragmentation方法,通過DeviceMonitor觀察內存分配狀態
afterallocate.png

Heap Allocated 大小爲12.786M

同樣導出HPROF內存文件,並通過MAT工具打開:
3.png

可以看到此時MainActivity一共佔用了201920byte

執行testMemoryFragmentation方法,並GC之後,內存Heap Allocated增加了0.228M,大概是220k

MainActivity佔用內存增加了159608byte,約等於160k,小於DeviceMonitor顯示的內存增加數據220kk。

監測工具 對象分配前後增加內存
DeviceMonitor 220k
MAT 160k

增加的內存主要是因爲result[]的內存申請

4.png

觀察result[]數組內存分配,發現內存地址不是連續的,莫非是GC之後是存在了內存碎片?

驗證:去掉代碼

byte[] tmp = new byte[2000];

會不會就不存在內存碎片呢?
測試後數據如圖所示:
5.png

從圖上的數據觀察到此時依然有內存碎片。

那麼我們就無法下結論這裏看到的內存碎片是由於代碼引起的。

byte[] tmp = new byte[2000];

什麼是內存碎片?

內存需要像硬盤一樣定期清理碎片麼?當然!內存碎片分爲內部碎片和外部碎片。例如

HotSpot VM的自動內存管理系統要求對象起始地址必須是8字節的整數倍,換句話說,就是對象的大小必須是8字節的整數倍。而對象頭部分正好是8字節的倍數(1倍或者2倍),因此,當對象實例數據部分沒有對齊時,就需要通過對齊填充來補全.。

這部分對齊填充的內存就屬於內部碎片。外部碎片更容易理解,GC發生後會出現空閒的內存不連續,這部分就是外部碎片。

從MAT分析的result[]對象內存數據可以看到,每一個result[i]佔用的內存是16byte,對應的代碼是

result[i] = new byte[4];

這裏16byte是如何得到的呢?

對象在內存中存儲佈局可以分爲3塊區域:對象頭、實例數據和對齊填充。如果對象是一個數組,那麼在對象頭中還必須有一塊用於記錄數組長度的數據。《深入理解JAVA虛擬機》

我們來分析一下result[i]佔用的內存空間:

類型 大小
對象頭 8byte
數組大小 4byte
實例數據 4byte
對齊填充 4byte

因此每一個result[]對象就佔用了4byte的內部碎片。

除了內部碎片,從MAT分析的內存數據看到,還存在一些外部碎片。這也是上述測試數據中MAT分析的result[]申請對象160k,但是實際上HeapAllocated增加了220k的原因。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章