01、String 為什么是 final
??1、String 類是一個不可變類,被 final 修改的類不能被繼承,這樣提高了 String 類使用的安全性。
??2、String 類的主要變量 value[]都被設計成private final 的,這樣在多線程時,對 String對象的訪問是可以保證安全。
??3、JVM 對 final 修飾的類進行了編譯優(yōu)化,設計成 final,JVM 不用對相關方法在虛函數表中查詢,直接定位到 String 類的相關方法調用,提高了執(zhí)行效率。
02、Class.forName 和 和 ClassLoader 的區(qū)別
??Class.forName 和 ClassLoader 都是用來裝載類的,對于類的裝載一般為分三個階段加載、鏈接、編譯,它們裝載類的方式是有區(qū)別。
??首先看一下 Class.forName(..),forName(..)方法有一個重載方法 forName(className,boolean,ClassLoader)。 它有三個參數,第一個參數是類的包路徑,第二個參數是 boolean類型,為 true 地表示 Loading 時會進行初始化,第三個就是指定一個加載器;當你調用class.forName(..)時,默認調用的是有三個參數的重載方法,第二個參數默認傳入 true,第三個參數默認使用的是當前類加載時用的加載器。
??ClassLoader.loadClass()也有一個重載方法,從源碼中可以看出它默認調的是它的重載方法 loadClass(name, false),當第二參數為 false 時,說明類加載時不會被鏈接。 這也是兩者之間最大區(qū)別,前者在加載的時候已經初始化,后者在加載的時候還沒有鏈接。如果你需要在加載時初始化一些東西,就要用 Class.forName 了,比如我們常用的驅動加載,實際上它的注冊動作就是在加載時的一個靜態(tài)塊中完成的。所以它不能被 ClassLoader 加載代替。
03、進程和線程的區(qū)別

??定義:進程是具有一定獨立功能的程序關于某個數據集合上的一次運行活動,進程是系統(tǒng)進行資源分配和調度的一個獨立單位。線程是進程的一個實體,是 CPU 調度和分派的基本單位,它是比進程更小的能獨立運行的基本單位。
??特點:
??1、一個進程可以擁有很多個線程,但每個線程只屬于一個進程。
??2、線程相對進程而言,劃分尺度更小,并發(fā)性能更高。
??3、進程在執(zhí)行過程中擁有獨立的內存單元,而多個線程共享內存,從而極大地提高了程序的運行效率。
??4、線程必須依賴應用,在應用中調度,每個線程必須執(zhí)行的入口、出口、執(zhí)行序列,線程是不能夠獨立存在運行的。
??5、進程是資源分配的基本單位,線程是處理機調度的基本單位,所有的線程共享其所屬進程的所有資源與代碼。
??6、多線程的意義在于一個應用程序中,有多個執(zhí)行部分可以同時執(zhí)行。但操作系統(tǒng)并沒有將多個線程看做多個獨立的應用,來實現進程的調度和管理以及資源分配。
04、Java 的引用類型有哪幾種
??Java 自從 JDK1.2 版本開始,引入四種引用的類型,它們由強到弱依次是:強引用(StongReference) 、軟引用(SoftReference)、弱引用(WeakReference )、虛引用(PhantomReference) ,它們的各自特點及使用領域。
??強引用: 代碼中最常用看到的 Object o = new Object();這里就是強引用,只要引用在,GC 就不回收它,如果 JVM 內存空間不足會拋出 OutOfMemoryError。
??軟引用:通常用描述一些有用但不是必需的對象,如果 JVM 內存不足時,會被 GC,通常被用來作為一些緩存模塊的設計,而且不容易 OOM。
??弱引用:比軟引用還低級別的引用,軟引用一般是內存不足時回收,而弱引用只要被 GC 掃描線程發(fā)現就會回收掉,即便是 JVM 內存還充足的情況下。
??虛引用:如其名,虛無般的存在,完全不會影響對象的生命周期,如果一個對象僅持有虛引用,就如同沒有引用一樣,可能隨時被回收掉,一般會與強用隊列關聯(lián)使用,一般只用于對象回收的事件傳遞。
05、Http報文結構


??上圖為請求報文,包括“請求行①②③”、“請求頭部④”、“請求包體⑤”,請求行①②③之間是用空格隔開的,面試回答時撿主要的說,沒必要把所有的參數都介紹一下。
??請求報文包括 “請求行” 、“ 請 求頭部” 、“ 請求包體”;請求行中主要包括:請求方式、請求地址、Http 版本,它們之間用空格分開。請求頭部主要包括 : Accept: 告 訴 服 務 端 客 戶 端 接 受 什 么 類 型 的 響 應 ;Accept-Language:客戶端可接受的自然語言;User-Agent:請求端的瀏覽器以及服務器類型;Accept-Encoding:客戶端可接受的編碼壓縮格式; Accept-Charset:可接受的應答的字符集;Host:請求的主名,允許多個域名同處一個 IP 地址,即虛擬主機;connection:連接方式(close 或 keep-alive);Cookie:存儲于客戶端擴展字段,向同一域名的服務端發(fā)送屬于該域的 cookie;空行:最后一個請求頭之后是一個空行,發(fā)送回車符和換行符,通知服務器以下不再有請求頭;
??請求包體:也是請求正文,業(yè)務報文。
??響應報文包括“狀態(tài)行” 、“響應頭部” 、“響應包體”;狀態(tài)行中包括:Http 協(xié)議版本、狀態(tài)碼以及狀態(tài)碼描述(常用狀態(tài)碼及描述,500:服務器內部錯誤,404:頁面找不到,200OK:表示請求成功返回,403:服務器收到請求但拒絕服務;其它的 1xx,2xx,3xx,4xx,5xx系,大家可以網上查找一下,蠻重要,能說出來就行)。
??響應頭部,Server:響應服務器類型;Content-Type:響應數據的文檔類型;Cache-Control:響應輸出到客戶端后,服務端通過該報文頭屬告訴客戶端如何控制響應內容的緩存。
??響應包體,真正的業(yè)務報文,也就是請求期望的返回數據。
06、Http 如何 處理長連接
??Http目前有兩個版本分別是 Http1.0 和 Http1.1,Http1.0 默認是短連接,如果需要長連接支持,需要加上Connection: Keep-alive;Http1.1 的版本默認是支持長連接請求的方式,可以在抓取的請求中看到 Connection: Keep-alive,如果不想用長連接,需要在報文首部加上 Connection:close;對于默認的長連接可以通過 Keep-Alive:timeout=N 進行超時時間設置。
??[追問]對長連接數據傳輸完成的識別:第一種:通過 Content-Length 指示的大小,如果傳的報文長度達到了 Content-Length,則認為傳輸完成。
??第二種:動態(tài)請求生成的文件中往往不包含 Content-Length,往往是通過分塊傳輸入,服務器不能預先判斷文件大小,這里要通過 Transfer-Encoding:chunked 模式來傳輸數據。Chunked 是按塊進行數據傳輸的,這時候就要根據 chunked 編碼來判斷,chunked 編碼的數據在最后有一個空 chunked 塊,表明本次傳輸數據結束。
07、TCP三次握手和四次揮手


??圖來源于網上,如果要看懂這個圖,先來了解一下幾個簡單的概念:SYN 表示建立連接,FIN 表示關閉連接,ACK 表示響應,序號是隨機產生的但作用很大,這里不詳細說了,這幾個關鍵字在面試的時候有必要先解釋一下。
??TCP 建立連接和斷開連接的操作有幾個很重要的關鍵字,分別:SYN 表示請求建立連接、ACK 表示響應、FIN 表示關閉連接請求、隨機序列 會 隨傳送報文 的 字節(jié)數增加 (SYN 、FIN 都) 算位的,即便沒有字節(jié)傳送,序列也會增加)。
??TCP 建立連接的三次握手, 第一次握手:主機 A 發(fā)送位碼為 syn=1,隨機產生 seq =200的數據包到服務器,主機 B 由 SYN=1 知道,A 要求建立聯(lián)機;
??第二次握手:主機 B 收到請求后要確認聯(lián)機信息,向 A 發(fā)送 ack 確認序列=(主機 A的 seq+1),syn=1,ack=1,隨機產生 seq=500 的包;
??第三次握手:主機 A 收到后檢查 ack 確認序列是否正確,即第一次發(fā)送的 seq number+1,以及位碼 ack 是否為 1,若正確,主機 A 會再發(fā)送 ack number=(主機 B 的seq+1),ack=1,主機B收到后確認 seq 值與 ack=1 則連接建立成功。
??【三次握手總結】 主機A發(fā) syn 給主機B,主機B回 ack,syn ,主機 A 回 ack ,三次握手,連接成功。
??四次揮手, 第一次揮手:主機A發(fā)送一個FIN,用來關閉客戶A到服務器B的數據傳送。
??第二 次揮手:主機B收到這個FIN,它發(fā)回一個ACK,確認序號為收到的序號加 1。和SYN一樣,一個FIN將占用一個序號。
??第三 次揮手:主機B關閉與主機A的連接,發(fā)送一個 FIN 給主機 A。
08、線程啟動用 start 方法還是run
??下面是 JDK 中 start()方法的源碼,可以看到 start()調用的是 start0(),而start0()是一個 native 方法,通過注釋可以知道,它的作用主要是為線程分配系統(tǒng)資源的;而 run 只是一個普通的方法,所以線程的啟動是通過 start 方法實現的。
public synchronized void start() {
if (threadStatus != 0 || this != me)
throw new IllegalThreadStateException();
group.add(this);
start0();
if (stopBeforeStart) {
stop0(throwableFromStop);
}
}
private native void start0();
09、ThreadLocal 的基本原理
??下面兩個問題是一個同學面試的時候遇到的,網上也能看到,問題不難但平時不留意也不太容易回答。1、每個線程的變量副本是存儲在哪里的?2、threadlocal 是何時初始化的?變量副本是如何為共享的那個變量賦值的?回答這樣的問題,建議大家看一下 JDK相關 threadlocal 部分的源碼,下面只引用部分源碼來解釋說明。
??ThreadLocal 并非是線程的本地實現,而是線程的本地變量,它歸附于具體的線程,為每個使用該變量的線程提供一個副本,每個線程都可以獨立的操作這個副本,在外面看來,貌似每個線程都有一份變量。線程的變量存在哪里,這里可以結果 ThreadLocal 的源碼說明,這里看一下 get 實現
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null)
return (T)e.value;
}
return setInitialValue();
}
??在 get 方法中,會先獲得當前線程對象,然后傳到 getMap()中獲取 ThreadLocalMap對象,我們要的變量副本是從 ThreadLocalMap 對象中取出來的,可見每個線程的變量副本是保存在 ThreadLocalMap 對象里,而跟一下代碼可以看到 ThreadLocalMap 是在 Thread中聲明實現的,所以 每個線程的變量副本就保 存 在相應線程的 ThreadLocalMap 對象中。
??第二個問題,可以理解 ThreadLocal 如何把變量的副本復制并且初始化的(聲明和初始化),這里看一下源碼中的 set 方法實現
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
??當第一次調用 set 方法時,獲取的 ThreadLocalMap 對象為空,這里會調用 createMap方法創(chuàng)建一個 ThreadLocalMap 對象,并且完成相應的初始,將 value 值存放進去。后面再次調用將會直接從線程中獲取ThreadLocalMap 對象,然后將副本保存進去。
10、JVM內存泄露的原因有哪些
??這個問題看似簡單,卻用一個問題考察了 JVM 很多個相關的知識點,回答這個問題你首先要了解 JVM 的結構、對象的分配與存儲、GC 的原理等,但你看到此的時候如果對上面的知識點還不是很熟悉的話,先翻開其相關知識點,之前都已經談到過,然后這個問題就容易回答了。
??JVM 結構上一般分為堆內存、棧內存、方法區(qū),內存泄露可能會發(fā)生在任何一個位置;在 JVM 劃分上方法區(qū)通常劃給堆,所以這兩塊可以一起。而棧內存通常是用來存放普通變量和對象引用,回收速度速度快,一般不會造成內存泄露,一旦溢出通常是棧內存大小分配不合理,或者可能顯示的將對象空間分配到棧內存來追求效率造成的。下面我們重點探討堆內存的溢出(根據面試的場景來判斷有沒有談棧內存這塊)。
??參考: 首先造成 Java JVM 泄露的主要原因:JVM 未及時的對垃圾進行回收造成的;當對象失去引用且持續(xù)占有內存或無用對象的內存得不到及時釋放,從而造成內存空間的浪費稱為內存泄漏。造成這種對象無法及時釋放導致內存泄露的原因,可以簡單的為歸分兩類。
??一是基 于設計方面 :1、對應用加載數據級別判斷失誤,從而導致 JVM 內存分配不合理(企業(yè)單機部署應用常見到)。2、應用請求的常連接設計,常連接會一直占用后臺資源,不能及時釋放。3、數據庫操作時,存在很多耗時連接,導致大量資源不能釋放。4、大量的監(jiān)聽設計等。
??二 是基于開發(fā)方面:1、大量靜態(tài)變量的使用(靜態(tài)變量的生成周期與應用一致),如果靜態(tài)引用指向的是集合或者數據,會一直占用資源。2、不合理的方法使用,比如 jdk6之前的 substring 就可能導致內存泄露。3、數據庫連接未能及時關閉,剛工作不久的同學容易忽略。4、單例模式使用,單例通常用來加載資源信息,但如果加載信息里有大量的集合、數組等對象,這些資源會一直駐留內存中,不易釋放。5、在循環(huán)中創(chuàng)建復雜對象、一次性讀取加載大量信息到內存中,都有可能造成內存泄露。
11、OO的設計原則
??面向對象設計原則通常歸結為五大類,
??第 一 “單 一職責原則” (SRP):一個設計元素只做一件事,不要隨意耦合,多管閑事;
??第二 “開 放 封閉 原則” (OCP):對變更關閉、對擴展開放,提倡基于接口設計,新的需要最好不要去變更已經完成的功能,可以靈活通過接口擴展新功能;
??第三 “里氏 替換原則” (LSP):子類可以替換父類并且出現在父類能夠出現的任何地方,這個也是提倡面向接口編程思想的;
??第四 “依賴 倒置原則” (DIP):要依賴于抽象,不要依賴于具體,簡單的說就是面對抽象編程,不要過于依賴于細節(jié);
??第 五 “接口 隔離原則” (ISP):使用多個專門的接口比使用單個接口要好,在設計中不要把各種業(yè)務類型的東西放到一個接口中造成臃腫。