阿里巴巴面試題總結(java後端)-第一章

forrunning發表於2019-04-09

①,一面的面試題總結

1,HashMap實現原理,ConcurrentHashMap實現原理。

<1>HashMap,底層hash表,在jdk 1.7以前是陣列與連結串列,jdk1.8以後是連結串列長度達到8時會演變成紅黑樹(維持資料的插入和查詢的效率平衡)。 <2>ConcurrentHashMap,是hashMap的演變,在jdk 1.7以前是segement分段加鎖,為了減少鎖競爭。 每次的資料查詢經歷兩次hash,第一次hash先找到segement,第二次hash是查詢segement段內的連結串列位置,在寫入資料時對segement加鎖. 每個版本查詢資料的特殊地方:1.6以前是找到節點資料才加鎖(外層呼叫不加鎖),1.7以後都是使用Unsafe.getObjectVolatile。 ConcurrentHashMap 1.8以後是陣列和連結串列,不再使用segement,直接對每個連結串列進行加鎖,更進一步降低鎖競爭,put操作是使用了synchronize對當前連結串列加鎖,get是使用Unsafe.getObjectVolatile獲取最新的共享記憶體值(利用cas不加鎖),為了保證獲取的資料是最新的(可見性)

Segement的modCount變更條件:呼叫put或者remove操作,並且導致元素新增或者被刪除,才能引起變化,如果僅僅覆蓋或者刪除不成功,不會導致變化。 Size()方法:其實就是在每個segement modcount相同情況下(否則馬上再次發起重試),再校驗count的總數(查兩次),如果不同,重試兩次,如果還是不同,就對每個segement加鎖。

補充:LinkedList、ArrayList,LinkedHashMap <1>LinkedList:底層是基於雙向連結串列(非環狀)實現,為什麼使用雙向連結串列?為了提高資料的查詢效率(包括指定index位置插入),因此內部會根據index和size做比較,決定從頭部向後或者尾部向前開始遍歷,如當我們呼叫get(int location)時,首先會比較“location”和“雙向連結串列長度的1/2”;若前者大,則從連結串列頭開始往後查詢,直到location位置;否則,從連結串列末尾開始先前查詢,直到location位置。

特點:LinkedList查詢效率低,增刪效率更高,可以利用零碎的記憶體空間。

<2>ArrayList:是基於動態陣列實現的,如果空間不夠的時候,增加新元素時要動態擴容陣列(期間還需要拷貝資料到新陣列),刪除元素時也需要對後面的資料進行向前移動整合(因為後面還需要使用index搜尋資料)。 特點:ArrayList的查詢效率高,而增刪操作的效率低(只能使用連續的記憶體空間),使用建議:空間可以申請大一些,儘量不要刪除資料。

ArrayList中的modCount,繼承於AbstractList,這個成員變數記錄著集合的修改次數,也就每次add或者remove它的值都會加1,在使用迭代器遍歷集合的時候同時修改集合元素。因為ArrayList被設計成非同步的,因此會丟擲異常,實現原理:獲取迭代器的時候,會記錄一個expectedModCount(不會被改變),在每次迭代過程中會校驗expectedModCount和modCount是否相等,否則會丟擲異常ConcurrentModificationException

<3>LinkedHashMap:繼承了HashMap,因此底層還是雜湊表結構(陣列+連結串列),但是另外多維護了一個雙向環狀連結串列(只有一個頭結點),提供了插入有序和訪問有序(lru)兩種模式,預設為插入有序(列印的結果就是之前的插入順序),LinkedHashMap重寫了的內部addEntry(put呼叫)方法,重寫了Entry,包含before, after,為了將來能構建一個雙向的連結串列,當向map裡面新增資料時,在createEntry時,把新建立的Entry加入到雙向連結串列中,之所以使用雙向環狀連結串列就是為了實現訪問有序,每次訪問,都把節點調整到頭結點前面(實際上變成了尾節點)。 簡單總結:當put元素時,不但要把它加入到HashMap中去,還要加入到雙向連結串列中,所以可以看出LinkedHashMap就是HashMap+雙向環狀連結串列

參考:www.jianshu.com/p/8f4f58b4b…

補充:為什麼Hashtable ConcurrentHashmap不支援key或者value為null ConcurrentHashmap和Hashtable都是支援併發的,這樣會有一個問題,當你通過get(k)獲取對應的value時,如果獲取到的是null時,你無法判斷(除非全程在插入、查詢時加鎖判斷,此時效能就下降了),它是put(k,v)的時候value為null,還是這個key從來沒有做過對映。HashMap是非併發的,可以通過contains(key)來做這個判斷。而支援併發的Map在呼叫m.contains(key)和m.get(key),m可能已經不同了。 備註:實際儲存null,這種場景已經不大,如果真的要儲存,在查詢和插入都需要犧牲了線上程安全條件的併發效能。 參考:blog.csdn.net/gagewang1/a…

2,紅黑樹,為什麼允許區域性不平衡 完全平衡二叉樹的左右兩個子樹的高度差的絕對值不超過1,因此每次的節點變更都需要保證樹的嚴格平衡。 紅黑樹只需要保證從一個節點到該節點的子孫節點的所有路徑上包含相同數目的黑節點,因此紅黑樹是一顆接近完全平衡的二叉樹,減少了旋轉次數,提高資料的寫入效率。

紅黑樹特點: (1)每個節點或者是黑色,或者是紅色。 (2)根節點是黑色。 (3)每個葉子節點(NIL)是黑色。 [注意:這裡葉子節點,是指為空(NIL或NULL)的葉子節點!] (4)如果一個節點是紅色的,則它的子節點必須是黑色的。 (5)從一個節點到該節點的子孫節點的所有路徑上包含相同數目的黑節點。

補充:插入節點預設是紅色(第五點限制),紅黑樹具體的旋轉:www.cnblogs.com/skywang1234…

平衡二叉搜尋樹:又被稱為AVL樹(有別於AVL演算法),且具有以下性質:它是一 棵空樹或它的左右兩個子樹的高度差的絕對值不超過1,並且左右兩個子樹都是一棵平衡二叉樹。平衡二叉樹的常用實現方法有紅黑樹、AVL、替罪羊樹、Treap、伸展樹等。

普通的二叉查詢樹:容易失去”平衡“,極端情況下,二叉查詢樹會退化成線性的連結串列,導致插入和查詢的複雜度下降到 O(n) ,所以,這也是平衡二叉樹設計的初衷。那麼平衡二叉樹如何保持”平衡“呢?根據定義,有兩個重點,一是左右兩子樹的高度差的絕對值不能超過1,二是左右兩子樹也是一顆平衡二叉樹

樹的補充:blog.csdn.net/qq_25940921…

3,tcp三次握手,四次揮手,為什麼不是2次握手?

<1>三次握手(Three-way Handshake),是指建立一個TCP連線時,需要客戶端和伺服器總共傳送3個包 TCP是一個雙向通訊協議,客戶端向服務端建立連線需要得到服務端ack響應,意味著客戶端向服務端通訊正常,此時客戶端再給服務端回覆一個ack,意味著服務端向客戶端通訊正常,這樣才能正確建立起來雙工通訊通道,因此兩次握手不能保證服務端向客戶端通訊是否正常。

<2>TCP的連線的拆除需要傳送四個包,因此稱為四次揮手(four-way handshake)。客戶端或伺服器均可主動發起揮手動作,在socket程式設計中,任何一方執行close()操作即可產生揮手操作。 客戶端或伺服器可以單獨關閉向另外一端的通訊通道,即為半關閉狀態,因此才進行四次揮手。 為什麼需要半關閉?比如只需要向一方傳輸資源,傳輸完畢以EOF標記即可。

4,tcp和udp的區別,為什麼是可靠和不可靠的? <1>TCP協議是有連線的,有連線的意思是開始傳輸實際資料之前TCP的客戶端和伺服器端必須通過三次握手建立連線,會話結束之後也要結束連線。而UDP是無連線的 <2>TCP協議保證資料按序傳送,按序到達,提供超時重傳來保證可靠性,但是UDP不保證按序到達,甚至不保證到達,只是努力交付,即便是按序傳送的序列,也不保證按序送到。 <3>TCP有流量控制和擁塞控制,UDP沒有,網路擁堵不會影響傳送端的傳送速率 <4>TCP是一對一的連線,而UDP則可以支援一對一,多對多,一對多的通訊。 <5>TCP面向的是位元組流的服務,UDP面向的是報文的服務

TCP的可靠性含義: 接收方收到的資料是完整, 有序, 無差錯的。 UDP不可靠性含義: 接收方接收到的資料可能存在部分丟失, 順序也不一定能保證。

備註:GET和POST還有一個重大區別,簡單的說:GET產生一個TCP資料包;POST產生兩個TCP資料包。

長的說: 對於GET方式的請求,瀏覽器會把http header和data一併傳送出去,伺服器響應200(返回資料); 而對於POST,瀏覽器先傳送header,伺服器響應100 continue,瀏覽器再傳送data,伺服器響應200 ok(返回資料)。

post請求的過程: (1)瀏覽器請求tcp連線(第一次握手) (2)伺服器答應進行tcp連線(第二次握手) (3)瀏覽器確認,併傳送post請求頭(第三次握手,這個報文比較小,所以http會在此時進行第一次資料傳送) (4)伺服器返回100 Continue響應 (5)瀏覽器傳送資料 (6)伺服器返回200 OK響應 get請求的過程: (1)瀏覽器請求tcp連線(第一次握手) (2)伺服器答應進行tcp連線(第二次握手) (3)瀏覽器確認,併傳送get請求頭和資料(第三次握手,這個報文比較小,所以http會在此時進行第一次資料傳送) (4)伺服器返回200 OK響應 也就是說,目測get的總耗是post的2/3左右,這個口說無憑,網上已經有網友進行過測試。

參考:blog.csdn.net/zzk220106/a…

5,TCP滑動視窗和socket緩衝區之間的關係 TCP的滑動視窗大小實際上就是socket的接收緩衝區大小的位元組數,“視窗”對應的是一段可以被髮送者傳送的位元組序列,其連續的範圍稱之為“視窗”;每次成功傳送資料之後,傳送視窗就會在傳送緩衝區中按順序移動,將新的資料包含到視窗中準備傳送。(資料都往視窗寫入,因此視窗大小控制資料傳輸速率,起到一個緩衝的作用)

6,tcp擁塞控制 網路中的鏈路容量和交換結點中的快取和處理機都有著工作的極限,當網路的需求超過它們的工作極限時,就出現了擁塞。擁塞控制就是防止過多的資料注入到網路中,這樣可以使網路中的路由器或鏈路不致過載。常用的方法就是:

  1. 慢開始、擁塞控制
  2. 快重傳、快恢復

補充說明:慢開始是指傳送方先設定cwnd=1,一次傳送一個報文段,隨後每經過一個傳輸輪次,擁塞串列埠cwnd就加倍,其實增長並不慢,以指數形式增長。還要設定一個慢開始門限,當cwnd>門限值,改用擁塞避免演算法。擁塞避免演算法使cwnd按線性規律緩慢增長。當網路發生延時,門限值減半,擁塞視窗執行慢開始演算法。(先指數級別增加,再線性增加,延遲再衰減)

快重傳的機制是: 接收方建立這樣的機制,如果一個包丟失,則對後續的包繼續傳送針對該包的重傳請求,一旦傳送方接收到三個一樣的確認,就知道該包之後出現了錯誤,立刻重傳該包;

此時傳送方開始執行“快恢復”演算法: 慢開始門限減半; cwnd設為慢開始門限減半後的數值; 執行擁塞避免演算法(高起點,線性增長) 參考:www.cnblogs.com/woaiyy/p/35…

7,mysql事務是什麼?四大特性,四大隔離級別 事務:事務處理可以用來維護資料庫的完整性,保證成批的 SQL 語句要麼全部執行,要麼全部不執行,是最小的不可再分的工作單元。 四大特性,原子性,隔離性,永續性,一致性。 四大隔離級別: 事務隔離級別 髒讀 不可重複讀 幻讀 讀未提交(read-uncommitted) 是 是 是 讀已提交(read-committed) 否 是 是 可重複讀(repeatable-read) 否 否 是 序列化(serializable) 否 否 否 serializable級別是最高的 mysql預設的事務隔離級別為repeatable-read

① Serializable (序列化):可避免髒讀、不可重複讀、幻讀的發生。   ② Repeatable read (可重複讀):可避免髒讀、不可重複讀的發生。   ③ Read committed (讀已提交):可避免髒讀的發生。   ④ Read uncommitted (讀未提交):最低階別,任何情況都無法保證。

  以上四種隔離級別最高的是Serializable級別,最低的是Read uncommitted級別,當然級別越高,執行效率就越低。像Serializable這樣的級別,就是以鎖表的方式(類似於Java多執行緒中的鎖)使得其他的執行緒只能在鎖外等待,所以平時選用何種隔離級別應該根據實際情況。在MySQL資料庫中預設的隔離級別為Repeatable read (可重複讀)。

  在MySQL資料庫中,支援上面四種隔離級別,預設的為Repeatable read (可重複讀);而在Oracle資料庫中,只支援Serializable (序列化)級別和Read committed (讀已提交)這兩種級別,其中預設的為Read committed級別。

8,spring ioc和aop,各自有什麼優點?

ioc,依賴注入,在以前的軟體工程編碼過程中,類的屬性需要硬編碼生成物件資料,耦合性較高,如果使用ioc,是在容器啟動過程中,在bean物件例項化過程中需要檢查其依賴資料,並且進行資料注入(setter,構造器注入),完成一個物件的例項化並實現瞭解耦合,並且能夠對這些物件進行復用。

aop,主要分為兩大類:一是採用動態代理技術,利用擷取訊息的方式,對該訊息進行裝飾,以取代原有物件行為的執行;二是採用靜態織入的方式,引入特定的語法建立“方面”,從而使得編譯器可以在編譯期間織入有關“方面”的程式碼。它利用一種稱為"橫切"的技術,並將那些影響了多個類的公共行為封裝到一個可重用模組,簡單理解是抽象出與業務邏輯無關的公共行為邏輯。

9,java有哪幾種執行緒池 //執行緒池大小固定為1 Executors.newSingleThreadExecutor();

    //固定大小執行緒池由自己設定,即自己控制資源的固定分配
    Executors.newFixedThreadPool(10);

    //動態調整執行緒池的大小,最小為0,最大為int最大值,,newCachedThreadPool會大幅度提高大量短暫非同步任務的效能,
    //如果執行業務邏輯比較慢,會導致持續建立執行緒,導致cpu資源消耗殆盡
    //為什麼使用SynchronousQueue?最多隻能持有一個任務資料,當任務資料插入佇列失敗,會驅動建立新執行緒
    Executors.newCachedThreadPool();

    //基於延遲佇列實現的延時任務執行緒池,週期性的執行所提交的任務
    ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(10);
    scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() + " run");
        }
    }, 1000,2000, TimeUnit.MILLISECONDS);
複製程式碼

10,什麼情況下使用Thread和Runnable建立執行緒,Runnable和Callable區別 兩個都可以實現多執行緒程式設計,但是基於java是單繼承和多實現,所以實現Runable更靈活,並且Runable可以簡單的實現變數的執行緒間共享。 Runnable和Callable區別: 實現Callable介面的任務執行緒能返回執行結果;而實現Runnable介面的任務執行緒不能返回結果; Callable介面的call()方法允許丟擲異常;而Runnable介面的run()方法的異常只能在內部消化,不能繼續上拋;

11,執行緒方法中的異常如果處理?父執行緒可以捕獲到嗎? 在run方法體裡面必須主動捕獲check exception,如果是unchecked exception(RunntimeException)可以給執行緒註冊一個UncaughtExceptionHandler,發生異常時執行回撥。 通過Callable建立的執行緒,可以在futureTask.get()獲取結果時捕獲異常。

12,synchronized和lock區別,什麼情況下使用synchronized和Reentrantlock ? 1.首先synchronized是java內建關鍵字,在jvm層面,Lock是個java類; 2.synchronized無法判斷是否獲取鎖的狀態,Lock可以判斷是否獲取到鎖; 3.synchronized會自動釋放鎖(a 執行緒執行完同步程式碼會釋放鎖 ;b 執行緒執行過程中發生異常會釋放鎖),Lock需在finally中手工釋放鎖(unlock()方法釋放鎖),否則容易造成執行緒死鎖; 4.用synchronized關鍵字的兩個執行緒1和執行緒2,如果當前執行緒1獲得鎖,執行緒2執行緒等待。如果執行緒1阻塞,執行緒2則會一直等待下去,而Lock鎖就不一定會等待下去,如果嘗試獲取不到鎖,執行緒可以不用一直等待就結束了; 5.synchronized的鎖可重入、不可中斷、非公平,而Lock鎖可重入、可中斷、可判斷、可公平(兩者皆可) 6.Lock鎖適合大量同步的程式碼的同步問題,synchronized鎖適合程式碼少量的同步問題

備註:synchronized適合低併發的場景,鎖競爭發生的概率很小,此時鎖處於偏向鎖或者輕量級鎖狀態,因此效能更加好,比如jdk1.8concurrentHashMap為什麼使用synchronized的原因就是,每個hash槽上的鎖競爭很少,用synchronized比lock更好

13,jvm的物件分配在哪個區,Class物件分配在哪個區? 執行緒共享的變數分配在堆(例項)和方法區(靜態變數等)裡面,非執行緒共享的變數在棧區 Class物件分配在堆裡面,Class的後設資料是在方法區裡面

14,一次http請求的全過程,包括域名解析、主機定位等。 <1>域名解析,分為如下幾個步驟(找不到逐步遞迴) A,查詢瀏覽器的dns快取(域名與ip的對映表) B,查詢自身作業系統自身的DNS快取, C,查詢自身的host檔案 D,請求打到運營商的dns伺服器,訪問運營商的dns快取。 E,訪問全球頂級域名解析器,獲得將要定址域名的許可權域名伺服器地址(用來儲存該區中的所有主機域名到IP地址的對映,如baidu.com的域名ip管理機器) F,運營商dns訪問許可權域名伺服器獲取目標域名的ip。

<2>tcp三次握手 <3>建立TCP連線後發起http請求(get,post由發起者決定)   HTTP請求報文由三部分組成:請求行,請求頭和請求正文   請求行:用於描述客戶端的請求方式,請求的資源名稱以及使用的HTTP協議的版本號(例:GET/books/java.html HTTP/1.1)   請求頭:用於描述客戶端請求哪臺主機,以及客戶端的一些環境資訊等   注:這裡提一個請求頭 Connection,Connection設定為 keep-alive用於說明 客戶端這邊設定的是,本次HTTP請求之後並不需要關閉TCP連線,這樣可以使下次HTTP請求使用相同的TCP通道,節省TCP建立連線的時間   請求正文:當使用POST, PUT等方法時,通常需要客戶端向伺服器傳遞資料。這些資料就儲存在請求正文中(GET方式是儲存在url地址後面,不會放到這裡) <4>伺服器端響應http請求,瀏覽器得到html程式碼   HTTP響應也由三部分組成:狀態碼,響應頭和實體內容   狀態碼:狀態碼用於表示伺服器對請求的處理結果   列舉幾種常見的:200(沒有問題) 302(要你去找別人) 304(要你去拿快取) 307(要你去拿快取) 403(有這個資源,但是沒有訪問許可權) 404(伺服器沒有這個資源) 500(伺服器這邊有問題)   若干響應頭:響應頭用於描述伺服器的基本資訊,以及客戶端如何處理資料   實體內容:伺服器返回給客戶端的資料   注:html資原始檔應該不是通過 HTTP響應直接返回去的,應該是通過nginx通過io操作去拿到的吧

<5>瀏覽器解析html程式碼,並請求html程式碼中的資源 <6>斷開TCP連線(四次揮手) <7>頁面渲染給使用者

總結:域名解析 --> 發起TCP的3次握手 --> 建立TCP連線-->發起http請求 --> 伺服器響應http請求,瀏覽器得到html程式碼 --> 瀏覽器解析html程式碼,並請求html程式碼中的資源(如js、css、圖片等)-->tcp四次揮手 --> 瀏覽器對頁面進行渲染呈現給使用者

②,二面的面試題總結 1,常用設計模式的介紹,單例模式,裝飾模式,及使用場景

單例模式: public class SingletonTest { private static volatile SingletonTest singletonTest;

public SingletonTest getInstance() {
    if (singletonTest == null) {
        synchronized (SingletonTest.class) {
            if (singletonTest == null) {
                singletonTest = new SingletonTest();
            }
        }
    }
    return singletonTest;
}
複製程式碼

} 使用場景:全域性只需要初始化一個例項物件,節省記憶體開銷(比如spring bean的單例配置)

裝飾模式 public class DecoratorTest { public interface Greeting { void sayHello(); }

public static class GreetingImpl implements Greeting {
    @Override
    public void sayHello() {
        System.out.println("GreetingImpl run");
    }
}

public static abstract class GreetingDecorator implements Greeting {
    private Greeting greeting;

    public GreetingDecorator(Greeting greeting) {
        this.greeting = greeting;
    }

    @Override
    public void sayHello() {
        greeting.sayHello();
    }
}

public static class GreetingBefore extends GreetingDecorator {
    public GreetingBefore(Greeting greeting) {
        super(greeting);
    }

    @Override
    public void sayHello() {
        before();
        super.sayHello();
    }

    private void before() {
        System.out.println("Before");
    }
}

public static void main(String[] args) throws FileNotFoundException {
    Greeting greeting = new GreetingBefore(new GreetingImpl());
    greeting.sayHello();

}
複製程式碼

}

代理模式重在於對方法的控制,新增行為對於使用者是被動的;裝飾模式重在於裝飾方法,增加方法的功能(功能增強),新增裝飾對於使用者是主動的(代理模式和裝飾模式看起來很相像)

輸入輸出流使用裝飾模式的地方:BufferedInputStream就是一個裝飾者,它能為一個原本沒有緩衝功能的InputStream新增上緩衝的功能。

2,Java會出現記憶體溢位嗎?什麼情況下會出現? 記憶體溢位(Out Of Memory,OOM),就是記憶體不夠用了,記憶體洩漏(Memory Leak),指的是申請的記憶體空間,自己沒有去主動釋放,gc也無法釋放(如強引用),多次記憶體洩漏,就會導致記憶體溢。

3,雙親委派模型,為什麼這樣做? 當一個類載入和初始化的時候,類僅在有需要載入的時候被載入。假設你有一個應用需要的類叫作Abc.class,首先載入這個類的請求由Application類載入器委託給它的父類載入器Extension類載入器,然後再委託給Bootstrap類載入器。Bootstrap類載入器會先看看rt.jar中有沒有這個類,因為並沒有這個類,所以這個請求由回到Extension類載入器,它會檢視jre/lib/ext目錄下有沒有這個類,如果這個類被Extension類載入器找到了,那麼它將被載入,而Application類載入器不會載入這個類;而如果這個類沒有被Extension類載入器找到,那麼再由Application類載入器從classpath中尋找。

雙親委託可以避免重複載入,當父親已經載入了該類的時候,就沒有必要子ClassLoader再載入一次(單一性)。

4,物件什麼情況下進入老年代? <1>申請大物件,young區無法存放這麼大的物件,此時需要在老年代申請空間。 <2>young區中的eden區物件經過多次Minor GC晉升到Survivor區,如果還存活,就會移動到老年代。

補充: <1>full gc發生條件:System.gc()呼叫,老年代空間不足,perm(方法區,即永生代)空間不足。 <2>young區為什麼要有Survivor區?只要進行一次MinorGC,存活的物件就進入了old區,導致old區很快寫滿,就會頻繁出發full gc,進而引發99線波動很大。 <3>young區為什麼要有2個Survivor區?如果使用單個Survivor,eden區和Survivor經過Minor GC之後存活的物件存放在單個Survivor,可能出現碎片化,使用兩個就不會(每次gc後s0和s1之間交換資料,保證有一個無碎片,一個為空) <4>young和old區比例一般是1:2,eden和s0,s1是8:1:1 參考:blog.csdn.net/towads/arti…

5,快速排序說一下(todo)

6,Spring AOP原理說下 AOP是切面程式設計,所謂切面程式設計是把與業務無關的公共邏輯抽象成一個公共方法執行。AOP的實現是通過動態代理,動態代理有兩種實現方式,jdk自帶的動態代理(通過類載入器,執行時生成一個新的代理類,代理類通過反射機制獲取被代理類的方法,完成被代理方法的執行),cglib在類載入時通過探針技術(javaAgent和ASM,均是位元組碼修改工具)修改被代理類的位元組碼,生成新的class,完成被代理類的方法呼叫。 兩者區別:Jdk動態代理針對介面,cglib針對類。 補充:Jdk動態代理針對介面為什麼只能代理介面?考慮到被代理物件的通用性,由於一個介面可能有多個實現類,不需要針對每個實現類寫一個代理類,這樣提高了通用性。

7,BIO、NIO(如何實現的)、AIO 先簡介一下同步、非同步、阻塞、非阻塞的概念 同步:呼叫者(使用者執行緒,如selector執行緒)呼叫一個服務,必須自己主動等待結果,中間可以做別的事情,但是得要輪詢結果。 非同步:呼叫者(使用者執行緒)呼叫一個服務,不需要等待業務邏輯執行完,就可以去別的事情,之前業務邏輯處理完,會通知呼叫者。 關注點:執行結果獲取結果方式(主動、被動(回撥))

阻塞:呼叫結果沒有返回之前,會持續等待結果(呼叫者執行緒被阻塞,cpu沒有交出去) 非阻塞:呼叫結果沒有返回之前,呼叫者執行緒不會被阻塞。 關注點:使用者執行緒是否被阻塞

BIO:同步阻塞io NIO:同步非阻塞io,需要多路複用器Selector進行排程,執行結果需要多路複用器詢問。 AIO:非同步非阻塞io,執行結果由os進行回撥通知

使用場景: BIO:使用連線數較少,伺服器壓力很低的架構。 NIO方式適用於連線數目多且連線比較短(輕操作)的架構,比如聊天伺服器,併發侷限於應用中,程式設計比較複雜,JDK1.4開始支援。 AIO方式使用於連線數目多且連線比較長(重操作)的架構,比如相簿伺服器,充分呼叫OS參與併發操作,程式設計比較複雜,JDK7開始支援。

AIO在效能上相對於NIO沒有本質的提升。 AIO只是幫助你從核心中將資料複製到使用者空間中,並呼叫你傳入的回撥方法(核心完成回撥)。 NIO是需要程式自己從核心中將資料複製到使用者空間中,並需要程式自己呼叫相應的處理邏輯。

補充:在微服務架構裡面,服務響應都比較快,aio並不比nio響應快多少,因此netty實現上也就不使用aio。

8,訊息中介軟體有哪些?它們之間的優劣勢? ActiveMQ:單機吞吐量在萬級,比RocketMQ和Kafka低一個數量級,傳統中小企業用的多一些,沒有大規模吞吐場景驗證,沒有活躍社群,有較低的概率丟失資料(不是百分百可靠),支援事務。 RabbitMQ:單機吞吐量在萬級,比RocketMQ和Kafka低一個數量級,基於erlang開發,不利於java開發者深入研究,其可靠性非常高,有活躍社群,不支援事務。 RocketMQ:吞吐量10萬級(高吞吐,低延遲),單機支援topic可達到好幾百,基於java開發(客戶端只支援java),有活躍社群,支援事務。(高吞吐量實現上參考了很多kafka的原理,比如記憶體對映,多個broker) Kafka:吞吐量10萬級(高吞吐,低延遲),客戶端支援多種語言,單機topic過多時,會導致效能下降,此時需要增加機器,有活躍社群,不支援事務。

參考:www.jianshu.com/p/eaafb1581…

9,redis的持久化方式 RDB:在指定的時間間隔能對資料進行快照儲存。 優點:使用單獨子程式來進行持久化,主程式不會進行任何IO操作,保證了redis的高效能 缺點:RDB是間隔一段時間進行持久化,如果持久化之間redis發生故障,會發生資料丟失,資料的準確性不高。 AOF:AOF持久化方式記錄每次對伺服器寫的操作,當伺服器重啟的時候會重新執行這些命令來恢復原始的資料,AOF命令以redis協議追加儲存每次寫的操作到檔案末尾,Redis還能對AOF檔案進行後臺重寫,使得AOF檔案的體積不至於過大。 優點:可以保持更高的資料完整性,因此已成為主流的持久化方案 缺點:AOF檔案比RDB檔案大,且恢復速度慢。

補充:redis如何壓縮AOF檔案,具體過程如下: redis呼叫fork ,現在有父子兩個程式 <1>,子程式根據記憶體中的資料庫快照,往臨時檔案中寫入重建資料庫狀態的命令 <2>,父程式繼續處理client請求,除了把寫命令寫入到原來的aof檔案中。同時把收到的寫命令快取起來。這樣就能保證如果子程式重寫失敗的話並不會出問題。 <3>,當子程式把快照內容寫入已命令方式寫到臨時檔案中後,子程式發訊號通知父程式。然後父程式把快取的寫命令也寫入到臨時檔案。 <4>,現在父程式可以使用臨時檔案替換老的aof檔案,並重新命名,後面收到的寫命令也開始往新的aof檔案中追加。 <5>,需要注意到是重寫aof檔案的操作,並沒有讀取舊的aof檔案,而是將整個記憶體中的資料庫內容用命令的方式重寫了一個新的aof檔案,這點和快照有點類似。

簡單總結:如何縮小AOF檔案大小:檔案重寫是指定期重寫AOF檔案(產生新的AOF檔案),減小AOF檔案的體積。需要注意的是,AOF重寫是把Redis程式內的資料轉化為寫命令,同步到新的AOF檔案(為了壓縮aof的持久化檔案。redis提供了bgrewriteaof命令。收到此命令redis將使用與快照類似的方式將記憶體中的資料 以命令的方式儲存到臨時檔案中,最後替換原來的檔案)

10,棧和佇列(todo)

參考:www.cnblogs.com/smyhvae/p/4…

11,jvm垃圾回收演算法 <1>標記-清除演算法 這種垃圾回收一次回收分為兩個階段:標記、清除。首先標記所有需要回收的物件,在標記完成後回收所有被標記的物件。這種回收演算法會產生大量不連續的記憶體碎片,當要頻繁分配一個大物件時,jvm在新生代中找不到足夠大的連續的記憶體塊,會導致jvm頻繁進行記憶體回收(目前有機制,對大物件,直接分配到老年代中) 場景:如果沒有Survivor,eden區gc後會產生很多碎片。 <2>複製演算法 這種演算法會將記憶體劃分為兩個相等的塊,每次只使用其中一塊。當這塊記憶體不夠使用時,就將還存活的物件複製到另一塊記憶體中,然後把這塊記憶體一次清理掉。這樣做的效率比較高,也避免了記憶體碎片。但是這樣記憶體的可使用空間減半,是個不小的損失。 場景:在發生young gc的時候,Survivor0和Survivor1之間的資料相互複製遷移。

<3>標記-整理演算法(標記壓縮法) 這是標記-清除演算法和複製演算法的綜合版,在完成標記階段後,不是直接對可回收物件進行清理,而是讓存活物件向著一端移動,然後清理掉邊界以外的記憶體,避免產生記憶體碎片。

<4>分代收集演算法 這是對上面三種演算法的綜合應用,並且採取分代處理,當前商業虛擬機器都採用這種演算法。首先根據物件存活週期的不同將記憶體分為幾塊即新生代、老年代,然後根據不同年代的特點,採用不同的收集演算法。

補充:

1,Java GC如何判斷物件是否為垃圾(www.cnblogs.com/hzzjj/p/626… <1>引用計數法,引用計數法就是如果一個物件沒有被任何引用指向,則可視之為垃圾。這種方法的缺點就是不能檢測到環的存在。 <2>根搜尋演算法,主流的虛擬機器都是採用GC Roots Tracing演算法,比如Sun的Hotspot虛擬機器便是採用該演算法。 該演算法的核心演算法是從GC Roots物件作為起始點,利用數學中圖論知識,圖中可達物件便是存活物件,而不可達物件則是需要回收的垃圾記憶體。 那麼可以作為GC Roots的物件: A,虛擬機器棧的棧幀的區域性變數表所引用的物件; B,本地方法棧的JNI所引用的物件; C,方法區的靜態變數和常量所引用的物件;

垃圾回收器

Cms垃圾回收器:CMS(Concurrent Mark-Sweep)是以犧牲吞吐量為代價來獲得最短回收停頓時間的垃圾回收器,老年代的回收採用CMS,使用演算法:標記清除法(參考:blog.csdn.net/hutongling/…

工作過程: <1>初始標記:在這個階段,需要虛擬機器停頓正在執行的任務,官方的叫法STW(Stop The Word),所以這個過程雖然暫停了整個JVM,但是很快就完成了。初始標記也就是標記一下GC roots 關聯到的物件。(並不是所有活動物件)。 <2>併發標記:這個階段緊隨初始標記階段,在初始標記的基礎上繼續向下追溯標記。併發標記階段,應用程式的執行緒和併發標記的執行緒併發執行,所以使用者不會感受到停頓。併發標記就需要標記出GC roots關聯到的物件的引用物件有哪些。比如說 A -> B (A 引用 B,假設 A是GC Roots關聯到的物件),那麼這個階段就是標記出B物件,A物件會在初始標記中標記出來。 <3>重新標記(也會stw),之前在併發標記時,因為是GC和使用者程式是併發執行的,可能導致一部分已經標記為 從GC Roots不可達 的物件,因為使用者程式的(併發)執行,又可達了(物件被複活了),Remark的作用就是將這部分物件又標記為 可達物件。 <4>併發清理:清理垃圾物件,這個階段收集器執行緒和應用程式執行緒併發執行。 初始標記和重新標記都要stop the world

重新標記(Remark) 的作用在於: 之前在併發標記時,因為是 GC 和使用者程式是併發執行的,可能導致一部分已經標記為 從 GC Roots 不可達 的物件,因為使用者程式的(併發)執行,又可達 了,Remark 的作用就是將這部分物件又標記為 可達物件。 至於 “浮動垃圾”,因為 CMS 在 併發標記 時是併發的,GC 執行緒和使用者執行緒併發執行,這個過程當然可能會因為執行緒的交替執行而導致新產生的垃圾(即浮動垃圾)沒有被標記到;而 重新標記 的作用只是修改之前 併發標記 所獲得的不可達物件,所以是沒有辦法處理 “浮動垃圾” 的。

Cms的缺點: <1>CMS不會整理和壓縮堆空間,導致產生問題:經過CMS收集的堆會產生空間碎片(不壓縮空間是為了響應更快),典型的空間換時間(實際可以配置經過多少次old gc之後整理old區,減少記憶體碎片) <2>需要更多的CPU資源,不直接使用多執行緒,直接利用多核cpu。 <3>CMS的另一個缺點是它需要更大的堆空間,為了保證在CMS回收完堆之前還有空間分配給正在執行的應用程式,CMS不會在老年代滿的時候才開始收集,在空間到了68%就開始回收。 <4>cms會產生浮動垃圾,由於在應用執行的同時進行垃圾回收,所以有些垃圾可能在垃圾回收進行完成時產生,這樣就造成了“Floating Garbage”,這些垃圾需要在下次垃圾回收週期時才能回收掉。所以,併發收集器一般需要20%的預留空間用於這些浮動垃圾。

G1垃圾回收器,回收演算法是:標記整理,減少記憶體碎片化

G1是在JDK 7u4版本之後釋出的垃圾收集器,並在jdk9中成為預設垃圾收集器,G1也是利用多CPU來縮短stop the world時間(弱化了分代的概念,g1能同時作用於新生代和老年代),並且是高效的併發垃圾收集器。但是G1不再像上文所述的垃圾收集器,需要分代配合不同的垃圾收集器,因為G1中的垃圾收集區域是“分割槽”(Region)的。G1的分代收集和以上垃圾收集器不同的就是除了有年輕代的ygc,全堆掃描的fullgc外,還有包含所有年輕代以及部分老年代Region的MixedGC。G1的優勢還有可以通過調整引數,指定垃圾收集的最大允許pause time。下面會詳細闡述下G1分割槽以及分代的概念,以及G1 GC的幾種收集過程的分類。

G1的優點: 0. 弱化young區和old區概念,把堆區分為若干小的區域region,這些region 可分為eden,surival,old,humergus,預設分為2048個區,每塊1~32M 1,同時對多個regoin進行併發標記,每次GC不需要清理全部的堆區域,只需要清理垃圾最多的分割槽,這樣可以減少stw的時間,提升了jvm的響應效能。 2,不同區域region不需要連續。活躍資料在不同region複製和移動。 3,單個大物件可以存放多個humergus之間,可以有效利用空間。 4,G1根據回收時間的可預計性(時間可配置),一次回收一定數量的region,減少stw時間。

參考:www.cnblogs.com/niejunlei/p…

備註:G1的收集都是STW的,但年輕代和老年代的收集界限比較模糊,採用了混合(mixed)收集的方式。即每次收集既可能只收集年輕代分割槽(年輕代收集),也可能在收集年輕代的同時,包含部分老年代分割槽(混合收集),這樣即使堆記憶體很大時,也可以限制收集範圍,從而降低停頓。G1採用記憶體分割槽(Region)的思路,將記憶體劃分為一個個相等大小的記憶體分割槽,回收時則以分割槽為單位進行回收,存活的物件複製到另一個空閒分割槽中。由於都是以相等大小的分割槽為單位進行操作,因此G1天然就是一種壓縮方案(區域性壓縮)。

G1的工作流程: 1:初始標記,STW。基於yong GC,標記survivor中可能引用老年代物件的物件,作為Root Region,並掃描 2:併發標記:貫穿整個堆記憶體,標記活躍物件,並立即清除,同時收集活躍物件統計資訊。 3:重新標記:使用snapshot-at-the-beginning(SATB),移除,回收標記的空region。STW 4:清理/複製,G1選擇最不活躍的region,以便最快收集。這些區域可以和yong GC同時收集,STW 清理:統計活躍物件,活躍區域(STW)=》清理RSet(STW)=》重置空的region=》歸還到free list(併發)。 複製:移動活躍物件到未應用的區域(STW)

發生full gc條件:當收集垃圾,從一個區域複製資料到另一個區域時,找不到可用區域。

12,MySql索引

<1>InnoDB(b+樹,聚簇索引):支援事務處理,支援外來鍵,支援崩潰修復能力和併發控制。如果需要對事務的完整性要求比較高(比如銀行),要求實現併發控制(比如售票),那選擇InnoDB有很大的優勢。如果需要頻繁的更新、刪除操作的資料庫,也可以選擇InnoDB,因為支援事務的提交(commit)和回滾(rollback)(用於事務處理應用程式,具有眾多特性,包括ACID事務支援。(提供行級鎖))

<2>MyISAM(b+樹,非聚簇索引):插入資料快,空間和記憶體使用比較低。如果表主要是用於插入新記錄和讀出記錄,那麼選擇MyISAM能實現處理高效率。如果應用的完整性、併發性要求比較低,也可以使用(MyISAM型別不支援事務處理等高階處理,因此也不支援資料的回滾修復)。

<3>MEMORY(hash結構):所有的資料都在記憶體中,資料的處理速度快,但是安全性不高。如果需要很快的讀寫速度,對資料的安全性要求較低,可以選擇MEMOEY。它對錶的大小有要求,不能建立太大的表。所以,這類資料庫只使用在相對較小的資料庫表。

為什麼使用b+樹? 在範圍查詢方面,B+樹的優勢更加明顯,B樹的範圍查詢需要不斷進行遍歷查詢每個資料(查一次,進行一次io)。首先二分查詢到範圍下限,在不斷通過遍歷,知道查詢到範圍的上限即可。整個過程比較耗時,而B+樹的範圍查詢則簡單了許多。首先通過二分查詢,找到範圍下限,然後同過葉子結點的連結串列順序遍歷,直至找到上限即可,整個過程簡單許多,效率也比較高。(b+樹的資料都分佈在子節點上)。B+樹的空間相對比較大,典型的空間換時間。

13,tomcat類載入器 在tomcat7版本下,Tomcat自己定義了兩個核心類載入器,JasperLoader負責載入jsp檔案經過編譯生成的jsp類,該類載入器打破了雙親委託機制(為什麼打破雙親委託機制?實現jsp檔案的熱部署,如果不打破雙親委託,之前的jsp部署一次,就會被持續執行,由於父類載入器一直存在,無法再次熱部署了)。webappclassLoader只負責載入web專案下的lib類(分析原始碼並沒有打破雙親委託機制)

14,記憶體洩漏,什麼情況下會發生,如何排查? 指的是申請的記憶體空間,自己沒有去主動釋放,gc也無法釋放(如強引用),多次記憶體洩漏,就會導致記憶體溢位,在生成環境中對某些大物件的不合理使用(沒有用的物件沒被釋放),導致gc回收不了。 如何排查:用jmap生成堆記憶體資訊檔案(常說的記憶體dump), MAT(Memory Analyzer Tool),Eclipse提供的一款用於Heap Dump檔案的工具,MAT分析堆資訊檔案,包括整個堆記憶體的大小、類(Class)的數量、物件(Object)的數量、類載入器(Class Loader)的數量、以及所佔的空間大小。 補充:jstack檢視執行緒堆疊資訊,排查死鎖用的比較多。

16,spi,service provice interface,其實是一套協議,使用者不用關注其實現,各個功能實現者針對標準進行實現就可以了,比如jdbc的資料庫驅動,各個廠商有自己的具體實現

相關文章