阿里java面試題

(1)JVM如何加載一個類的過程,雙親委派模型中有哪些方法?

類的生命週期:加載、(驗證、準備、解析)鏈接、初始化、使用和卸載七個階段

其中類加載的過程包括了加載、驗證、準備、解析、初始化五個階段。在這五個階段中,加載、驗證、準備和初始化這四個階段發生的順序是確定的,而解析階段則不一定,它在某些情況下可以在初始化階段之後開始,這是爲了支持 Java 語言的運行時綁定(也成爲動態綁定或晚期綁定)。

加載階段:通過類的全限定名取得類的二進制流,轉爲方法區數據結構,在Java堆中生成對應的Class對象,作爲對方法區這些數據的訪問入口

驗證階段:文件格式是以0xCAFEBABE開頭,版本號是否合理,元數據,字節碼符號引用的驗證。

準備階段:類變量(static變量)賦初值(0,NULL……),常量被賦正確的值。

解析階段:符號引用替換爲直接引用,類或接口的解析(需要判斷是否爲數組),字段解析(從本類找到接口->父接口->父類->祖父類 依次查找),類方法解析(與字段差不多,但是先父類後接口 ),接口方法解析(只搜父接口)

初始化:執行類構造器(static{}),static變量賦值語句,子類的<clinit>調用前保證父類的<clinit>被調用

雙親委派模型要求除了頂層的啓動類加載器外,其餘的類加載器都應有自己的父類加載器。這些類加載器的父子關係不是以繼承的關係實現,而都是使用組合關係來複用父加載器的代碼。

雙親委派模式的方法:向上不斷查看是否加載了類(findLoadedClass()),向下嘗試加載類(loadClass()),自定義類加載器需要回調(findClass())。

即使兩個類來源於同一個 Class 文件,只要加載它們的類加載器不同,那這兩個類就必定不相等。這裏的“相等”包括了代表類的 Class 對象的 equals()、isAssignableFrom()、isInstance()等方法的返回結果,也包括了使用 instanceof 關鍵字對對象所屬關係的判定結果。

具體請查看Blog Java類裝載過程與類裝載器

(2)HashMap如何實現的?

在JDK1.7及以前,HashMap中維護着Entry,Entry中維護着key,value以及hash和next指針,而整個HashMap實際就是一個Entry數組

當向 HashMap 中 put 一對鍵值時,它會根據 key的 hashCode 值計算出一個位置, 該位置就是此對象準備往數組中存放的位置。 

如果該位置沒有對象存在,就將此對象直接放進數組當中;如果該位置已經有對象存在了,則順着此存在的對象的鏈開始尋找(爲了判斷是否是否值相同,map不允許<key,value>鍵值對重複), 如果此鏈上有對象的話,再去使用 equals方法進行比較,如果對此鏈上的每個對象的 equals 方法比較爲 false,則將該對象放到數組當中,然後將數組中該位置以前存在的那個對象鏈接到此對象的後面。 

get方法類似,通過key取hash找到數組的某個位置,然後遍歷這個數組上的每個Entry,直到key值equals則返回。

如果Hash碰撞嚴重,那麼JDK1.7中的實現性能就很差,因爲每次插入都要遍歷完整條鏈去查看key值是否重複,每次get也要遍歷整個鏈,在JDK1.8中,由於鏈表的查找複雜度爲O(n),而紅黑樹的查找複雜度爲O(logn),JDK1.8中採用鏈表/紅黑樹的方式實現HashMap,達到某個閥值時,鏈表轉成了紅黑樹。

具體請查看 JDK7與JDK8中HashMap的實現

(3)HashMap和Concurrent HashMap區別, Concurrent HashMap 線程安全嗎, ConcurrentHashMap如何保證 線程安全?

HashMap不是線程安全的,ConcurrentHashMap是線程安全的,HashMap內部維護着一個Entry數組,而ConcurrentHashMap內部有一個Segment段,它將大的HashMap切分成若干個段(小的HashMap),然後讓數據在每一段上Hash,這樣多個線程在不同段上的Hash操作一定是線程安全的,所以只需要同步同一個段上的線程就可以了,這樣實現了鎖的分離,大大增加了併發量。ConcurrentHashMap的實現中還使用了不變模式final和volatile來保障線程安全

具體請查看 ConcurrentHashMap總結

(4)HashMap和HashTable 區別,HashTable線程安全嗎?

  1. HashMap允許key和value爲null,HashTable不允許。

  2. HashMap是非線程安全的,HashTable是線程安全的

  3. HashMap的迭代器是Iterator是fail-fast迭代器,當有其它線程改變了HashMap的結構(增加或者移除元素),將會拋出ConcurrentModificationException(關於ConcurrentModificationException請查看 ConcurrentModificationException的原因以及解決措施) 而HashTable的迭代器有enumerator和Iterator兩種,enumerator不會拋出上述異常。

  4. hash算法不同,HashMap能更廣泛地分散到數組的不同位置

  5. 擴展數組的算法不同,HashTable:2 * 原數組長度+1,HashMap:原數組長度 * 2

(5)進程間通信有哪幾種方式?

傳統的進程間通信的方式有大致如下幾種:

# 管道( pipe ):管道是一種半雙工的通信方式,數據只能單向流動,而且只能在具有親緣關係的進程間使用。進程的親緣關係通常是指父子進程關係。
# 有名管道 (named pipe) : 有名管道也是半雙工的通信方式,但是它允許無親緣關係進程間的通信。
# 信號量( semophore ) : 信號量是一個計數器,可以用來控制多個進程對共享資源的訪問。它常作爲一種鎖機制,防止某進程正在訪問共享資源時,其他進程也訪問該資源。因此,主要作爲進程間以及同一進程內不同線程之間的同步手段。
# 消息隊列( message queue ) : 消息隊列是由消息的鏈表,存放在內核中並由消息隊列標識符標識。消息隊列克服了信號傳遞信息少、管道只能承載無格式字節流以及緩衝區大小受限等缺點。
# 信號 ( sinal ) : 信號是一種比較複雜的通信方式,用於通知接收進程某個事件已經發生。
# 共享內存( shared memory ) :共享內存就是映射一段能被其他進程所訪問的內存,這段共享內存由一個進程創建,但多個進程都可以訪問。共享內存是最快的 IPC 方式,它是針對其他進程間通信方式運行效率低而專門設計的。它往往與其他通信機制,如信號兩,配合使用,來實現進程間的同步和通信。
# 套接字( socket ) : 套接字也是一種進程間通信機制,與其他通信機制不同的是,它可用於不同及其間的進程通信。

Java如何支持進程間通信。我們把Java進程理解爲JVM進程。很明顯,傳統的這些大部分技術是無法被我們的應用程序利用了(這些進程間通信都是靠系統調用來實現的)。但是Java也有很多方法可以進行進程間通信的。 
除了上面提到的Socket之外,當然首選的IPC可以使用Rmi,或者Corba也可以。另外Java nio的MappedByteBuffer也可以通過內存映射文件來實現進程間通信(共享內存)。

有兩種類型的進程間通信(IPC):

本地過程調用(LPC):LPC用在多任務操作系統中,使得同時運行的任務能互相會話。這些任務共享內存空間使任務同步和互相發送信息。

遠程過程調用(RPC):RPC類似於LPC,只是在網上工作。RPC開始是出現在Sun微系統公司和HP公司的運行UNIX操作系統的計算機中。

 

(6)JVM分爲哪些區,每一個區幹嗎的?

虛擬機將所管理的內存分爲以下幾個部分:

  • 程序計數器        指向正在執行的字節碼地址

  • 虛擬機棧           存放局部變量表,操作數棧,動態鏈接,方法出口

  • 本地方法區        Native方法服務

  • 堆                     用來存放對象實例的

  • 方法區              用於存儲已經被虛擬機加載過的類信息,常量(JDK7中String常量池被移到堆中),靜態變量(JDK7中被移到Java堆),及時編譯期編譯後的代碼(類方法)等數據。

具體請查看  Java內存區域

(7)JVM如何GC,新生代,老年代,持久代,都存儲哪些東西?

JVM通過可達性(可觸及性)分析算法標記出哪些對象是垃圾對象,然後將垃圾對象進行回收,在新生代中採用複製算法,在老年代中採用標記清理或標記壓縮算法。新生代存儲了新new出的對象,老年代存儲了大的對象和多次GC後仍然存在的老年對象,持久代存儲了類信息,常量(JDK7中String常量池被移到堆中),靜態變量(JDK7中被移到了Java堆),類方法

具體請查看 JVM 垃圾回收機制 , Java內存區域

(8)GC用的引用可達性分析算法中,哪些對象可作爲GC Roots對象?

  • 棧中引用的對象

  • 方法區中靜態成員或者常量引用的對象(全局對象)

  • JNI方法棧中引用對象

總體來說就是,全局中的引用(例如常量或者靜態屬性)與執行上下文(例如棧幀中的本地變量表)。

具體請查看  JVM 垃圾回收機制

(9)快速排序,過程,複雜度?

快排的基本思想是:

1.先從數列中取出一個數作爲基準數(pivot)。

2.分區過程,將比這個數大的數全放到它的右邊,小於或等於它的數全放到它的左邊。

3.再對左右區間重複第二步,直到各區間只有一個數。

平均複雜度O(NlogN),最差O(N^2),最好O(NlogN)

具體請查看 排序總結(不斷更新)

(10)什麼是二叉平衡樹,如何插入節點,刪除節點,說出關鍵步驟。

二叉平衡樹:它是一棵空樹或它的左右兩個子樹的高度差的絕對值不超過1,並且左右兩個子樹都是一棵平衡二叉樹。

AVL樹的插入和刪除,主要是依靠左旋和右旋來達到平衡狀態。

紅黑樹的插入,插入節點是紅色,通過一系列選擇和着色使其成爲紅黑樹。

步驟爲:

  • 插入節點的父親節點是黑色,直接插入。

  • 插入節點的父親節點是紅色:

  1. 插入節點的叔叔節點是紅色的話,將父節點和叔叔節點變成黑色,父節點的父節點變成紅色。然後整體上移

  2. 叔叔節點是黑色,當前節點是右孩子,通過旋轉將當前節點轉到左孩子

  3. 叔叔節點是黑色,當前節點是左孩子,一次旋轉一次着色。

所以紅黑樹的插入需要最多兩次旋轉,刪除需要最多三次旋轉

具體請查看 紅黑樹

(11)TCP如何保證可靠傳輸?三次握手過程?

TCP用三次握手和滑動窗口機制來保證傳輸的可靠性和進行流量控制。
第一次握手:
客戶端發送一個TCP的SYN標誌位置1的包指明客戶打算連接的服務器的端口,以及初始序號X,保存在包頭的序列號(Sequence Number)字段裏。
第二次握手:
服務器發回確認包(ACK)應答。即SYN標誌位和ACK標誌位均爲1同時,將確認序號(Acknowledgement Number)設置爲客戶的ISN加1以.即X+1。
第三次握手.
客戶端再次發送確認包(ACK) SYN標誌位爲0,ACK標誌位爲1.並且把服務器發來ACK的序號字段+1,放在確定字段中發送給對方.並且在數據段放寫ISN的+1

(12)TCP和UDP區別?

TCP---傳輸控制協議,提供的是面向連接、可靠的字節流服務。當客戶和服務器彼此交換數據前,必須先在雙方之間建立一個TCP連接,之後才能傳輸數據。TCP提供超時重發,丟棄重複數據,檢驗數據,流量控制等功能,保證數據能從一端傳到另一端。
UDP---用戶數據報協議,是一個簡單的面向數據報的運輸層協議。UDP不提供可靠性,它只是把應用程序傳給IP層的數據報發送出去,但是並不能保證它們能到達目的地。由於UDP在傳輸數據報前不用在客戶和服務器之間建立一個連接,且沒有超時重發等機制,故而傳輸速度很快

(13)滑動窗口算法?

1. 首先是AB之間三次握手建立TCP連接。在報文的交互過程中,A將自己的緩衝區大小(窗口大小)3發送給B,B同理,這樣雙方就知道了對端的窗口大小。
2. A開始發送數據,A連續發送3個單位的數據,因爲他知道B的緩衝區大小。在這一波數據發送完後,A就不能再發了,需等待B的確認。
3. A發送過來的數據逐漸將緩衝區填滿。
4. 這時候緩衝區中的一個報文被進程讀取,緩衝區有了一個空位,於是B向A發送一個ACK,這個報文中指示窗口大小爲1。
A收到B發過來的ACK消息,並且知道B將窗口大小調整爲1,因此他只發送了一個單位的數據並且等待B的下一個確認報文。
5. 如此反覆。

(14)Linux下如何進行進程調度的?

在Linux中,進程的運行時間不可能超過分配給他們的時間片,他們採用的是搶佔式多任務處理,所以進程之間的掛起和繼續運行無需彼此之間的協作。

(15)操作系統什麼情況下會死鎖?

產生死鎖的原因:一是系統提供的資源數量有限,不能滿足每個進程的使用;二是多道程序運行時,進程推進順序不合理。  

銀行家算法:安全狀態一定沒有死鎖發生
產生死鎖的必要條件是:1、互斥條件;2、不可剝奪條件(不可搶佔);3、部分分配;4、循環等待。

詳情查看操作系統總結

(16)常用的hash算法有哪些?

  1. 直接定址法 :地址集合 和 關鍵字集合大小相同

  2. 數字分析法 :根據需要hash的 關鍵字的特點選擇合適hash算法,儘量尋找每個關鍵字的 不同點

  3. 平方取中法:取關鍵字平方之後的中間極爲作爲哈希地址,一個數平方之後中間幾位數字與數的每一位都相關,取得位數由表長決定。比如:表長爲512,=2^9,可以取平方之後中間9位二進制數作爲哈希地址。

  4. 摺疊法:關鍵字位數很多,而且關鍵字中每一位上的數字分佈大致均勻的時候,可以採用摺疊法得到哈希地址,

  5. 除留取餘法除P取餘,可以選P爲質數,或者不含有小於20的質因子的合數

  6. 隨機數法:通常關鍵字不等的時候採用此法構造哈希函數較恰當。

實際工作中需要視不同的情況採用不同的hash函數:

  1. kao慮因素:計算哈希函數所需要的時間,硬件指令等因素。

  2. 關鍵字長度

  3. 哈希表大小

  4. 關鍵字分佈情況

  5. 記錄查找的頻率。(huffeman樹)

處理衝突的方法:

  1. 開放地址法:現行探測再散列 只要哈希表爲填滿,總能找到一個不衝突的地址,二次探測再散列 表長爲素數時纔可能保證總能找到一個不衝突的地址,隨機探測再散列取決於僞隨機數列

  2. 再哈希法:不易發生聚集,但是增加了計算的時間

  3. 鏈地址法;Chord協議中,一致性hash有應用。

(17)什麼是一致性哈希?

consistent hashing 是一種 hash 算法,簡單的說,在移除 / 添加一個 cache 時,它能夠儘可能小的改變已存在key 映射關係,儘可能的滿足單調性的要求。

通過一個環形hash空間,將服務器和需要緩存的內容都映射到環形空間內,將緩存的內容映射到下一個服務器中,當一個服務器down了以後,就映射到下一個服務器上。

但是這樣會對下一個服務器造成很大的壓力,沒有平均到其他服務器上。這裏就提出了增加虛擬節點,虛擬節點保證了映射到每個服務器的概率均衡。 

詳情查看一致性Hash算法

(18)如何理解分佈式鎖?

分佈式鎖是控制分佈式系統之間同步訪問共享資源的一種方式。在分佈式系統中,常常需要協調他們的動作。如果不同的系統或是同一個系統的不同主機之間共享了一個或一組資源,那麼訪問這些資源的時候,往往需要互斥來防止彼此干擾來保證一致性,在這種情況下,便需要使用到分佈式鎖。

分佈式鎖可以使用數據庫鎖,redis(緩存),zookeeper來實現

數據庫鎖主要是使用唯一索引來代替鎖,加鎖時就往表中插入一個記錄,其他線程要加鎖則會唯一性約束無法成功。缺點是

1. 無法阻塞(加鎖失敗後,需要再發一次請求再次嘗試)。

2. 如果服務器宕機,則無法解鎖,造成死鎖(可以從應用層上加定時任務,超過時間則強制解鎖)

redis作爲分佈式鎖:

第一種方式是緩存鎖,就是使用setnx,即只有在某個key不存在情況才能set成功該key,這樣就達到了多個進程併發去set同一個key,只有一個進程能set成功。(缺點和數據庫鎖一樣,但是redis自帶過期時間EX,則不需要從應用層加定時任務,雖然redis有主從複製,由於主從複製是異步的,仍然無法保證宕機後鎖丟失)

第二種方式是Redlock

爲了解決鎖丟失,提出了Redlock算法

Redlock算法假設有N個redis節點,這些節點互相獨立,一般設置爲N=5,這N個節點運行在不同的機器上以保持物理層面的獨立。

算法的步驟如下:

  • 1、客戶端獲取當前時間,以毫秒爲單位。

  • 2、客戶端嘗試獲取N個節點的鎖,(每個節點獲取鎖的方式和前面說的緩存鎖一樣),N個節點以相同的key和value獲取鎖。客戶端需要設置接口訪問超時,接口超時時間需要遠遠小於鎖超時時間,比如鎖自動釋放的時間是10s,那麼接口超時大概設置5-50ms。這樣可以在有redis節點宕機後,訪問該節點時能儘快超時,而減小鎖的正常使用。

  • 3、客戶端計算在獲得鎖的時候花費了多少時間,方法是用當前時間減去在步驟一獲取的時間,只有客戶端獲得了超過3個節點的鎖,而且獲取鎖的時間小於鎖的超時時間,客戶端才獲得了分佈式鎖。

  • 4、客戶端獲取的鎖的時間爲設置的鎖超時時間減去步驟三計算出的獲取鎖花費時間。

  • 5、如果客戶端獲取鎖失敗了,客戶端會依次刪除所有的鎖。

redlock由於和時間有關,比如在應用程序發生長時間的fullgc時,也會導致問題。

zookeeper的分佈式鎖:

zookeeper實現鎖的方式是客戶端一起競爭寫某條數據,比如/path/lock(路徑),只有第一個客戶端能寫入成功,其他的客戶端都會寫入失敗。寫入成功的客戶端就獲得了鎖,寫入失敗的客戶端,註冊watch事件,等待鎖的釋放,從而繼續競爭該鎖。

優點:

  • zookeeper支持watcher機制,這樣實現阻塞鎖,可以watch鎖數據,等到數據被刪除,zookeeper會通知客戶端去重新競爭鎖。

  • zookeeper的數據可以支持臨時節點的概念,即客戶端寫入的數據是臨時數據,在客戶端宕機後,臨時數據會被刪除,這樣就實現了鎖的異常釋放。使用這樣的方式,就不需要給鎖增加超時自動釋放的特性了。

具體關於分佈式鎖的詳情請查看分佈式鎖

(19)數據庫中的範式有哪些?

第一範式:確保每列的原子性.
    如果每列(或者每個屬性)都是不可再分的最小數據單元(也稱爲最小的原子單元),則滿足第一範式.
第二範式:在第一範式的基礎上更進一層,目標是確保表中的每列都和主鍵相關.
    如果一個關係滿足第一範式,並且除了主鍵以外的其它列,都依賴於該主鍵,則滿足第二範式.
第三範式:在第二範式的基礎上更進一層,目標是確保每列消除傳遞依賴.
    如果一個關係滿足第二範式,並且沒有傳遞依賴,則滿足第三範式.
    爲了理解第三範式,需要根據Armstrong公里之一定義傳遞依賴。假設A、B和C是關係R的三個屬性,如果A-〉B且B-〉C,則從這些函數依賴中,可以得出A-〉C,如上所述,依賴A-〉C是傳遞依賴。
例如:訂單表(訂單編號,定購日期,顧客編號,顧客姓名,……),初看該表沒有問題,滿足第二範式,每列都和主鍵列"訂單編號"相關,再細看你會發現"顧客姓名"和"顧客編號"相關,"顧客編號"和"訂單編號"又相關,最後經過傳遞依賴,"顧客姓名"也和"訂單編號"相關。爲了滿足第三範式,應去掉"顧客姓名"列,放入客戶表中。

(20)數據庫中的索引的結構?什麼情況下適合建索引?

mysql索引結構是B+樹和hash。(hash索引只有在等值查詢時,並且重複值少時才高效,具體兩者區別請查看MySQL B+樹索引和哈希索引的區別)

兩種情況下不建議建索引。

第一種情況是表記錄比較少,例如一兩千條甚至只有幾百條記錄的表,沒必要建索引,讓查詢做全表掃描就好了。至於多少條記錄纔算多,這個個人有個人的看法,我個人的經驗是以2000作爲分界線,記錄數不超過 2000可以考慮不建索引,超過2000條可以酌情考慮索引。

另一種不建議建索引的情況是索引的選擇性較低。所謂索引的選擇性(Selectivity),是指不重複的索引值(也叫基數,Cardinality)與表記錄數(#T)的比值:

Index Selectivity = Cardinality / #T

顯然選擇性的取值範圍爲(0, 1],選擇性越高的索引價值越大,這是由B+Tree的性質決定的。

關於mysql索引的詳情請查看MySQL索引背後的數據結構及算法原理

(21)Java中的NIO,BIO,AIO分別是什麼?

 

(24)用什麼工具調試程序?JConsole,用過嗎?

(25)JVM中某個線程掛起,如何用工具查出原因?

visualVM Dump線程信息出來。然後查看是因爲死鎖,還是阻塞等

(26)線程同步與阻塞的關係?同步一定阻塞嗎?阻塞一定同步嗎?

(27)同步和異步有什麼區別?

(28)線程池用過嗎?

(29)如何創建單例模式?說了雙重檢查,他說不是線程安全的。如何高效的創建一個線程安全的單例?

(30)concurrent包下面,都用過什麼?

(31)常用的數據庫有哪些?redis用過嗎?

(32)瞭解hadoop嗎?說說hadoop的組件有哪些?hdfs,hive,hbase,zookeeper。說下mapreduce編程模型。

(33)你知道的開源協議有哪些?

(34)你知道的開源軟件有哪些?

(35)你最近在看的書有哪些?

(37)瞭解哪些設計模式?說說都用過哪些設計模式

(38)如何判斷一個單鏈表是否有環?

給定一個單鏈表,只給出頭指針h:

1、如何判斷是否存在環?

2、如何知道環的長度?

3、如何找出環的連接點在哪裏?

4、帶環鏈表的長度是多少?

解法:

1、對於問1,使用追趕的方法,設定兩個指針slow、fast,從頭指針開始,每次分別前進1步、2步。如存在環,則兩者相遇;如不存在環,fast遇到NULL退出。

2、對於問2,記錄下問1的碰撞點p,slow、fast從該點開始,再次碰撞所走過的操作數就是環的長度s。

3、問3:有定理:碰撞點p到連接點的距離=頭指針到連接點的距離,因此,分別從碰撞點、頭指針開始走,相遇的那個點就是連接點。

4、問3中已經求出連接點距離頭指針的長度,加上問2中求出的環的長度,二者之和就是帶環單鏈表的長度

(39)操作系統如何進行分頁調度?

(40)匿名內部類是什麼?如何訪問在其外面定義的變量?

發佈了386 篇原創文章 · 獲贊 388 · 訪問量 128萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章