Android 面試準備進行曲-Java基礎篇

語言: CN / TW / HK

虛擬機器 基礎

jvm 參考文章

JVM記憶體管理

JVM執行Java程式的過程:Java原始碼檔案(.java)會被Java編譯器編譯為位元組碼檔案(.class),然後JVM中的類載入器載入各個類的位元組碼檔案,載入完畢之後,交由JVM執行引擎執行。

1.webp

執行時資料區被分為 執行緒私有資料區執行緒共享資料區 兩大類:

執行緒私有資料區包含:程式計數器、虛擬機器棧、本地方法棧 執行緒共享資料區包含:Java堆、方法區(內部包含常量池)

執行緒私有資料區包含:

  • 程式計數器:是當前執行緒所執行的位元組碼的行號指示器
  • 虛擬機器棧:是Java方法執行的記憶體模型
  • 本地方法棧:是虛擬機器使用到的Native方法服務

執行緒共享資料區包含:

  • Java堆:用於存放幾乎所有的物件例項和陣列;是垃圾收集器管理的主要區域,也被稱做“GC堆”;是Java虛擬機器所管理的記憶體中最大的一塊

  • 方法區:用於儲存已被虛擬機器載入的類資訊、常量、靜態變數、即時編譯器編譯後的程式碼等資料;Class檔案中除了有類的版本、欄位、方法、介面等描述資訊外,還有一項資訊是常量池(Constant Pool Table),用於存放編譯期生成的各種字面量和符號引用,這部分內容將在類載入後進入方法區的執行時常量池中存放

Java堆和棧的區別

  • 堆記憶體 用於儲存Java中的物件和陣列,當我們new一個物件或者建立一個數組的時候,就會在堆記憶體中開闢一段空間給它,用於存放。特點: 先進先出,後進後出。堆可以動態地分配記憶體大小,由於要在執行時動態分配記憶體,存取速度較慢。

  • 棧記憶體 主要是用來執行程式用的,比如:基本型別的變數和物件的引用變數。特點:先進後出,後進先出,存取速度比堆要快,僅次於暫存器,棧資料可以共享,但缺點是,存在棧中的資料大小與生存期必須是確定的,缺乏靈活性

垃圾回收機制/ 回收演算法

判定物件可回收有兩種方法:

  • 引用計數演算法:給物件中新增一個引用計數器,每當有一個地方引用它時,計數器值就加1;當引用失效時,計數器值就減1;任何時刻計數器為0的物件就是不可能再被使用的。然而在主流的Java虛擬機器裡未選用引用計數演算法來管理記憶體,主要原因是它難以解決物件之間相互迴圈引用的問題,所以出現了另一種物件存活判定演算法。

  • 可達性分析法:通過一系列被稱為『GC Roots』的物件作為起始點,從這些節點開始向下搜尋,搜尋所走過的路徑稱為引用鏈,當一個物件到GC Roots沒有任何引用鏈相連時,則證明此物件是不可用的。其中可作為GC Roots的物件:虛擬機器棧中引用的物件,主要是指棧幀中的本地變數、本地方法棧中Native方法引用的物件、方法區中類靜態屬性引用的物件、方法區中常量引用的物件

回收演算法

分代收集演算法:是當前商業虛擬機器都採用的一種演算法,根據物件存活週期的不同,將Java堆劃分為新生代和老年代,並根據各個年代的特點採用最適當的收集演算法。

  • 新生代:多數物件死去,少量存活。使用『複製演算法』,只需複製少量存活物件即可。
  • 複製演算法:把可用記憶體按容量劃分為大小相等的兩塊,每次只使用其中的一塊。當這一塊的記憶體用盡後,把還存活著的物件『複製』到另外一塊上面,再將這一塊記憶體空間一次清理掉。

  • 老年代:物件存活率高。使用『標記—清理演算法』或者『標記—整理演算法』,只需標記較少的回收物件即可。

    • 標記-清除演算法:首先『標記』出所有需要回收的物件,然後統一『清除』所有被標記的物件。
    • 標記-整理演算法:首先『標記』出所有需要回收的物件,然後進行『整理』,使得存活的物件都向一端移動,最後直接清理掉端邊界以外的記憶體。

參考文章

Java基礎

Java 引用型別

  • 強引用(StrongReference):具有強引用的物件不會被GC;即便記憶體空間不足,JVM寧願丟擲OutOfMemoryError使程式異常終止,也不會隨意回收具有強引用的物件。

  • 軟引用(SoftReference):具有軟引用的物件,會在記憶體空間不足的時候被GC;軟引用常用來實現記憶體敏感的快取記憶體。

  • 弱引用(WeakReference):被弱引用關聯的物件,無論當前記憶體是否足夠都會被GC;強度比軟引用更弱,常用於描述非必需物件;常用於解決記憶體洩漏的問題(Handle 中Context 部分)

  • 虛引用(PhantomReference):僅持有虛引用的物件,在任何時候都可能被GC;常用於跟蹤物件被GC回收的活動;必須和引用佇列 (ReferenceQueue)聯合使用,當垃圾回收器準備回收一個物件時,如果發現它還有虛引用,就會在回收物件的記憶體之前,把這個虛引用加入到與之關聯的引用佇列中。

類載入機制

類載入機制:是虛擬機器把描述類的資料從Class檔案載入到記憶體,並對資料進行校驗、轉換解析和初始化,形成可被虛擬機器直接使用的Java型別的過程。另外,型別的載入、連線和初始化過程都是在程式執行期完成的,從而通過犧牲一些效能開銷來換取Java程式的高度靈活性。主要階段:

  • 載入(Loading):通過類的全限定名來獲取定義此類的二進位制位元組流;將該二進位制位元組流所代表的靜態儲存結構轉化為方法區的執行時資料結構,該資料儲存資料結構由虛擬機器實現自行定義;在記憶體中生成一個代表這個類的java.lang.Class物件,它將作為程式訪問方法區中的這些型別資料的外部介面

  • 驗證(Verification):確保Class檔案的位元組流中包含的資訊符合當前虛擬機器的要求,包括檔案格式驗證、元資料驗證、位元組碼驗證和符號引用驗證

  • 準備(Preparation):為類變數分配記憶體,因為這裡的變數是由方法區分配記憶體的,所以僅包括類變數而不包括例項變數,後者將會在物件例項化時隨著物件一起分配在Java堆中;設定類變數初始值,通常情況下零值

  • 解析(Resolution):虛擬機器將常量池內的符號引用替換為直接引用的過程

  • 初始化(Initialization):是類載入過程的最後一步,會開始真正執行類中定義的Java位元組碼。而之前的類載入過程中,除了在『載入』階段使用者應用程式可通過自定義類載入器參與之外,其餘階段均由虛擬機器主導和控制

記憶體模型

22.webp 主記憶體是所有變數的儲存位置,每條執行緒都有自己的工作記憶體,用於儲存被該執行緒使用到的變數的主記憶體副本拷貝。為了獲取更好的執行速度,虛擬機器可能會讓工作記憶體優先儲存於暫存器和快取記憶體中。

併發過程中的原子性 時序性

  • 原子性

可直接保證的原子性變數操作有:read、load、assign、use、store和write,因此可認為基本資料型別的訪問讀寫是具備原子性的。

若需要保證更大範圍的原子性,可通過更高層次的位元組碼指令monitorenter和monitorexit來隱式地使用lock和unlock這兩個操作,反映到Java程式碼中就是同步程式碼塊synchronized關鍵字。

  • 可見性(一個執行緒修改了共享變數的值,其他執行緒能夠立即得知這個修改)

通過在變數修改後將新值同步回主記憶體,在變數讀取前從主記憶體重新整理變數值這種依賴主記憶體作為傳遞媒介的方式來實現。

提供三個關鍵字保證可見性:volatile能保證新值能立即同步到主記憶體,且每次使用前立即從主記憶體重新整理;synchronized對一個變數執行unlock操作之前可以先把此變數同步回主記憶體中;被final修飾的欄位在構造器中一旦初始化完成且構造器沒有把this的引用傳遞出去,就可以在其他執行緒中就能看見final欄位的值。

  • 有序性(按照指令順序執行)

如果在本執行緒內觀察,所有的操作都是有序的,指“執行緒內表現為序列的語義”;如果在一個執行緒中觀察另一個執行緒,所有的操作都是無序的,指“指令重排序”現象和“工作記憶體與主記憶體同步延遲”現象。

提供兩個關鍵字保證有序性:volatile 本身就包含了禁止指令重排序的語義;synchronized保證一個變數在同一個時刻只允許一條執行緒對其進行lock操作,使得持有同一個鎖的兩個同步塊只能序列地進入。

設計模式 (使用過的)

  • 單一職責原則:一個類只負責一個功能領域中的相應職責

  • 開放封閉原則:對擴充套件開放,對修改關閉

  • 依賴倒置原則:抽象不應該依賴於細節,細節應當依賴於抽象。換言之,要針對介面程式設計,而不是針對實現程式設計

  • 迪米特法則:應該儘量減少物件之間的互動,如果兩個物件之間不必彼此直接通訊,那麼這兩個物件就不應當發生任何直接的相互作用,如果其中的一個物件需要呼叫另一個物件的某一個方法的話,可以通過第三者轉發這個呼叫

  • 合成/聚合複用原則:要儘量使用合成/聚合,儘量不要使用繼承

延伸:Android 中原始碼使用的設計模式,自己使用過的設計模式

View事件分發: 責任鏈模式 BitmapFactory載入圖片: 工廠模式 ListAdapter: 介面卡模式
DialogBuilder: 建造者模式 Adapter.notifyDataSetChanged(): 觀察者模式 Binder機制: 代理模式

平時經常使用的 設計模式

單例模式

初級版

```java public class Singleton { private static Singleton instance; private Singleton (){}

public static Singleton getInstance() {
    if (instance == null) {
        instance = new Singleton();
    }
    return instance;
}

} ``` 進階版

java public class Singleton { private volatile static Singleton singleton;//程式碼1 private Singleton (){} public static Singleton getSingleton() { if (singleton == null) {//程式碼2 synchronized (Singleton.class) { if (singleton == null) {//程式碼3 singleton = new Singleton();//程式碼4 } } } return singleton; } }

  在多執行緒中 兩個執行緒可能同時進入程式碼2, synchronize保證只有一個執行緒能進入下面的程式碼,   此時一個執行緒A進入一個執行緒B在外等待, 當執行緒A完成程式碼3 和程式碼4之後, 執行緒B進入synchronized下面的方法, 執行緒B在程式碼3的時候判斷不過,從而保證了多執行緒下 單例模式的執行緒安全,   另外要慎用單例模式,因為單例模式一旦初始化後 只有程序退出才有可能被回收,如果一個物件不經常被使用,儘量不要使用單例,否則為了幾次使用,一直讓單例存在佔用記憶體

優點: - 記憶體中只存在一個物件,節省了系統資源。 - 避免對資源的多重佔用,例如一個檔案操作,由於只有一個例項存在記憶體中,避免對同一資原始檔的同時操作。

缺點: - 單例物件如果持有Context,那麼很容易引發記憶體洩露。 - 單例模式一般沒有介面,擴充套件很困難,若要擴充套件,只能修改程式碼來實現。

Builder 模式

參考文章

33.webp 具體的產品類 ```java public class Computer { private String mCPU; private String mMemory; private String mHD;

public void setCPU(String CPU) {
    mCPU = CPU;
}

public void setMemory(String memory) {
    mMemory = memory;
}

public void setHD(String HD) {
    mHD = HD;
}

} ```

抽象建造者 ```java public abstract class Builder { public abstract void buildCPU(String cpu);//組裝CPU

public abstract void buildMemory(String memory);//組裝記憶體

public abstract void buildHD(String hd);//組裝硬碟

public abstract Computer create();//返回組裝好的電腦

} ```

建立者實現類 ```java public class ConcreteBuilder extends Builder { //建立產品例項 private Computer mComputer = new Computer();

@Override
public void buildCPU(String cpu) {//組裝CPU
    mComputer.setCPU(cpu);
}

@Override
public void buildMemory(String memory) {//組裝記憶體
    mComputer.setMemory(memory);
}

@Override
public void buildHD(String hd) {//組裝硬碟
    mComputer.setHD(hd);
}

@Override
public Computer create() {//返回組裝好的電腦
    return mComputer;
}

} 呼叫者java public class Director { private Builder mBuild = null;

public Director(Builder build) {
    this.mBuild = build;
}

//指揮裝機人員組裝電腦
public void Construct(String cpu, String memory, String hd) {
    mBuild.buildCPU(cpu);
    mBuild.buildMemory(memory);
    mBuild.buildHD(hd);
}

} ```

呼叫

```java

Director direcror = new Director(new ConcreteBuilder());//建立指揮者例項,並分配相應的建造者,(老闆分配任務) direcror.Construct("i7-6700", "三星DDR4", "希捷1T");//組裝電腦 ```

Builder 模式 優缺點

優點 - 封裝性良好,隱藏內部構建細節。 - 易於解耦,將產品本身與產品建立過程進行解耦,可以使用相同的建立過程來得到不同的產品。也就說細節依賴抽象。 - 易於擴充套件,具體的建造者類之間相互獨立,增加新的具體建造者無需修改原有類庫的程式碼。 - 易於精確控制物件的建立,由於具體的建造者是獨立的,因此可以對建造過程逐步細化,而不對其他的模組產生任何影響。

缺點 - 產生多餘的Build物件以及Dirextor類。 - 建造者模式所建立的產品一般具有較多的共同點,其組成部分相似;如果產品之間的差異性很大,則不適合使用建造者模式,因此其使用範圍受到一定的限制

原始碼中使用的 比如: Dialog.Builder

工廠模式

44.webp

抽象產品類 java public abstract class Product { public abstract void show(); }

具體產品類 java public class ProductA extends Product { @Override public void show() { System.out.println("product A"); } } //具體產品類B public class ProductB extends Product { @Override public void show() { System.out.println("product B"); } } 建立抽象工廠類

java //抽象工廠類 public abstract class Factory { public abstract Product create(); } 建立具體工廠類,繼承抽象工廠類

java public class FactoryA extends Factory { @Override public Product create() { return new ProductA();//建立ProductA } } //具體工廠類B public class FactoryB extends Factory { @Override public Product create() { return new ProductB();//建立ProductB } } 測試程式碼 java Factory factoryA = new FactoryA(); Product productA = factoryA.create(); productA.show(); //產品B Factory factoryB = new FactoryB(); Product productB = factoryB.create(); productB.show();

優點: - 符合開放封閉原則。新增產品時,只需增加相應的具體產品類和相應的工廠子類即可。 - 符合單一職責原則。每個具體工廠類只負責建立對應的產品。

缺點: - 增加新產品時,還需增加相應的工廠類,系統類的個數將成對增加,增加了系統的複雜度和效能開銷。

原始碼中 使用的 比如 ThreadFactory

觀察者模式

參考文章

含義: 定義物件間的一種一個對多的依賴關係,當一個物件的狀態傳送改變時,所以依賴於它的物件都得到通知並被自動更新。

55.webp

備註:

  • Subject(抽象主題):又叫抽象被觀察者,把所有觀察者物件的引用儲存到一個集合裡,每個主題都可以有任何數量的觀察者。抽象主題提供一個介面,可以增加和刪除觀察者物件。

  • ConcreteSubject(具體主題):又叫具體被觀察者,將有關狀態存入具體觀察者物件;在具體主題內部狀態改變時,給所有登記過的觀察者發出通知。

  • Observer (抽象觀察者):為所有的具體觀察者定義一個介面,在得到主題通知時更新自己。

  • ConcrereObserver(具體觀察者):實現抽象觀察者定義的更新介面,當得到主題更改通知時更新自身的狀態。

程式碼實現

抽象觀察者

java public interface Observer {//抽象觀察者 public void update(String message);//更新方法 } 具體觀察者

```java public class Boy implements Observer {

    private String name;//名字
    public Boy(String name) {
        this.name = name;
    }
    @Override
    public void update(String message) {//男孩的具體反應
        System.out.println(name + ",收到了資訊:" + message+"屁顛顛的去取快遞.");
    }
}

```

建立抽象主題

```java public interface Observable {//抽象被觀察者 void add(Observer observer);//新增觀察者

     void remove(Observer observer);//刪除觀察者

     void notify(String message);//通知觀察者
}

``` 建立具體主題

```java public class Postman implements Observable{//快遞員

    private List<Observer> personList = new ArrayList<Observer>();//儲存收件人(觀察者)的資訊
    @Override
    public void add(Observer observer) {//新增收件人
        personList.add(observer);
    }

    @Override
    public void remove(Observer observer) {//移除收件人
        personList.remove(observer);

    }

    @Override
    public void notify(String message) {//逐一通知收件人(觀察者)
        for (Observer observer : personList) {
            observer.update(message);
        }
    }
}

``` 測試程式碼

```java Observable postman=new Postman();

    Observer boy1=new Boy("路飛");
    Observer boy2=new Boy("喬巴");
    postman.notify("快遞到了,請下樓領取.");

```

優點: - 解除觀察者與主題之間的耦合。讓耦合的雙方都依賴於抽象,而不是依賴具體。從而使得各自的變化都不會影響另一邊的變化。 - 易於擴充套件,對同一主題新增觀察者時無需修改原有程式碼。

缺點: - 使用觀察者模式時需要考慮一下開發效率和執行效率的問題,程式中包括一個被觀察者、多個觀察者,開發、除錯等內容會比較複雜,而且在Java中訊息的通知一般是順序執行,那麼一個觀察者卡頓,會影響整體的執行效率,在這種情況下,一般會採用非同步實現。 - 可能會引起多餘的資料通知。

Android 原始碼中的觀察者模式:Listener

其他設計模式

由於本人涉獵較少,有些只能說簡單瞭解。這裡分享一個 不錯的 系列部落格,感謝前人栽樹。

設計模式系列教程 !!!

原始碼設計

介面和抽象類

  • 抽象類可以提供成員方法的實現細節,而介面中只能存在 public 抽象方法,沒有實現,(JDK8以後可以有)
  • 抽象類中的成員變數可以是各種型別的,而介面中的成員變數只能是 public static final 型別的;
  • 介面的成員變數只能是靜態常量,沒有建構函式,也沒有程式碼塊,但抽象類都可以有。
  • 一個類只能繼承一個抽象類,而一個類卻可以實現多個介面; 抽象類訪問速度比介面速度要快,因為介面需要時間去尋找在類中具體實現的方法;
  • 如果你往抽象類中新增新的方法,你可以給它提供預設的實現。因此你不需要改變你現在的程式碼。如果你往介面中新增方法,那麼你必須改變實現該介面的類。

所以:抽象類強調的是重用,介面強調的是解耦。

最後更新時間:2019年12月5日 13:45:40