1. 關(guān)于JVM瘤袖、JDK和JRE
JVM
- 含義: 運(yùn)行 Java 字節(jié)碼的虛擬機(jī)。
- 目的:針對(duì)不同系統(tǒng)的特定實(shí)現(xiàn)(Windows昂验,Linux捂敌,macOS),使用相同的字節(jié)碼既琴,它們都會(huì)給出相同的結(jié)果占婉。使一份程序運(yùn)行至不同平臺(tái)。字節(jié)碼和不同系統(tǒng)的 JVM 實(shí)現(xiàn)是 Java 語(yǔ)言“一次編譯甫恩,隨處可以運(yùn)行”的關(guān)鍵所在逆济。
如何理解Java字節(jié)碼
在 Java 中,JVM可以理解的代碼就叫做字節(jié)碼(即擴(kuò)展名為 .class 的文件)磺箕,它不面向任何特定的處理器奖慌,只面向虛擬機(jī)。Java 語(yǔ)言通過(guò)字節(jié)碼的方式松靡,在一定程度上解決了傳統(tǒng)解釋型語(yǔ)言執(zhí)行效率低的問(wèn)題简僧,同時(shí)又保留了解釋型語(yǔ)言可移植的特點(diǎn)。所以 Java 程序運(yùn)行時(shí)比較高效雕欺,而且岛马,由于字節(jié)碼并不針對(duì)一種特定的機(jī)器棉姐,因此,Java程序無(wú)須重新編譯便可在多種不同操作系統(tǒng)的計(jì)算機(jī)上運(yùn)行啦逆。
Java 程序從源代碼到運(yùn)行一般有下面兩步:
- 源代碼 ==> 字節(jié)碼文件
- JDK中的javac編譯成 .class文件
- 字節(jié)碼文件 ==> 可執(zhí)行的機(jī)器碼
- JVM 類加載器首先加載字節(jié)碼文件伞矩,然后通過(guò)解釋器逐行解釋執(zhí)行,這種方式的執(zhí)行速度會(huì)相對(duì)比較慢夏志。而且乃坤,有些方法和代碼塊是經(jīng)常需要被調(diào)用的(也就是所謂的熱點(diǎn)代碼),所以后面引進(jìn)了 JIT 編譯器盲镶,而JIT 屬于運(yùn)行時(shí)編譯侥袜。當(dāng) JIT 編譯器完成第一次編譯后蝌诡,其會(huì)將字節(jié)碼對(duì)應(yīng)的機(jī)器碼保存下來(lái)溉贿,下次可以直接使用。而我們知道浦旱,機(jī)器碼的運(yùn)行效率肯定是高于 Java 解釋器的宇色。這也解釋了我們?yōu)槭裁唇?jīng)常會(huì)說(shuō) Java 是編譯與解釋共存的語(yǔ)言。
- JDK 9引入了一種新的編譯模式AOT(Ahead of Time Compilation)颁湖,它是直接將字節(jié)碼編譯成機(jī)器碼宣蠕,這樣就避免了JIT預(yù)熱等各方面的開(kāi)銷。JDK支持分層編譯和AOT協(xié)作使用甥捺。但是 抢蚀,AOT 編譯器的編譯質(zhì)量是肯定比不上 JIT 編譯器的。
JDK 和 JRE
JDK (Java Development Kit)
功能齊全的Java SDK镰禾。等同于 JRE + 編譯器(javac)+ 工具(如javadoc和jdb)皿曲。能夠創(chuàng)建和編譯程序。
JRE (Java Runtime Environment)
Java運(yùn)行時(shí)環(huán)境吴侦。它是運(yùn)行已編譯 Java 程序所需的所有內(nèi)容的集合屋休,包括 Java虛擬機(jī)(JVM),Java類庫(kù)备韧,java命令和其他的一些基礎(chǔ)構(gòu)件劫樟。但是,它不能用于創(chuàng)建新程序织堂。
總結(jié)
要運(yùn)行一下 Java 程序的話叠艳,只需要安裝 JRE 就可以了。如果需要進(jìn)行一些 Java 編程方面的工作易阳,那么你就需要安裝JDK了附较。但是,這不是絕對(duì)的闽烙。有時(shí)翅睛,即使您不打算在計(jì)算機(jī)上進(jìn)行任何Java開(kāi)發(fā)声搁,仍然需要安裝JDK。例如捕发,如果要使用JSP部署Web應(yīng)用程序疏旨,那么從技術(shù)上講,您只是在應(yīng)用程序服務(wù)器中運(yùn)行Java程序扎酷。那你為什么需要JDK呢檐涝?因?yàn)閼?yīng)用程序服務(wù)器會(huì)將 JSP 轉(zhuǎn)換為 Java servlet,并且需要使用 JDK 來(lái)編譯 servlet法挨。
2. Java和C++的區(qū)別
- 都是面向?qū)ο蟮恼Z(yǔ)言谁榜,都支持封裝、繼承和多態(tài)
- Java 不提供指針來(lái)直接訪問(wèn)內(nèi)存凡纳,程序內(nèi)存更加安全
- Java 的類是單繼承的窃植,C++ 支持多重繼承;雖然 Java 的類不可以多繼承荐糜,但是接口可以多繼承巷怜。
- Java 有自動(dòng)內(nèi)存管理機(jī)制,不需要程序員手動(dòng)釋放無(wú)用內(nèi)存
3. 接口和抽象類的區(qū)別
- 接口的方法默認(rèn)是 public暴氏,所有方法在接口中不能有實(shí)現(xiàn)(Java 8 開(kāi)始接口方法可以有默認(rèn)實(shí)現(xiàn))延塑,而抽象類可以有非抽象的方法。
- 接口中除了static答渔、final變量关带,不能有其他變量,而抽象類中則不一定沼撕。
- 一個(gè)類可以實(shí)現(xiàn)多個(gè)接口宋雏,但只能實(shí)現(xiàn)一個(gè)抽象類。接口自己本身可以通過(guò)extends關(guān)鍵字?jǐn)U展多個(gè)接口端朵。
- 接口方法默認(rèn)修飾符是public好芭,抽象方法可以有public、protected和default這些修飾符(抽象方法就是為了被重寫所以不能使用private關(guān)鍵字修飾3迥亍)舍败。
- 從設(shè)計(jì)層面來(lái)說(shuō),抽象是對(duì)類的抽象敬拓,是一種模板設(shè)計(jì)邻薯,而接口是對(duì)行為的抽象,是一種行為的規(guī)范乘凸。
4. String StringBuffer 和 StringBuilder的差異
String
使用 final 關(guān)鍵字修飾字符數(shù)組來(lái)保存字符串厕诡,內(nèi)部持有一個(gè)final 類型的字符數(shù)組,所以 String 對(duì)象是不可變的营勤。
StringBuilder 與 StringBuffer 都繼承自 AbstractStringBuilder 類灵嫌,在 AbstractStringBuilder 中也是使用字符數(shù)組保存字符串char[]value 但是沒(méi)有用 final 關(guān)鍵字修飾壹罚,所以這兩種對(duì)象都是可變的。
StringBuffer
內(nèi)部方法加了同步鎖或者對(duì)調(diào)用的方法加了同步鎖寿羞,所以是線程安全的猖凛。
每次都會(huì)對(duì) StringBuffer 對(duì)象本身進(jìn)行操作,而不是生成新的對(duì)象并改變對(duì)象引用绪穆。
StringBuilder
沒(méi)有對(duì)方法進(jìn)行加同步鎖辨泳,所以是非線程安全的。
對(duì) String 類型進(jìn)行改變的時(shí)候玖院,都會(huì)生成一個(gè)新的 String 對(duì)象菠红,然后將指針指向新的 String 對(duì)象。
使用總結(jié)
- 操作少量的數(shù)據(jù): 適用String
- 單線程操作字符串緩沖區(qū)下操作大量數(shù)據(jù): 適用StringBuilder
- 多線程操作字符串緩沖區(qū)下操作大量數(shù)據(jù): 適用StringBuffer
5. == 與 equals 的區(qū)別
==
它的作用是判斷兩個(gè)對(duì)象的地址是不是相等难菌。即试溯,判斷兩個(gè)對(duì)象是不是同一個(gè)對(duì)象(基本數(shù)據(jù)類型==比較的是值,引用數(shù)據(jù)類型==比較的是內(nèi)存地址)扔傅。
equals
判斷兩個(gè)對(duì)象是否相等
- 類沒(méi)有覆蓋 equals() 方法耍共。則通過(guò) equals() 比較該類的兩個(gè)對(duì)象時(shí)烫饼,等價(jià)于通過(guò)“==”比較這兩個(gè)對(duì)象猎塞。
- 類覆蓋了 equals() 方法。一般杠纵,我們都覆蓋 equals() 方法來(lái)比較兩個(gè)對(duì)象的內(nèi)容是否相等荠耽;若它們的內(nèi)容相等,則返回 true (即比藻,認(rèn)為這兩個(gè)對(duì)象相等)铝量。
String 的 equals 方法說(shuō)明
- String 中的 equals 方法是被重寫過(guò)的银亲,因?yàn)?object 的 equals 方法是比較的對(duì)象的內(nèi)存地址务蝠,而 String 的 equals 方法比較的是對(duì)象的值轩拨。
- 當(dāng)創(chuàng)建 String 類型的對(duì)象時(shí),虛擬機(jī)會(huì)在常量池中查找有沒(méi)有已經(jīng)存在的值和要?jiǎng)?chuàng)建的值相同的對(duì)象淋肾,如果有就把它賦給當(dāng)前引用。如果沒(méi)有就在常量池中重新創(chuàng)建一個(gè) String 對(duì)象简识。
- Object的equals方法容易拋空指針異常,應(yīng)使用常量或確定有值的對(duì)象來(lái)調(diào)用 equals。
6. hashCode() 和 equals()
hashCode() 的作用
hashCode() 的作用是獲取哈希碼立由,也稱為散列碼弛房;它實(shí)際上是返回一個(gè)int整數(shù)荷逞。這個(gè)哈希碼的作用是確定該對(duì)象在哈希表中的索引位置。
equals上述已有說(shuō)明
更多可見(jiàn) Java hashCode() 和 equals()的若干問(wèn)題解答
7. 線程的狀態(tài)
由圖片可知:
- 線程創(chuàng)建之后它將處于 NEW(新建) 狀態(tài)斧抱,調(diào)用 start() 方法后開(kāi)始運(yùn)行,線程這時(shí)候處于** READY(可運(yùn)行)** 狀態(tài)掂恕∫绬可運(yùn)行狀態(tài)的線程獲得了 cpu 時(shí)間片(timeslice)后就處于** RUNNING(運(yùn)行)** 狀態(tài)。
- 當(dāng)線程執(zhí)行 wait()方法之后,線程進(jìn)入 WAITING(等待)狀態(tài)忙灼。進(jìn)入等待狀態(tài)的線程需要依靠其他線程的通知才能夠返回到運(yùn)行狀態(tài)该园,而 TIME_WAITING(超時(shí)等待) 狀態(tài)相當(dāng)于在等待狀態(tài)的基礎(chǔ)上增加了超時(shí)限制,比如通過(guò) sleep(long millis)方法或 wait(long millis)方法可以將 Java 線程置于TIMED WAITING 狀態(tài)青瀑。當(dāng)超時(shí)時(shí)間到達(dá)后 Java 線程將會(huì)返回到 RUNNABLE 狀態(tài)。當(dāng)線程調(diào)用同步方法時(shí)及刻,在沒(méi)有獲取到鎖的情況下骆莹,線程將會(huì)進(jìn)入到** BLOCKED(阻塞)** 狀態(tài)傅联。線程在執(zhí)行 Runnable 的run()方法之后將會(huì)進(jìn)入到 TERMINATED(終止) 狀態(tài)。
8. Java關(guān)鍵字
可見(jiàn) Java 關(guān)鍵字總結(jié)
9. Arraylist 與 LinkedList 區(qū)別
- 底層結(jié)構(gòu)
- ArrayList: 底層使用的是 Object 數(shù)組
- LinkedList: 底層使用的是 雙向鏈表 數(shù)據(jù)結(jié)構(gòu)(JDK1.6之前為循環(huán)鏈表貌嫡,JDK1.7取消了循環(huán)比驻。)
- 是否保證線程安全
- ArrayList 和 LinkedList 都是不同步的,不保證線程安全岛抄。
- 操作時(shí)間復(fù)雜度
- ArrayList 采用數(shù)組存儲(chǔ)嫁艇,所以插入和刪除元素的時(shí)間復(fù)雜度受元素位置的影響。 比如:執(zhí)行add(E e) 方法的時(shí)候弦撩, ArrayList 會(huì)默認(rèn)在將指定的元素追加到此列表的末尾步咪,這種情況時(shí)間復(fù)雜度就是O(1)。但是如果要在指定位置 i 插入和刪除元素的話(add(int index, E element) )時(shí)間復(fù)雜度就為 O(n-i)益楼。因?yàn)樵谶M(jìn)行上述操作的時(shí)候集合中第 i 和第 i 個(gè)元素之后的(n-i)個(gè)元素都要執(zhí)行向后位/向前移一位的操作爽冕。
- LinkedList 采用鏈表存儲(chǔ),所以插入赌蔑,刪除元素時(shí)間復(fù)雜度不受元素位置的影響拌蜘,都是近似 O(1)而數(shù)組為近似 O(n)。
- 是否支持快速隨機(jī)訪問(wèn)(快速隨機(jī)訪問(wèn)就是通過(guò)元素的序號(hào)快速獲取元素對(duì)象(對(duì)應(yīng)于get(int index) 方法))
具有快速隨機(jī)訪問(wèn)主要看是否實(shí)現(xiàn)了 RandomAccess 接口陪竿,但是源碼 RandomAccess 接口中什么都沒(méi)有定義禽翼。所以, RandomAccess 接口是一個(gè)標(biāo)識(shí)族跛, 標(biāo)識(shí)實(shí)現(xiàn)這個(gè)接口的類具有隨機(jī)訪問(wèn)功能闰挡。
- ArrayList 支持
- ArrayList 底層是數(shù)組,而 LinkedList 底層是鏈表礁哄。數(shù)組天然支持隨機(jī)訪問(wèn)长酗,時(shí)間復(fù)雜度為 O(1),所以稱為快速隨機(jī)訪問(wèn)桐绒。
- 鏈表需要遍歷到特定位置才能訪問(wèn)特定位置的元素夺脾,時(shí)間復(fù)雜度為 O(n),所以不支持快速隨機(jī)訪問(wèn)茉继。
- ArrayList 實(shí)現(xiàn)了 RandomAccess 接口咧叭,就表明了他具有快速隨機(jī)訪問(wèn)功能。 RandomAccess 接口只是標(biāo)識(shí)烁竭,并不是說(shuō) ArrayList 實(shí)現(xiàn) RandomAccess 接口才具有快速隨機(jī)訪問(wèn)功能的菲茬!
- LinkedList 不支持
- 內(nèi)存空間占用
- ArrayList的空間浪費(fèi)主要體現(xiàn)在在list列表的結(jié)尾會(huì)預(yù)留一定的容量空間。
- LinkedList的空間花費(fèi)則體現(xiàn)在它的每一個(gè)元素都需要消耗比ArrayList更多的空間(因?yàn)橐娣胖苯雍罄^和直接前驅(qū)以及數(shù)據(jù))。
10. HashMap 和 Hashtable 的區(qū)別
- 線程是否安全
- HashMap 是非線程安全的生均, 因?yàn)榫€程安全的問(wèn)題听想,HashMap 要比 HashTable 效率高一點(diǎn)。當(dāng)需要多線程操作的時(shí)候可以使用線程安全的ConcurrentHashMap马胧。ConcurrentHashMap雖然也是線程安全的汉买,但是它的效率比Hashtable要高好多倍。因?yàn)镃oncurrentHashMap使用了分段鎖佩脊,并不對(duì)整個(gè)數(shù)據(jù)進(jìn)行鎖定蛙粘。
- HashTable 是線程安全的;HashTable 內(nèi)部的方法基本都經(jīng)過(guò)synchronized 修飾威彰。另外出牧,現(xiàn)在HashTable 基本被淘汰,基本都會(huì)使用HashMap
- 對(duì)Null key 和Null value的支持
- HashMap 中歇盼,null 可以作為鍵舔痕,這樣的鍵只有一個(gè),可以有一個(gè)或多個(gè)鍵所對(duì)應(yīng)的值為 null豹缀。
- HashTable 中 put 進(jìn)的鍵值只要有一個(gè) null伯复,直接拋出 NullPointerException。
- 底層數(shù)據(jù)結(jié)構(gòu)
- JDK1.8 之前 HashMap 底層是 數(shù)組和鏈表 結(jié)合在一起使用也就是 鏈表散列邢笙。
- HashTable 底層是一個(gè)單向鏈表
- 初始容量大小和每次擴(kuò)充容量大小的不同
- 創(chuàng)建時(shí)如果不指定容量初始值:
- HashMap 默認(rèn)的初始化大小為16啸如。之后每次擴(kuò)充,容量變?yōu)樵瓉?lái)的2倍氮惯。
- Hashtable 默認(rèn)的初始大小為11叮雳,之后每次擴(kuò)充,容量變?yōu)樵瓉?lái)的2n+1妇汗。
- 創(chuàng)建時(shí)如果給定了容量初始值:
- HashMap 會(huì)將其擴(kuò)充為2的冪次方大辛辈弧(HashMap 中的tableSizeFor()方法保證,下面給出了源代碼)铛纬。也就是說(shuō) HashMap 總是使用2的冪作為哈希表的大小,后面會(huì)介紹到為什么是2的冪次方厌均。
- Hashtable 會(huì)直接使用你給定的大小。
備注
關(guān)于詳細(xì)分析HashMap 可見(jiàn): Java 8系列之重新認(rèn)識(shí)HashMap
11. 如何理解分析Java內(nèi)存區(qū)域
Java 虛擬機(jī)在執(zhí)行 Java 程序的過(guò)程中會(huì)把它管理的內(nèi)存劃分成若干個(gè)不同的數(shù)據(jù)區(qū)域告唆。
- 線程私有的:
- 程序計(jì)數(shù)器
用于記錄當(dāng)前線程執(zhí)行的位置 (唯一一個(gè)不會(huì)出現(xiàn)OutOfMemoryError的內(nèi)存區(qū)域,它的生命周期隨著線程的創(chuàng)建而創(chuàng)建晶密,隨著線程的結(jié)束而死亡擒悬。) - 虛擬機(jī)棧(棧內(nèi)存)
Java 方法執(zhí)行的內(nèi)存模型、編譯器可知的各種數(shù)據(jù)類型稻艰。 - 本地方法棧
虛擬機(jī)使用到的 Native 方法服務(wù)懂牧。
- 程序計(jì)數(shù)器
- 線程共享的:
- 堆
存放對(duì)象實(shí)例,幾乎所有的對(duì)象實(shí)例以及數(shù)組都在這里分配內(nèi)存。 - 方法區(qū)
是各個(gè)線程共享的內(nèi)存區(qū)域僧凤,用于存儲(chǔ)已被虛擬機(jī)加載的類信息畜侦、常量、靜態(tài)變量躯保、即時(shí)編譯器編譯后的代碼等數(shù)據(jù)旋膳。 - 直接內(nèi)存
頻繁地使用的內(nèi)存部分
- 堆
詳情可見(jiàn): 可能是把 Java 內(nèi)存區(qū)域講的最清楚的一篇文章
12. Thread sleep() 和 wait() 方法的區(qū)別
- 兩者最主要的區(qū)別在于:sleep 方法沒(méi)有釋放鎖,而 wait 方法釋放了鎖 途事。
- 兩者都可以暫停線程的執(zhí)行验懊。
- sleep()是線程線程類(Thread)的方法,調(diào)用會(huì)暫停此線程指定的時(shí)間尸变,但監(jiān)控依然保持义图,不會(huì)釋放對(duì)象鎖,到時(shí)間自動(dòng)恢復(fù)召烂,或者可以使用wait(long timeout)超時(shí)后線程會(huì)自動(dòng)蘇醒碱工;wait()是Object的方法,調(diào)用會(huì)放棄對(duì)象鎖奏夫,進(jìn)入等待隊(duì)列怕篷,線程不會(huì)自動(dòng)蘇醒,需要?jiǎng)e的線程調(diào)用同一個(gè)對(duì)象上的 notify() 或者 notifyAll() 方法桶蛔。
- Wait 通常被用于線程間交互/通信匙头,sleep 通常被用于暫停執(zhí)行。