
1.什么是線程局部變量?(答案)
線程局部變量是局限于線程內(nèi)部的變量,屬于線程自身所有,不在多個(gè)線程間共享。Java 提供 ThreadLocal 類來支持線程局部變量,是一種實(shí)現(xiàn)線程安全的方式。但是在管理環(huán)境下(如 web 服務(wù)器)使用線程局部變量的時(shí)候要特別小心,在這種情況下,工作線程的生命周期比任何應(yīng)用變量的生命周期都要長。任何線程局部變量一旦在工作完成后沒有釋放,Java 應(yīng)用就存在內(nèi)存泄露的風(fēng)險(xiǎn)。
2.用 wait-notify 寫一段代碼來解決生產(chǎn)者-消費(fèi)者問題?(答案)
請參考答案中的示例代碼。只要記住在同步塊中調(diào)用 wait() 和 notify()方法,如果阻塞,通過循環(huán)來測試等待條件。
3. 用 Java 寫一個(gè)線程安全的單例模式(Singleton)?(答案)
請參考答案中的示例代碼,這里面一步一步教你創(chuàng)建一個(gè)線程安全的 Java 單例類。當(dāng)我們說線程安全時(shí),意思是即使初始化是在多線程環(huán)境中,仍然能保證單個(gè)實(shí)例。Java 中,使用枚舉作為單例類是最簡單的方式來創(chuàng)建線程安全單例模式的方式。
4.Java 中 sleep 方法和 wait 方法的區(qū)別?(答案)
雖然兩者都是用來暫停當(dāng)前運(yùn)行的線程,但是 sleep() 實(shí)際上只是短暫停頓,因?yàn)樗粫尫沛i,而 wait() 意味著條件等待,這就是為什么該方法要釋放鎖,因?yàn)橹挥羞@樣,其他等待的線程才能在滿足條件時(shí)獲取到該鎖。
5.什么是不可變對象(immutable object)?Java 中怎么創(chuàng)建一個(gè)不可變對象?(答案)
不可變對象指對象一旦被創(chuàng)建,狀態(tài)就不能再改變。任何修改都會創(chuàng)建一個(gè)新的對象,如 String、Integer及其它包裝類。詳情參見答案,一步一步指導(dǎo)你在 Java 中創(chuàng)建一個(gè)不可變的類。
6.我們能創(chuàng)建一個(gè)包含可變對象的不可變對象嗎?
是的,我們是可以創(chuàng)建一個(gè)包含可變對象的不可變對象的,你只需要謹(jǐn)慎一點(diǎn),不要共享可變對象的引用就可以了,如果需要變化時(shí),就返回原對象的一個(gè)拷貝。最常見的例子就是對象中包含一個(gè)日期對象的引用。

數(shù)據(jù)類型和 Java 基礎(chǔ)面試問題
7.Java 中應(yīng)該使用什么數(shù)據(jù)類型來代表價(jià)格?(答案)
如果不是特別關(guān)心內(nèi)存和性能的話,使用BigDecimal,否則使用預(yù)定義精度的 double 類型。
8.怎么將 byte 轉(zhuǎn)換為 String?(答案)
可以使用 String 接收 byte[] 參數(shù)的構(gòu)造器來進(jìn)行轉(zhuǎn)換,需要注意的點(diǎn)是要使用的正確的編碼,否則會使用平臺默認(rèn)編碼,這個(gè)編碼可能跟原來的編碼相同,也可能不同。
9.Java 中怎樣將 bytes 轉(zhuǎn)換為 long 類型?
這個(gè)問題你來回答 :-)
10.我們能將 int 強(qiáng)制轉(zhuǎn)換為 byte 類型的變量嗎?如果該值大于 byte 類型的范圍,將會出現(xiàn)什么現(xiàn)象?
是的,我們可以做強(qiáng)制轉(zhuǎn)換,但是 Java 中 int 是 32 位的,而 byte 是 8 位的,所以,如果強(qiáng)制轉(zhuǎn)化是,int 類型的高 24 位將會被丟棄,byte 類型的范圍是從 -128 到 128。
11.存在兩個(gè)類,B 繼承 A,C 繼承 B,我們能將 B 轉(zhuǎn)換為 C 么?如 C = (C) B;(answer答案)
12.哪個(gè)類包含 clone 方法?是 Cloneable 還是 Object?(答案)
java.lang.Cloneable 是一個(gè)標(biāo)示性接口,不包含任何方法,clone 方法在 object 類中定義。并且需要知道 clone() 方法是一個(gè)本地方法,這意味著它是由 c 或 c++ 或 其他本地語言實(shí)現(xiàn)的。
13.Java 中 ++ 操作符是線程安全的嗎?(答案)
不是線程安全的操作。它涉及到多個(gè)指令,如讀取變量值,增加,然后存儲回內(nèi)存,這個(gè)過程可能會出現(xiàn)多個(gè)線程交差。
14.a = a + b 與 a += b 的區(qū)別(答案)
+= 隱式的將加操作的結(jié)果類型強(qiáng)制轉(zhuǎn)換為持有結(jié)果的類型。如果兩這個(gè)整型相加,如 byte、short 或者 int,首先會將它們提升到 int 類型,然后在執(zhí)行加法操作。如果加法操作的結(jié)果比 a 的最大值要大,則 a+b 會出現(xiàn)編譯錯誤,但是 a += b 沒問題,如下:
byte a = 127;
byte b = 127;
b = a + b; // error : cannot convert from int to byte
b += a; // ok
(譯者注:這個(gè)地方應(yīng)該表述的有誤,其實(shí)無論 a+b 的值為多少,編譯器都會報(bào)錯,因?yàn)?a+b 操作會將 a、b 提升為 int 類型,所以將 int 類型賦值給 byte 就會編譯出錯)
15.我能在不進(jìn)行強(qiáng)制轉(zhuǎn)換的情況下將一個(gè) double 值賦值給 long 類型的變量嗎?(答案)
不行,你不能在沒有強(qiáng)制類型轉(zhuǎn)換的前提下將一個(gè) double 值賦值給 long 類型的變量,因?yàn)?double 類型的范圍比 long 類型更廣,所以必須要進(jìn)行強(qiáng)制轉(zhuǎn)換。
16. 3*0.1 == 0.3 將會返回什么?true 還是 false?(答案)
false,因?yàn)橛行└↑c(diǎn)數(shù)不能完全精確的表示出來。
17.int 和 Integer 哪個(gè)會占用更多的內(nèi)存?(答案)
Integer 對象會占用更多的內(nèi)存。Integer 是一個(gè)對象,需要存儲對象的元數(shù)據(jù)。但是 int 是一個(gè)原始類型的數(shù)據(jù),所以占用的空間更少。
18.為什么 Java 中的 String 是不可變的(Immutable)?(answer答案)
Java 中的 String 不可變是因?yàn)?Java 的設(shè)計(jì)者認(rèn)為字符串使用非常頻繁,將字符串設(shè)置為不可變可以允許多個(gè)客戶端之間共享相同的字符串。更詳細(xì)的內(nèi)容參見答案。
19.我們能在 Switch 中使用 String 嗎?(answer答案)
從 Java 7 開始,我們可以在 switch case 中使用字符串,但這僅僅是一個(gè)語法糖。內(nèi)部實(shí)現(xiàn)在 switch 中使用字符串的 hash code。
20.Java 中的構(gòu)造器鏈?zhǔn)鞘裁矗?answer答案)
當(dāng)你從一個(gè)構(gòu)造器中調(diào)用另一個(gè)構(gòu)造器,就是Java 中的構(gòu)造器鏈。這種情況只在重載了類的構(gòu)造器的時(shí)候才會出現(xiàn)。

JVM 底層 與 GC(Garbage Collection) 的面試問題
21.64 位 JVM 中,int 的長度是多數(shù)?
Java 中,int 類型變量的長度是一個(gè)固定值,與平臺無關(guān),都是 32 位。意思就是說,在 32 位 和 64 位 的Java 虛擬機(jī)中,int 類型的長度是相同的。
22.Serial 與 Parallel GC之間的不同之處?(答案)
Serial 與 Parallel 在GC執(zhí)行的時(shí)候都會引起 stop-the-world。它們之間主要不同 serial 收集器是默認(rèn)的復(fù)制收集器,執(zhí)行 GC 的時(shí)候只有一個(gè)線程,而 parallel 收集器使用多個(gè) GC 線程來執(zhí)行。
23. 32 位和 64 位的 JVM,int 類型變量的長度是多數(shù)?(答案)
32 位和 64 位的 JVM 中,int 類型變量的長度是相同的,都是 32 位或者 4 個(gè)字節(jié)。
24.Java 中 WeakReference 與 SoftReference的區(qū)別?(答案)
雖然 WeakReference 與 SoftReference 都有利于提高 GC 和 內(nèi)存的效率,但是 WeakReference ,一旦失去最后一個(gè)強(qiáng)引用,就會被 GC 回收,而軟引用雖然不能阻止被回收,但是可以延遲到 JVM 內(nèi)存不足的時(shí)候。
25.WeakHashMap 是怎么工作的?(答案)
WeakHashMap 的工作與正常的 HashMap 類似,但是使用弱引用作為 key,意思就是當(dāng) key 對象沒有任何引用時(shí),key/value 將會被回收。
26.JVM 選項(xiàng) -XX:+UseCompressedOops 有什么作用?為什么要使用?(答案)
當(dāng)你將你的應(yīng)用從 32 位的 JVM 遷移到 64 位的 JVM 時(shí),由于對象的指針從 32 位增加到了 64 位,因此堆內(nèi)存會突然增加,差不多要翻倍。這也會對 CPU 緩存(容量比內(nèi)存小很多)的數(shù)據(jù)產(chǎn)生不利的影響。因?yàn)?,遷移到 64 位的 JVM 主要動機(jī)在于可以指定最大堆大小,通過壓縮 OOP 可以節(jié)省一定的內(nèi)存。通過 -XX:+UseCompressedOops 選項(xiàng),JVM 會使用 32 位的 OOP,而不是 64 位的 OOP。
27.怎樣通過 Java 程序來判斷 JVM 是 32 位 還是 64 位?(答案)
你可以檢查某些系統(tǒng)屬性如 sun.arch.data.model 或 os.arch 來獲取該信息。
28.32 位 JVM 和 64 位 JVM 的最大堆內(nèi)存分別是多數(shù)?(答案)
理論上說上 32 位的 JVM 堆內(nèi)存可以到達(dá) 2^32,即 4GB,但實(shí)際上會比這個(gè)小很多。不同操作系統(tǒng)之間不同,如 Windows 系統(tǒng)大約 1.5 GB,Solaris 大約 3GB。64 位 JVM允許指定最大的堆內(nèi)存,理論上可以達(dá)到 2^64,這是一個(gè)非常大的數(shù)字,實(shí)際上你可以指定堆內(nèi)存大小到 100GB。甚至有的 JVM,如 Azul,堆內(nèi)存到 1000G 都是可能的。
29.JRE、JDK、JVM 及 JIT 之間有什么不同?(答案)
JRE 代表 Java 運(yùn)行時(shí)(Java run-time),是運(yùn)行 Java 引用所必須的。JDK 代表 Java 開發(fā)工具(Java development kit),是 Java 程序的開發(fā)工具,如 Java 編譯器,它也包含 JRE。JVM 代表 Java 虛擬機(jī)(Java virtual machine),它的責(zé)任是運(yùn)行 Java 應(yīng)用。JIT 代表即時(shí)編譯(Just In Time compilation),當(dāng)代碼執(zhí)行的次數(shù)超過一定的閾值時(shí),會將 Java 字節(jié)碼轉(zhuǎn)換為本地代碼,如,主要的熱點(diǎn)代碼會被準(zhǔn)換為本地代碼,這樣有利大幅度提高 Java 應(yīng)用的性能。
30.解釋 Java 堆空間及 GC?(答案)
當(dāng)通過 Java 命令啟動 Java 進(jìn)程的時(shí)候,會為它分配內(nèi)存。內(nèi)存的一部分用于創(chuàng)建堆空間,當(dāng)程序中創(chuàng)建對象的時(shí)候,就從對空間中分配內(nèi)存。GC 是 JVM 內(nèi)部的一個(gè)進(jìn)程,回收無效對象的內(nèi)存用于將來的分配。

JVM 底層面試題及答案
31.你能保證 GC 執(zhí)行嗎?(答案)
不能,雖然你可以調(diào)用 System.gc() 或者 Runtime.gc(),但是沒有辦法保證 GC 的執(zhí)行。
32.怎么獲取 Java 程序使用的內(nèi)存?堆使用的百分比?
可以通過 java.lang.Runtime 類中與內(nèi)存相關(guān)方法來獲取剩余的內(nèi)存,總內(nèi)存及最大堆內(nèi)存。通過這些方法你也可以獲取到堆使用的百分比及堆內(nèi)存的剩余空間。
Runtime.freeMemory() 方法返回剩余空間的字節(jié)數(shù),Runtime.totalMemory() 方法總內(nèi)存的字節(jié)數(shù),Runtime.maxMemory() 返回最大內(nèi)存的字節(jié)數(shù)。
33.Java 中堆和棧有什么區(qū)別?(答案)
JVM 中堆和棧屬于不同的內(nèi)存區(qū)域,使用目的也不同。棧常用于保存方法幀和局部變量,而對象總是在堆上分配。棧通常都比堆小,也不會在多個(gè)線程之間共享,而堆被整個(gè) JVM 的所有線程共享。

關(guān)于內(nèi)存的的面試問題和答案
Java 基本概念面試題
34. “a==b”和”a.equals(b)”有什么區(qū)別?(答案)
如果 a 和 b 都是對象,則 a==b 是比較兩個(gè)對象的引用,只有當(dāng) a 和 b 指向的是堆中的同一個(gè)對象才會返回 true,而 a.equals(b) 是進(jìn)行邏輯比較,所以通常需要重寫該方法來提供邏輯一致性的比較。例如,String 類重寫 equals() 方法,所以可以用于兩個(gè)不同對象,但是包含的字母相同的比較。
35.a.hashCode() 有什么用?與 a.equals(b) 有什么關(guān)系?(答案)
hashCode() 方法是相應(yīng)對象整型的 hash 值。它常用于基于 hash 的集合類,如 Hashtable、HashMap、LinkedHashMap等等。它與 equals() 方法關(guān)系特別緊密。根據(jù) Java 規(guī)范,兩個(gè)使用 equal() 方法來判斷相等的對象,必須具有相同的 hash code。
36.final、finalize 和 finally 的不同之處?(答案)
final 是一個(gè)修飾符,可以修飾變量、方法和類。如果 final 修飾變量,意味著該變量的值在初始化后不能被改變。finalize 方法是在對象被回收之前調(diào)用的方法,給對象自己最后一個(gè)復(fù)活的機(jī)會,但是什么時(shí)候調(diào)用 finalize 沒有保證。finally 是一個(gè)關(guān)鍵字,與 try 和 catch 一起用于異常的處理。finally 塊一定會被執(zhí)行,無論在 try 塊中是否有發(fā)生異常。
37.Java 中的編譯期常量是什么?使用它又什么風(fēng)險(xiǎn)?
公共靜態(tài)不可變(public static final )變量也就是我們所說的編譯期常量,這里的 public 可選的。實(shí)際上這些變量在編譯時(shí)會被替換掉,因?yàn)榫幾g器知道這些變量的值,并且知道這些變量在運(yùn)行時(shí)不能改變。這種方式存在的一個(gè)問題是你使用了一個(gè)內(nèi)部的或第三方庫中的公有編譯時(shí)常量,但是這個(gè)值后面被其他人改變了,但是你的客戶端仍然在使用老的值,甚至你已經(jīng)部署了一個(gè)新的jar。為了避免這種情況,當(dāng)你在更新依賴 JAR 文件時(shí),確保重新編譯你的程序。

Java 集合框架的面試題
這部分也包含數(shù)據(jù)結(jié)構(gòu)、算法及數(shù)組的面試問題
38.List、Set、Map 和 Queue 之間的區(qū)別(答案)
List 是一個(gè)有序集合,允許元素重復(fù)。它的某些實(shí)現(xiàn)可以提供基于下標(biāo)值的常量訪問時(shí)間,但是這不是 List 接口保證的。Set 是一個(gè)無序集合。
39.poll() 方法和 remove() 方法的區(qū)別?
poll() 和 remove() 都是從隊(duì)列中取出一個(gè)元素,但是 poll() 在獲取元素失敗的時(shí)候會返回空,但是 remove() 失敗的時(shí)候會拋出異常。
40.Java 中 LinkedHashMap 和 PriorityQueue 的區(qū)別是什么?(答案)
PriorityQueue 保證最高或者最低優(yōu)先級的的元素總是在隊(duì)列頭部,但是 LinkedHashMap 維持的順序是元素插入的順序。當(dāng)遍歷一個(gè) PriorityQueue 時(shí),沒有任何順序保證,但是 LinkedHashMap 課保證遍歷順序是元素插入的順序。
41.ArrayList 與 LinkedList 的不區(qū)別?(答案)
最明顯的區(qū)別是 ArrrayList 底層的數(shù)據(jù)結(jié)構(gòu)是數(shù)組,支持隨機(jī)訪問,而 LinkedList 的底層數(shù)據(jù)結(jié)構(gòu)書鏈表,不支持隨機(jī)訪問。使用下標(biāo)訪問一個(gè)元素,ArrayList 的時(shí)間復(fù)雜度是 O(1),而 LinkedList 是 O(n)。更多細(xì)節(jié)的討論參見答案。
42.用哪兩種方式來實(shí)現(xiàn)集合的排序?(答案)
你可以使用有序集合,如 TreeSet 或 TreeMap,你也可以使用有順序的的集合,如 list,然后通過 Collections.sort() 來排序。
43.Java 中怎么打印數(shù)組?(answer答案)
你可以使用 Arrays.toString() 和 Arrays.deepToString() 方法來打印數(shù)組。由于數(shù)組沒有實(shí)現(xiàn) toString() 方法,所以如果將數(shù)組傳遞給 System.out.println() 方法,將無法打印出數(shù)組的內(nèi)容,但是 Arrays.toString() 可以打印每個(gè)元素。
44.Java 中的 LinkedList 是單向鏈表還是雙向鏈表?(答案)
是雙向鏈表,你可以檢查 JDK 的源碼。在 Eclipse,你可以使用快捷鍵 Ctrl + T,直接在編輯器中打開該類。
45.Java 中的 TreeMap 是采用什么樹實(shí)現(xiàn)的?(答案)
Java 中的 TreeMap 是使用紅黑樹實(shí)現(xiàn)的。
46. Hashtable 與 HashMap 有什么不同之處?(答案)
這兩個(gè)類有許多不同的地方,下面列出了一部分:
a) Hashtable 是 JDK 1 遺留下來的類,而 HashMap 是后來增加的。
b)Hashtable 是同步的,比較慢,但 HashMap 沒有同步策略,所以會更快。
c)Hashtable 不允許有個(gè)空的 key,但是 HashMap 允許出現(xiàn)一個(gè) null key。
更多的不同之處參見答案。
47.Java 中的 HashSet,內(nèi)部是如何工作的?(answer答案)
HashSet 的內(nèi)部采用 HashMap來實(shí)現(xiàn)。由于 Map 需要 key 和 value,所以所有 key 的都有一個(gè)默認(rèn) value。類似于 HashMap,HashSet 不允許重復(fù)的 key,只允許有一個(gè)null key,意思就是 HashSet 中只允許存儲一個(gè) null 對象。
48.寫一段代碼在遍歷 ArrayList 時(shí)移除一個(gè)元素?(答案)
該問題的關(guān)鍵在于面試者使用的是 ArrayList 的 remove() 還是 Iterator 的 remove()方法。這有一段示例代碼,是使用正確的方式來實(shí)現(xiàn)在遍歷的過程中移除元素,而不會出現(xiàn) ConcurrentModificationException 異常的示例代碼。
49.我們能自己寫一個(gè)容器類,然后使用 for-each 循環(huán)碼?
可以,你可以寫一個(gè)自己的容器類。如果你想使用 Java 中增強(qiáng)的循環(huán)來遍歷,你只需要實(shí)現(xiàn) Iterable 接口。如果你實(shí)現(xiàn) Collection 接口,默認(rèn)就具有該屬性。
50.ArrayList 和 HashMap 的默認(rèn)大小是多數(shù)?(答案)
在 Java 7 中,ArrayList 的默認(rèn)大小是 10 個(gè)元素,HashMap 的默認(rèn)大小是16個(gè)元素(必須是2的冪)。這就是 Java 7 中 ArrayList 和 HashMap 類的代碼片段:
// from ArrayList.java JDK 1.7
private static final int DEFAULT_CAPACITY = 10;
//from HashMap.java JDK 7
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16