一. 談?wù)勀銓ava平臺的理解? "Java 是解釋執(zhí)行"涛癌,這句話正確么?
典型回答:
Java本身是一種面向?qū)ο蟮恼Z言,最顯著的特點(diǎn)有兩個方面,一個是所謂的"書寫一次,到處運(yùn)行";能夠非常容易地獲得跨平臺能力;另外就是垃圾收集器(GC),Java通 過垃圾收集器回收分配內(nèi)存,大部分情況下送火,程序員不需要自己操心內(nèi)存的分配和回收拳话。我們?nèi)粘=佑|到JRE或者JDK。JRE也就是Java運(yùn)行環(huán)境,包含了JVM和Java類庫,以及一些模塊等种吸。而JDK可以看作是JRE的一個超集,提供了更多的工具,比如編譯器各種診斷工具弃衍。
"對于Java是解釋執(zhí)行"這句話,這個說法不準(zhǔn)確骨稿。我們開發(fā)的Java的源代碼,首先通過Javac編譯成為字節(jié)碼,然后在運(yùn)行時通過Java虛擬機(jī)內(nèi)嵌的解釋器將字節(jié)碼轉(zhuǎn)換為最終的機(jī)器碼笨鸡。但是常見的JVM,比如我們大數(shù)據(jù)情況使用的0racleJDK提供的HostpotJVM,提供了JIT編譯器,就是通常所說的動態(tài)編譯器,JIT能夠在運(yùn)行時將熱點(diǎn)代碼(高頻調(diào)用的方法和代碼塊)編譯成機(jī)器碼,這種情況下部分熱點(diǎn)就屬于編譯執(zhí)行,而不是解釋執(zhí)行姜钳。這樣類似于緩存技術(shù),運(yùn)行時在遇到熱點(diǎn)代碼可以直接執(zhí)行,而不是先解釋在執(zhí)行坦冠。
知識擴(kuò)展:
在運(yùn)行時 , JVM通過類加載器加載字節(jié)碼,解釋或者編譯執(zhí)行形耗。就像我們前面提到的,主流的Java版本中,如JDK8實(shí)際是解釋和編譯混合的一種模式,即所謂的混合模式。JIT編譯器分為多種模式(Server模式C1ient模式AOT模式)通常運(yùn)行在Server模式的JVM,會進(jìn)行上萬次調(diào)用以收集足夠的信息進(jìn)行高效編譯,client模式這個門限是1500次辙浑。
Oracle Hostpot JVM內(nèi)置了兩個不同的JIT compiler,C1對應(yīng) 前面說的client模式激涤,適用于對于啟動速度敏感的應(yīng)用,比如普通Java桌面應(yīng)用;C2對應(yīng)Server模式,它的優(yōu)點(diǎn)是為長時間運(yùn)行的服務(wù)器端應(yīng)用設(shè)計的判呕。
Java虛擬機(jī)啟動時倦踢, 可以指定不同的參數(shù)對運(yùn)行模式進(jìn)行選擇。比如鲸阻,執(zhí)行"-Xint",就是告訴JVM只進(jìn)行解釋執(zhí)行,不對代碼進(jìn)行編譯,這種模式拋棄了JIT可能帶來的性能優(yōu)勢躺坟。畢竟解釋器是逐條讀入,逐條解釋運(yùn)行的匠题。與其相對性的,還有一個"Xcomp"參數(shù),這是告訴JVM關(guān)閉解釋器,不要進(jìn)行解釋執(zhí)行,或者叫做最大優(yōu)化級別晤碘。那你可能會問這種模式是不是最高效啊?簡單來說,還真未必功蜓。"-Xcomp" 會導(dǎo)致JVM啟動變慢非常多..
除了日常最常見的Java使用模式园爷,其實(shí)還有一種新的編譯方式,即所謂的AOT,直接將字節(jié)碼編譯成機(jī)器碼,這樣就避免了JIT預(yù)熱等各方面的開銷,比如Oracle JDK 9就引入了實(shí)性質(zhì)的AOT特性,并且增加了jaotc工具式撼。
另外,JVM作為一個強(qiáng)大的平臺童社,不僅僅只有Java語言可以運(yùn)行在JVM上,本質(zhì)上合規(guī)的字節(jié)碼都可以運(yùn)行,Java語言自身也為此提供了便利,我們可以看到類似ClojureScala Groovy JRuby Jython等 大量JVM語言,活躍在不同的場景。
二. 請對比Exception和Error,另外著隆,運(yùn)行時異常與一般異常有什么區(qū)別?
典型回答:
Exception和Error都是繼承了Throwable類,在Java中只有Throwable類型的實(shí)例才可以被拋出或者捕獲,它是異常處理機(jī)制的基本組成類型扰楼。Exceptoin和Error體現(xiàn)了Java平臺設(shè)計者對于不同異常情況的分類。Exception是程序正常運(yùn)行中,可以預(yù)料的意外情況美浦,可以并且應(yīng)該被捕獲,進(jìn)行相應(yīng)處理弦赖。Error是指在正常情況下,不大可能出現(xiàn)的情況,絕大部分的Error都會導(dǎo)致處于非正常的不可恢復(fù)狀態(tài)。既然是非正常情況,所以不便于也不需要捕獲,常見的比如.OutofMemoryError之類抵代,都是Error的子類腾节。
Exceptoin又分為可檢查異常和不檢查異常,可檢查異常在代碼里必須顯示地進(jìn)行捕獲處理,這是編譯器檢查的一部分荤牍。不檢查異常就是所謂的運(yùn)行時異常案腺,類似于Nul1PointerException ArrayIndex0utofBoundsException之類,通常是可以編碼避免的邏輯錯誤,具體根據(jù)需要來進(jìn)行判斷是否需要捕獲,并不會在編譯期強(qiáng)制要求。
知識擴(kuò)展:
在開發(fā)中盡量不要捕獲類似Exceptio這樣的通用異常,而是應(yīng)該捕獲特定異常.這是因為我們在日常的開發(fā)和合作中康吵,我們讀代碼的機(jī)會往往超過寫代碼,軟劈榨,件工程是門協(xié)作的藝術(shù),所以我們有義務(wù)讓自己的代碼能夠直接地體現(xiàn)出盡量多的信息,而泛泛的Exception之類,恰恰隱藏了我們的目的。另外晦嵌,我們也要保證程序不會捕獲到我們不希望捕獲的異常同辣。比如,你可能更希望RuntimeException 被擴(kuò)散出來,而不是被捕獲拷姿。
在開發(fā)中不要生吞異常。這是異常處理中要特別注意的事情,因為很可能會導(dǎo)致非常難以診斷的詭異情況旱函。生吞異常,往往是基于假設(shè)這段代碼可能不會發(fā)生,或者感覺忽略異常是無所謂的,但是千萬不要在產(chǎn)品代碼做這種假設(shè)!如果我們不把異常拋出來,或者也沒有輸出日志之類,程序可能在后續(xù)代碼以不可控的方式結(jié)束响巢。沒有人能夠輕易判斷究竟是哪里出了異常,以及是什么原因產(chǎn)生了異常。
在開發(fā)中不要輸出標(biāo)準(zhǔn)錯誤(STERR)棒妨,因為有時候你很難判斷出到底輸出到哪里去了踪古。尤其是分布式系統(tǒng),如果發(fā)生異常,但是無法找到堆棧軌跡,這純屬是為診斷設(shè)置障礙。所以最好使用產(chǎn)品日志,詳細(xì)地輸出到日志系統(tǒng)里券腔。
Throw early,catch late伏穆。在開發(fā)中可能會出現(xiàn)各種情況,比如獲取配置失敗之類的。在發(fā)現(xiàn)問題的時候,第一時間拋出纷纫,能夠更加清晰地反映問題,這是Throw early枕扫。 catch late就是 我們經(jīng)常煩惱的問題,捕獲異常后,需要怎么處理?最差的方式,就是我們前面提到的"生吞異常",本質(zhì)上就是掩蓋問題。如果實(shí)在不知道如何處理辱魁,可以選擇保留原有異常的cause信息,直接再拋出或者構(gòu)建新的異常拋出去烟瞧。在更高層,因為有了清晰的(業(yè)務(wù))邏輯,往往會更清楚合適的處理方式是什么。
有時候,我們會根據(jù)需要自己定義異常商叹,這個時候除了保證提供足夠的信息燕刻,還需要考慮兩點(diǎn)。一是否需要定異常CheckedException,因為這種類型的設(shè)計初衷是為了從異常情況恢復(fù),作為異常設(shè)計者,我們往往有充足信息進(jìn)行分類剖笙。在保證診斷信息足夠的同時,也要考慮避免包含敏感信息,因為那樣可能會導(dǎo)致潛在的安全問題卵洗。如果我們看Java的標(biāo)準(zhǔn)類庫,你可能注意到類似java. net. ConnectException,出錯信息是類似"Connection refused", 而不包含具體的機(jī)器名IP端口等,一個重要的考量就是信息安全。類似的情況在日志系統(tǒng)中也有,比如,用戶數(shù)據(jù)一般是不可以輸出到日志里面的弥咪。
try-catch代碼段會產(chǎn)生額外的性能開銷,或者換個角度來說,它往往會影響JVM對代碼進(jìn)行優(yōu)化,所以建議僅捕獲有必要的代碼段.盡量不要一個大的try包住整段代碼过蹂,與此同時,利用異常控制代碼流程聚至,也不是一個好主意,遠(yuǎn)比我們通常意義上的條件語句要低效酷勺。
額外:
NoClassDeF oundError和ClassNotFoundException的區(qū)別?
首先NoClassDeFoundError是一個錯誤,ClassNotFoundException是一個異常扳躬。ClassNotFoundException的產(chǎn)生原因,Java支持使用Class. froName方法來動態(tài)地加載類,任意一個類的類名如果被作為參數(shù)傳遞給這個方法都將導(dǎo)致該類被加載到JVM內(nèi)存中,如果這個類在類路徑中沒有被找到,那么此時就會在運(yùn)行時拋出ClassNotFoundException異常脆诉。
另外還有一個導(dǎo)致ClassNotFoundException的原因就是,當(dāng)一個類已經(jīng)被某個類加載器加載到內(nèi)存中,此時另一個類加載器又嘗試著動態(tài)地從同一個包中加載這個類贷币。
NoClassDeFoundError產(chǎn)生的原因在于:如果JVM或者ClassLoader實(shí)例嘗試加載類的時候找不到類的定義击胜。例如要查找的類在編譯的時候是存在的,運(yùn)行的時候,找不到了役纹。這個時候就會導(dǎo)致NoClassDefFoundError.造成該問題的原因可能是打包過程中漏掉了部分類,或者jar包出現(xiàn)損壞或者篡改偶摔。解決這個問題的辦法就是查找那些在開發(fā)期間存在與類路徑下,但在運(yùn)行期間卻不在類路徑下的類。
三. 對比Hashtable促脉、HashMap辰斋、TreeMap有什么不同?
典型回答:
HashTable HashMap TreeMap都是最常見的一些Map實(shí) 現(xiàn),是以鍵值對的形式存儲和操作數(shù)據(jù)的容器類型策州。
HashTable是早期Java類庫提供的一個哈希表實(shí)現(xiàn),本身是同步的,不支持nu11鍵和值,由于同步導(dǎo)致的性能開銷,所以已經(jīng)很少被推薦使用。
HashMap是應(yīng)用更加廣泛的哈希表實(shí)現(xiàn)宫仗,行為大致.上與HashTable- -致,主要區(qū)別在于HashMap不是同步的够挂,支持nul1鍵和值等。通常情況下, HashMap進(jìn)行put或者get操作,可以達(dá)到常數(shù)時間的性能锰什,所以它是絕大部分利用鍵值對存儲場景的首選,比如下硕,實(shí)現(xiàn)一個用戶ID和用戶信息對應(yīng)的運(yùn)行時存儲結(jié)構(gòu)丁逝。
TreeMap則是基于紅黑樹的一種提供順序訪問的Map,和HashMap不同,它的getremove之類操作都是0(long(n)的時間復(fù)雜度汁胆,具體順序可以由指定的Comparator來決定或者根據(jù)鍵的自然順序來判斷。
知識擴(kuò)展:
HashMap實(shí)現(xiàn)原理是經(jīng)常被問到的一個問題,以下基于JDK1.8分析霜幼。
1. 內(nèi)部存儲
HashMap的內(nèi)部存儲是一個數(shù)組,數(shù)組的元素Node實(shí)現(xiàn)了Map.Entry接口(hash, key, value, next) , next非空時指向定位相同的另一個Entry,如圖:
JDK 8之前嫩码,其內(nèi)部是由數(shù)組+鏈表來實(shí)現(xiàn)的,而JDK 8對于鏈表長度超過8的鏈表將轉(zhuǎn)儲為紅黑樹罪既。
2. 容量(capacity)和負(fù)載因子(loadFactor)
簡單的說, capacity就是數(shù)組的大小铸题,loadFactory就是數(shù)組填滿程度的最大比列。當(dāng)數(shù)組中的元素的數(shù)目大于capaci ty*loadFactor時就需要擴(kuò)容,調(diào)整數(shù)組的大小為當(dāng)前的2 倍琢感。同時初始化容量的大小也是2的次冪(大于等于設(shè)定容量的最小次冪),則數(shù)組的大小在擴(kuò)容前后都將是2的次冪丢间。默認(rèn)的容量為16,負(fù)載因子為0.75。
3. put方法的大致的思路
- 如果key的值為null,則將該鍵值對添加到table[0]處驹针,遍歷該鏈表烘挫,如果有key為null,則將value替換柬甥。
- 如果key不為null,獲取key的hashCode值,經(jīng)過indexFor()方法運(yùn)算得到的值作為標(biāo)識,但由于hashCode的值并不唯一,經(jīng)過運(yùn)算獲取的值也不能保證唯一(哈希沖突),所以,經(jīng)過以上運(yùn)算得來的數(shù)值只能作為數(shù)組的索引饮六。
- 當(dāng)通過索引定位到這個節(jié)點(diǎn)時,在遍歷該鏈表,判斷是否存在相同的key對象,如果存在就用新的value覆蓋舊的value
- 如果不存在,就創(chuàng)建一個Entry對象添加到table[i]處,如果table[i]已經(jīng)存在其他元素苛蒲,那么新Entry對象將會保存在鏈表的表頭卤橄,通過next指針指向原有的Entry對象,形成鏈表結(jié)構(gòu)。
- 當(dāng)鏈表的結(jié)構(gòu)太長時(默認(rèn)超過8個元素),鏈表就會轉(zhuǎn)為紅黑樹臂外。
4. get方法的大致的思路
- 對key進(jìn)行nu11檢查窟扑。如果key是nu11,table[0]這個位置的元素將被返回
- key的hashcode() 方法被調(diào)用漏健,然后計算hash值嚎货。
- indexFor (hash, table. length)用來計算要獲取的Entry對象在table數(shù)組中的精確的位置(使用剛才計算的hash值)
- 在獲取了table數(shù)組的索引之后,會迭代鏈表漾肮,調(diào)用equals()方法檢查key的相等性厂抖,如果equals()方法返回true,get方法返回Entry對象的value克懊,否則忱辅,返回null七蜘。
四. ArrayList Vector LinkedList的 區(qū)別?
典型回答:
這三者都是實(shí)現(xiàn)集合框架中的List,也就是所謂的有序集合,因此具體功能也比較類似,比如都提供按照位置進(jìn)行定位添加或者刪除的操作,都提供迭代器以遍歷其內(nèi)容等。但因為具體的設(shè)計區(qū)別在行為性能線程安全等方面,表現(xiàn)又有很大不同墙懂。
Vector是Java早期提供的線程安全的動態(tài)數(shù)組,如果不需要線程安全,并不建議選擇,畢竟同步是有額外開銷的橡卤。Vector內(nèi) 部是使用對象數(shù)組來保存數(shù)據(jù),可以根據(jù)需要自動的增加容量,當(dāng)數(shù)組以滿時,會創(chuàng)建新的數(shù)組,并拷貝原有數(shù)組數(shù)據(jù)。
ArrayList是應(yīng)用更加廣泛的動態(tài)數(shù)組實(shí)現(xiàn)损搬,它本身不是線程安全的,所以性能要好很多碧库。與Vector近似,ArrayList也是可以根據(jù)需要調(diào)整容量,不過兩者的調(diào)整邏輯有所區(qū)別, Vector在擴(kuò)容時會提高1倍,而ArrayList則是增加50%巧勤。
LinkedList顧明思議是Java提供的雙向鏈表,所以它不需要像.上面兩種那樣調(diào)整容量嵌灰,它也不是線程安全的。
Vector和ArrayList作為動態(tài)數(shù)組,其內(nèi)部元素以數(shù)組形式順序存儲的颅悉,所以非常適合隨即訪問的場合沽瞭。除了尾部出入元素和刪除元素,往往性能會相對較差,比如我們在中間位置插入一個元素,需要移動后續(xù)所有元素。而LinkedList進(jìn)行節(jié)點(diǎn)插入刪除卻要高效得很多,但是隨即訪問性能則要比動態(tài)數(shù)組慢剩瓶。
文末福利
限于篇幅驹溃,今日份大廠面試真題到此為止;需要的更多的Java后端面試題延曙、視頻學(xué)習(xí)資料豌鹤、架構(gòu)師成長路線圖的朋友點(diǎn)擊下方傳送門, 即可免費(fèi)領(lǐng)取面試資料和視頻學(xué)習(xí)資料
傳送門
筆者整理的面試題包含但不限于Kafka枝缔、Mysql布疙、Tomcat、Docker魂仍、Spring拐辽、MyBatis、Nginx擦酌、Netty俱诸、Dubbo、Redis赊舶、Netty睁搭、Spring cloud、分布式笼平、高并發(fā)园骆、性能調(diào)優(yōu)、微服務(wù)等架構(gòu)技術(shù)
以下是筆者整理的部分面試題截圖