Java面試指南

雲水在掘金發表於2018-12-04

01、String 為什麼是 final


  1、String 類是一個不可變類,被 final 修改的類不能被繼承,這樣提高了 String 類使用的安全性。
  2、String 類的主要變數 value[]都被設計成private final 的,這樣在多執行緒時,對 String物件的訪問是可以保證安全。
  3、JVM 對 final 修飾的類進行了編譯優化,設計成 final,JVM 不用對相關方法在虛擬函式表中查詢,直接定位到 String 類的相關方法呼叫,提高了執行效率。


02、Class.forName 和 和 ClassLoader 的區別


  Class.forNameClassLoader 都是用來裝載類的,對於類的裝載一般為分三個階段載入、連結、編譯,它們裝載類的方式是有區別。
  首先看一下 Class.forName(..),forName(..)方法有一個過載方法 forName(className,boolean,ClassLoader)。 它有三個引數,第一個引數是類的包路徑,第二個引數是 boolean型別,為 true 地表示 Loading 時會進行初始化,第三個就是指定一個載入器;當你呼叫class.forName(..)時,預設呼叫的是有三個引數的過載方法,第二個引數預設傳入 true,第三個引數預設使用的是當前類載入時用的載入器。
  ClassLoader.loadClass()也有一個過載方法,從原始碼中可以看出它預設調的是它的過載方法 loadClass(name, false),當第二引數為 false 時,說明類載入時不會被連結。 這也是兩者之間最大區別,前者在載入的時候已經初始化,後者在載入的時候還沒有連結。如果你需要在載入時初始化一些東西,就要用 Class.forName 了,比如我們常用的驅動載入,實際上它的註冊動作就是在載入時的一個靜態塊中完成的。所以它不能被 ClassLoader 載入代替。


03、程式和執行緒的區別


image.png

  定義:程式是具有一定獨立功能的程式關於某個資料集合上的一次執行活動,程式是系統進行資源分配和排程的一個獨立單位。執行緒是程式的一個實體,是 CPU 排程和分派的基本單位,它是比程式更小的能獨立執行的基本單位。
  特點:
  1、一個程式可以擁有很多個執行緒,但每個執行緒只屬於一個程式。
  2、執行緒相對程式而言,劃分尺度更小,併發效能更高。
  3、程式在執行過程中擁有獨立的記憶體單元,而多個執行緒共享記憶體,從而極大地提高了程式的執行效率。
  4、執行緒必須依賴應用,在應用中排程,每個執行緒必須執行的入口、出口、執行序列,執行緒是不能夠獨立存在執行的。
  5、程式是資源分配的基本單位,執行緒是處理機排程的基本單位,所有的執行緒共享其所屬程式的所有資源與程式碼。
  6、多執行緒的意義在於一個應用程式中,有多個執行部分可以同時執行。但作業系統並沒有將多個執行緒看做多個獨立的應用,來實現程式的排程和管理以及資源分配。


04、Java 的引用型別有哪幾種


  Java 自從 JDK1.2 版本開始,引入四種引用的型別,它們由強到弱依次是:強引用(StongReference) 、軟引用(SoftReference)、弱引用(**WeakReference **)、虛引用(PhantomReference) ,它們的各自特點及使用領域。
  強引用: 程式碼中最常用看到的 Object o = new Object();這裡就是強引用,只要引用在,GC 就不回收它,如果 JVM 記憶體空間不足會丟擲 OutOfMemoryError。
  軟引用:通常用描述一些有用但不是必需的物件,如果 JVM 記憶體不足時,會被 GC,通常被用來作為一些快取模組的設計,而且不容易 OOM。
  弱引用:比軟引用還低階別的引用,軟引用一般是記憶體不足時回收,而弱引用只要被 GC 掃描執行緒發現就會回收掉,即便是 JVM 記憶體還充足的情況下。
  虛引用:如其名,虛無般的存在,完全不會影響物件的生命週期,如果一個物件僅持有虛引用,就如同沒有引用一樣,可能隨時被回收掉,一般會與強用佇列關聯使用,一般只用於物件回收的事件傳遞。


05、Http報文結構


image.png

image.png
  上圖為請求報文,包括“請求行①②③”、“請求頭部④”、“請求包體⑤”,請求行①②③之間是用空格隔開的,面試回答時撿主要的說,沒必要把所有的引數都介紹一下。
  請求報文包括 “請求行”“ 請 求頭部”“ 請求包體”;請求行中主要包括:請求方式、請求地址、Http 版本,它們之間用空格分開。請求頭部主要包括 : Accept: 告 訴 服 務 端 客 戶 端 接 受 什 麼 類 型 的 響 應 ;Accept-Language:客戶端可接受的自然語言;User-Agent:請求端的瀏覽器以及伺服器型別;Accept-Encoding:客戶端可接受的編碼壓縮格式; Accept-Charset:可接受的應答的字符集;Host:請求的主名,允許多個域名同處一個 IP 地址,即虛擬主機;connection:連線方式(close 或 keep-alive);Cookie:儲存於客戶端擴充套件欄位,向同一域名的服務端傳送屬於該域的 cookie;空行:最後一個請求頭之後是一個空行,傳送回車符和換行符,通知伺服器以下不再有請求頭;
  請求包體:也是請求正文,業務報文。
  響應報文包括**“狀態行”** 、“響應頭部”“響應包體”;狀態行中包括:Http 協議版本、狀態碼以及狀態碼描述(常用狀態碼及描述,500:伺服器內部錯誤,404:頁面找不到,200OK:表示請求成功返回,403:伺服器收到請求但拒絕服務;其它的 1xx,2xx,3xx,4xx,5xx系,大家可以網上查詢一下,蠻重要,能說出來就行)。
  響應頭部,Server:響應伺服器型別;Content-Type:響應資料的文件型別;Cache-Control:響應輸出到客戶端後,服務端通過該報文頭屬告訴客戶端如何控制響應內容的快取。
  響應包體,真正的業務報文,也就是請求期望的返回資料。


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,則認為傳輸完成。
  第二種:動態請求生成的檔案中往往不包含 Content-Length,往往是通過分塊傳輸入,伺服器不能預先判斷檔案大小,這裡要通過 Transfer-Encoding:chunked 模式來傳輸資料。Chunked 是按塊進行資料傳輸的,這時候就要根據 chunked 編碼來判斷,chunked 編碼的資料在最後有一個空 chunked 塊,表明本次傳輸資料結束。


07、TCP三次握手和四次揮手


image.png
image.png
  圖來源於網上,如果要看懂這個圖,先來了解一下幾個簡單的概念:SYN 表示建立連線,FIN 表示關閉連線,ACK 表示響應,序號是隨機產生的但作用很大,這裡不詳細說了,這幾個關鍵字在面試的時候有必要先解釋一下。
  TCP 建立連線和斷開連線的操作有幾個很重要的關鍵字,分別:SYN 表示請求建立連線、ACK 表示響應、FIN 表示關閉連線請求、隨機序列 會 隨傳送報文 的 位元組數增加 (SYN 、FIN 都) 算位的,即便沒有位元組傳送,序列也會增加)
  TCP 建立連線的三次握手, 第一次握手:主機 A 傳送位碼為 syn=1,隨機產生 seq =200的資料包到伺服器,主機 B 由 SYN=1 知道,A 要求建立聯機;
  第二次握手:主機 B 收到請求後要確認聯機資訊,向 A 傳送 ack 確認序列=(主機 A的 seq+1),syn=1,ack=1,隨機產生 seq=500 的包;
  第三次握手:主機 A 收到後檢查 ack 確認序列是否正確,即第一次傳送的 seq number+1,以及位碼 ack 是否為 1,若正確,主機 A 會再傳送 ack number=(主機 B 的seq+1),ack=1,主機B收到後確認 seq 值與 ack=1 則連線建立成功。
  【三次握手總結】 主機A發 syn 給主機B,主機B回 ack,syn ,主機 A 回 ack ,三次握手,連線成功。
  四次揮手, 第一次揮手:主機A傳送一個FIN,用來關閉客戶A到伺服器B的資料傳送。
  第二 次揮手:主機B收到這個FIN,它發回一個ACK,確認序號為收到的序號加 1。和SYN一樣,一個FIN將佔用一個序號。
  第三 次揮手:主機B關閉與主機A的連線,傳送一個 FIN 給主機 A。


08、執行緒啟動用 start 方法還是run


  下面是 JDK 中 start()方法的原始碼,可以看到 start()呼叫的是 start0(),而start0()是一個 native 方法,通過註釋可以知道,它的作用主要是為執行緒分配系統資源的;而 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方法建立一個 ThreadLocalMap 物件,並且完成相應的初始,將 value 值存放進去。後面再次呼叫將會直接從執行緒中獲取ThreadLocalMap 物件,然後將副本儲存進去。


10、JVM記憶體洩露的原因有哪些


  這個問題看似簡單,卻用一個問題考察了 JVM 很多個相關的知識點,回答這個問題你首先要了解 JVM 的結構、物件的分配與儲存、GC 的原理等,但你看到此的時候如果對上面的知識點還不是很熟悉的話,先翻開其相關知識點,之前都已經談到過,然後這個問題就容易回答了。
  JVM 結構上一般分為堆記憶體、棧記憶體、方法區,記憶體洩露可能會發生在任何一個位置;在 JVM 劃分上方法區通常劃給堆,所以這兩塊可以一起。而棧記憶體通常是用來存放普通變數和物件引用,回收速度速度快,一般不會造成記憶體洩露,一旦溢位通常是棧記憶體大小分配不合理,或者可能顯示的將物件空間分配到棧記憶體來追求效率造成的。下面我們重點探討堆記憶體的溢位(根據面試的場景來判斷有沒有談棧記憶體這塊)。
  參考: 首先造成 Java JVM 洩露的主要原因:JVM 未及時的對垃圾進行回收造成的;當物件失去引用且持續佔有記憶體或無用物件的記憶體得不到及時釋放,從而造成記憶體空間的浪費稱為記憶體洩漏。造成這種物件無法及時釋放導致記憶體洩露的原因,可以簡單的為歸分兩類。
  一是基 於設計方面 :1、對應用載入資料級別判斷失誤,從而導致 JVM 記憶體分配不合理(企業單機部署應用常見到)。2、應用請求的常連線設計,常連線會一直佔用後臺資源,不能及時釋放。3、資料庫操作時,存在很多耗時連線,導致大量資源不能釋放。4、大量的監聽設計等。
  二 是基於開發方面:1、大量靜態變數的使用(靜態變數的生成周期與應用一致),如果靜態引用指向的是集合或者資料,會一直佔用資源。2、不合理的方法使用,比如 jdk6之前的 substring 就可能導致記憶體洩露。3、資料庫連線未能及時關閉,剛工作不久的同學容易忽略。4、單例模式使用,單例通常用來載入資源資訊,但如果載入資訊裡有大量的集合、陣列等物件,這些資源會一直駐留記憶體中,不易釋放。5、在迴圈中建立複雜物件、一次性讀取載入大量資訊到記憶體中,都有可能造成記憶體洩露。


11、OO的設計原則


  物件導向設計原則通常歸結為五大類,
  第 一 “單 一職責原則” (SRP):一個設計元素只做一件事,不要隨意耦合,多管閒事;
  第二 “開 放 封閉 原則” (OCP):對變更關閉、對擴充套件開放,提倡基於介面設計,新的需要最好不要去變更已經完成的功能,可以靈活通過介面擴充套件新功能;
  第三 “里氏 替換原則” (LSP):子類可以替換父類並且出現在父類能夠出現的任何地方,這個也是提倡面向介面程式設計思想的;
  第四 “依賴 倒置原則” (DIP):要依賴於抽象,不要依賴於具體,簡單的說就是面對抽象程式設計,不要過於依賴於細節;
  第 五 “介面 隔離原則” (ISP):使用多個專門的介面比使用單個介面要好,在設計中不要把各種業務型別的東西放到一個介面中造成臃腫。


更多更新請關注........

Java面試指南

相關文章