【數據結構與算法之美】什麼是數組?引出jvm垃圾回收

目錄

一、爲什麼很多編程語言的數組都是從0開始編號的?

二、什麼是數組?

三、數組和鏈表的面試糾錯?

四、容器是否完全替代數組?

五、標記清除算法?

六、警惕數組的訪問越界問題

七、課後思考

1.  描述 Java 語言中 JVM 的標記清除垃圾回收算法。

1)判斷對象是否存活:

2)分代回收

3)可以作爲GC Roots的對象

4)JVM垃圾收集器分類:

2. 根據一維數組的內存尋址公式,寫出二維數組的內存尋址公式。


一、爲什麼很多編程語言的數組都是從0開始編號的?

1、從數組存儲的內存模型上來看,“下標”確切的說法就是一種“偏移”,相比從1開始編號,從0開始編號會少一次減法運算,數組作爲非常基礎的數組結構,通過下標隨機訪問元素又是非常基礎的操作,效率的優化就要儘可能的做到極致。
2、主要的原因是歷史原因,C語言的設計者是從0開始計數數組下標的,之後的Java、JS等語言都進行了效仿,或者說是爲了減少從C轉向Java、JS等的學習成本。

二、什麼是數組?

數組是一個線性數據結構,用一組連續的內存空間存儲一組具有相同類型的數據。
其實數組、鏈表、棧、隊列都是線性表結構;樹、圖則是非線性表結構。

三、數組和鏈表的面試糾錯?

1、數組中的元素存在一個連續的內存空間中,而鏈表中的元素可以不存在於連續的內存空間。
2、數組支持隨機訪問,根據下標隨機訪問的時間複雜度是O(1);鏈表適合插入、刪除操作,時間複雜度爲O(1)。

四、容器是否完全替代數組?

容器的優勢:對於Java語言,容器封裝了數組插入、刪除等操作的細節,並且支持動態擴容。
對於Java,一些更適合用數組的場景:
1、Java的ArrayList無法存儲基本類型,需要進行裝箱操作,而裝箱與拆箱操作都會有一定的性能消耗,如果特別注意性能,或者希望使用基本類型,就可以選用數組。
2、若數組大小事先已知,並且對數組只有非常簡單的操作,不需要使用到ArrayList提供的大部分方法,則可以直接使用數組。
3、多維數組時,使用數組會更加直觀。

五、標記清除算法?

GC最基礎的收集算法就是標記-清除算法,如同他們的名字一樣,此算法分爲“標記”、“清除”兩個階段,先標記出需要回收的對象,再統一回收標記的對象。不足有二,一是效率不高,二是產生碎片內存空間。

六、警惕數組的訪問越界問題

C 語言代碼的運行結果分析

int main(int argc, char* argv[]){
    int i = 0;
    int arr[3] = {0};
    for(; i<=3; i++){
        arr[i] = 0;
        printf("hello world\n");
    }
    return 0;
}

函數體內的局部變量存在棧上,且是連續壓棧。在Linux進程的內存佈局中,棧區在高地址空間,從高向低增長。變量i和arr在相鄰地址,且i比arr的地址大,所以arr越界正好訪問到i。當然,前提是i和arr元素同類型,否則那段代碼仍是未決行爲。

這段代碼無限循環原因有二,以及一個附加條件:

1. 函數體內的局部變量存在棧上,且是連續壓棧, 棧空間從高往低依次分配,i佔4字節,接着arr佔12字節,內存從高往低是這樣:存i的4字節|arr[2]|arr[1]|arr[0],數組訪問是通過“baseAddr+index乘typeSize”得到,算下來當index=3時,剛好是i的地址
2. 這裏剛好滿足字節對齊,系統爲64位系統,字長64,那麼字節對齊必須是8字節的倍數,剛好i變量和arr變量佔了16字節,對齊了;如果這裏將arr[3]改爲arr[4],爲了對齊,內存從高往低是這樣:存i的4字節|空4字節|arr[3]|arr[2]|arr[1]|arr[0],那麼arr[4]剛好是空的4字節,無法影響到i的值,則並不會無限循環
3.附加條件:編譯時gcc默認會自動添加越界保護,此處要達到無限循環效果,編譯時需加上-fno-stack-protector去除該保護

七、課後思考

1.  描述 Java 語言中 JVM 的標記清除垃圾回收算法。

解答:

1)判斷對象是否存活:

採用可達性分析算法來判斷對象是否存活,會遍歷所有 GC ROOTS,將所有 GC ROOTS 可達的對象標記爲存活

2)分代回收

(1)年輕代:複製算法,8:1:1的比例eden區和兩個survivor(survivor0,survivor1)區;

(2)老年代:標記整理

  1. 標記階段:首先進行標記;
  2. 清除階段:將存活的對象往一端移動,最後直接清除另一段端空間,不會造成對應內存碎片問題
  3. 不足:標記和清理效率都不高,少量垃圾產生時纔會高效;

3)可以作爲GC Roots的對象

  1. 虛擬機(棧幀中的本地變量表)中引用的對象
  2. 方法區中類靜態屬性引用的對象
  3. 方法區中常量引用的對象
  4. 本地方法棧中JNI(即一般說的native方法)中引用的對象

4)JVM垃圾收集器分類:

(1)新生代

Serial (第一代)

PraNew (第二代)

Parallel Scavenge (第三代)

G1收集器(第四代) JDK1.7後

(2)老年代

Serial Old (第一代)

Parallel Old (第二代)

CMS (第三代)

G1收集器 (第四代) JDK1.7後

2. 根據一維數組的內存尋址公式,寫出二維數組的內存尋址公式。

解答:

一維數組:a[i]_address=base_address+i*type_size
二維數組:二維數組假設是m*n, a[i][j]_address=base_address + (i*n+j)*type_size
三維數組:三維數組假設是m*n*q, a[i][j][k]_address=base_address + (i*n*q + j*q + k)*type_size

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