Java面經

1. Java裏面如何判斷一個對象是否存活

  • 引用計數算法
    • 爲每個對象設置一個對象引用計算器,每當有地方引用到這個對象時,計數器加一,每當引用失效的時候,該計數器就自動減一。任何時刻當該對象的引用變爲0的時候,說明該對象不再被引用。
    • 缺點:當objA和objB相互引用的時候,他們的引用計數器都是1,他們相互引用着對方,但實際上這兩個對象已經把不能被訪問了,於是引用計數器不能通知系統回收他們。
    • 可達性分析:算法基本思想是,通過一個根節點RootGC作爲一個起始點,從這個節點往下搜索,搜索所走過的路勁就是引用鏈,當一個對象到RootGC沒有引用鏈的時候,說明對象沒有被引用了。按照圖論的說法是既是一個GCRoot節點到某個節點不可達。
    • Java中可以用作GCRoot的對象:虛擬機棧中引用的對象,方法區中靜態屬性引用的對象,方法區中常量引用的對象,本地方法棧中引用的對象。
    • 是否當對象不可達的時候,對象就會馬上被回收?並不是。第一次系統檢測到RootGC節點不可達的時候,進行第一次的標記,然後系統檢查有沒有覆蓋finalize方法,如果有就執行finalize方法,如果該對象在finalize方法中與任何一個對象進行關聯的話便可以不會被回收。

轉載自:https://blog.csdn.net/peterchan88/article/details/52992795

2. Linux進程、線程間通信有哪幾種?

  • 管道(pipe):管道通信有兩種方式,一種是半雙工的通信,數據只能單向流動。二是只能在具有親緣關係的進程間使用,也就是父子進程之間
  • 信號量(semophore):信號量是一個計數器,可以用來控制多個進程堆共享資源的訪問。信號是比較複雜的通信方式,用於通知接受進程有某種事件發生,除了用於進程間通信外,進程還可以發送信號給進程本身;不能用於傳遞消息,只能用於同步
  • 消息隊列(message queue):消息隊列是由消息的鏈表,存放在內核中並由消息隊列標識符標識。消息隊列克服了信號傳遞信息少、管道只能承載無格式字節流以及緩衝區大小受限等缺點
  • 信號(signal):信號是一種比較複雜的通信方式,用於通知接收進程某個事件已經發生。主要作爲進程間以及同一進程不同線程之間的同步手段。
  • 共享內存(shared memory):共享內存就是映射一段能被其他進程所訪問的內存,這段共享內存由一個進程創建,但多個進程都可以訪問。共享內存是最快的 IPC 方式,它是針對其他進程間通信方式運行效率低而專門設計的。它往往與其他通信機制,如信號量,配合使用,來實現進程間的同步和通信。但是共享內存要注意讀寫問題
  • 套接字(socket):套解字也是一種進程間通信機制,與其他通信機制不同的是,它可用於不同機器間的進程通信

轉載自:https://blog.csdn.net/gatieme/article/details/50908749

3. tcp三次握手、四次揮手

  • TCP三次握手是建立連接,四次揮手是關閉連接。
  • TCP的概述:TCP把連接作爲最基本的對象,每一條TCP連接都有兩個端點,這種端點我們叫作套接字(socket),它的定義爲端口號拼接到IP地址即構成了套接字,例如,若IP地址爲192.3.4.16 而端口號爲80,那麼得到的套接字爲192.3.4.16:80。
  • TCP報文幾個字段的含義:
    • 同步SYN,在連接建立時用來同步序號。當SYN=1,ACK=0,表明是連接請求報文,若同意連接,則響應報文中應該使SYN=1,ACK=1
    • 確認ACK,僅當ACK=1時,確認號字段纔有效。TCP規定,在連接建立後所有報文的傳輸都必須把ACK置1
    • 確認號ack,佔4個字節,是期望收到對方下一個報文的第一個數據字節的序號。例如,B收到了A發送過來的報文,其序列號字段是501,而數據長度是200字節,這表明B正確的收到了A發送的到序號700爲止的數據。因此,B期望收到A的下一個數據序號是701,於B在發送給A的確認報文段中把確認號置爲701
    • 序號seq,佔4個字節,TCP連接中傳送的字節流中的每個字節都按順序編號。例如,一段報文的序號字段值是 301 ,而攜帶的數據共有100字段,顯然下一個報文段(如果還有的話)的數據序號應該從401開始
    • 終止FIN,用來釋放連接。當FIN=1,表明此報文的發送方的數據已經發送完畢,並且要求釋放;
  • TCP三次握手的主要過程:
    • TCP服務器進程先創建傳輸控制塊TCB,時刻準備接受客戶進程的連接請求,此時服務器進入了LISTEN(監聽)狀態。
    • TCP客戶進程也先創建傳輸控制塊TCB
    • 然後向服務器發出連接請求報文,這時,報文首部的同步位SYN=1,同時選擇一個初始序列號seq=xTCP規定,SYN=1的TCP報文不能攜帶數據,但是消耗一個序列號。TCP客戶端進入SYN-SENT(同步已發送狀態)。
    • TCP服務器如果收到報文後同意連接,則發出確認報文,SYN=1,ACK=1,ack=x+1,seq=y。在發送確認報文的時候,TCP服務器也需要爲自己初始化一個序列號。TCP服務器進入SYN-RCVD(同步收到狀態)。這個報文也不能攜帶數據,但是要消耗一個序列號。
    • TCP客戶進程收到確認後,還要向服務器給出確認。確認報文爲SYN=1,ACK=1,ack=y+1,seq=x+1。此時TCP連接建立,客戶端進入ESTABLISHED(已建立連接)狀態
    • 當服務器收到客戶端的確認後,也進入ESTABLISHED狀態
  • 問題:爲什麼最後客戶端還要再發送一次確認?
    答案:主要防止已經失效的連接請求報文突然又傳送到了服務器,從而產生錯誤。如果使用的是兩次握手建立連接,假設有這樣一種場景,客戶端發送了第一個請求連接並且沒有丟失,只是因爲在網絡結點中滯留的時間太長了,由於TCP的客戶端遲遲沒有收到確認報文,以爲服務器沒有收到,此時重新向服務器發送這條報文,此後客戶端和服務器經過兩次握手完成連接,傳輸數據,然後關閉連接。此時此前滯留的那一次請求連接,網絡通暢了到達了服務器,這個報文本該是失效的,但是,兩次握手的機制將會讓客戶端和服務器再次建立連接,這將導致不必要的錯誤和資源的浪費。
    如果採用的是三次握手,就算是那一次失效的報文傳送過來了,服務端接受到了那條失效報文並且回覆了確認報文,但是客戶端不會再次發出確認。由於服務器收不到確認,就知道客戶端並沒有請求連接。
  • TCP是全雙工連接,說以每個方向都必須單獨進行關閉。
  • TCP四次揮手過程:
    • 客戶端發出連接釋放報文,並且停止發送數據。釋放報文首部:FIN=1,seq=u。此時客戶端進入FIN-WAIT-1(終止等待1)狀態TCP規定,釋放報文即使不攜帶數據,也要消耗一個序列號。
    • 服務器收到連接釋放報文,發佈確認報文,ACK=1,ack=u+1,seq=v。此時服務端進入CLOSE-WAIT(關閉等待)狀態。TCP服務器通知高層的應用進程,客戶端向服務器的方向就釋放了,這時候處於半關閉狀態,即客戶端已經沒有數據要發送了,但是服務器若發送數據,客戶端依然要接受。這個狀態還要持續一段時間,也就是整個CLOSE-WAIT狀態持續的時間。
    • 客戶端收到服務器確認請求之後,此時客戶端進入FIN-WAIT-2(終止等待2)狀態,等待服務器發送連接釋放報文(在這之前還需要接受服務器發送的最後的數據)。
    • 服務器將最後的數據發送完畢之後,就向客戶端發送釋放報文:FIN=1,ack=u+1,假定此時的序列號爲seq=w,此時,服務器就進入了LAST-ACK(最後確認)狀態,等待客戶端的確認。
    • 客戶端收到服務器連接釋放報文後,必須發出確認,ACK=1,ack=w+1,squ=u+1。此時客戶端進入TIME-WAIT(時間等待)狀態。需要注意的是,此時的TCP連接還沒釋放,必須經過2∗MSL(最長報文段壽命)的時間後,當客戶端撤銷相應的TCB後,才進入CLOSED狀態。
    • 服務器只要收到確認報文之後,馬上進入CLOSED狀態。同樣,撤銷TCB後,就結束了這次的TCP連接。可以看到,服務器結束TCP連接的時間要比客戶端早一些。
  • 問題:爲什麼客戶端最後還要等待2MSL:
    • 保證客戶端發送的最後一個ACK報文能夠到達服務器,因爲這個ACK報文可能丟失,站在服務器的角度看來,我已經發送了FIN+ACK報文請求斷開了,客戶端還沒有給我回應,應該是我發送的請求斷開報文它沒有收到,於是服務器又會重新發送一次,而客戶端就能在這個2MSL時間段內收到這個重傳的報文,接着給出迴應報文,並且會重啓2MSL計時器。
    • 防止類似與“三次握手”中提到了的“已經失效的連接請求報文段”出現在本連接中。**客戶端發送完最後一個確認報文後,在這個2MSL時間中,就可以使本連接持續的時間內所產生的所有報文段都從網絡中消失。**這樣新的連接中不會出現舊連接的請求報文。
  • 問題:爲什麼連接是三次握手,關閉連接是四次揮手:
    • 因爲TCP是全雙工。關閉連接時,服務器收到對方的FIN報文,僅僅表示對方不再發送數據,但是對方還能接受數據,而自己並非全部數據都已經發送出去了。
  • 問題:如果已經建立連接,但是客戶端出現故障怎麼辦?
    • TCP還設有一個保活計時器,顯然,客戶端如果出現故障,服務器不能一直等下去,白白浪費資源。服務器每收到一次客戶端的請求後都會重新復位這個計時器,時間通常是設置爲2小時,若兩小時還沒有收到客戶端的任何數據,服務器就會發送一個探測報文段,以後每隔75分鐘發送一次。若一連發送10個探測報文仍然沒反應,服務器就認爲客戶端出了故障,接着就關閉連接。

轉載自:https://blog.csdn.net/qzcsu/article/details/72861891

4. 瀏覽器輸入一個網址,打開網頁,其中發生了什麼,用了什麼協議?

  • DNS域名解析:把輸入的網址變成ip,DNS域名解析的過程是首先我們知道我們本地的機器上在配置網絡時都會填寫DNS,這樣本機就會把這個url發給這個配置的DNS服務器,如果能夠找到相應的url則返回其ip,否則該DNS將繼續將該解析請求發送給上級DNS,整個DNS可以看做是一個樹狀結構,該請求將一直髮送到根直到得到結果。
  • 連接:當解析完之後,我們發送請求,首先要建立一個socket連接。因爲socket是通過ip和端口建立的,現在已經擁有了目標ip和端口號,這樣我們就可以打開socket連接了。http是一個應用層的協議,在這個層的協議,這是一種通訊規範,也就是因爲雙方要進行通訊,大家要事先約定一個標準。
  • 請求:連接成功建立後,開始向web服務器發送請求,這個請求一般是GET或POST命令(POST用於FORM參數的傳遞)。

5. 狀態碼301和302的區別:

  • 302重定向只是暫時的重定向,搜索引擎會抓取新的內容而保留舊的地址,因爲服務器返回302,所以,搜索搜索引擎認爲新的網址是暫時的
  • 301重定向是永久的重定向,搜索引擎在抓取新的內容的同時也將舊的網址替換爲了重定向之後的網址。

6. String,StringBuffer,StringBuilder之間的區別

  • String是字符串常量
  • StringBuffer是字符串變量,線程安全
  • StringBuilder是字符串變量,非線程安全
  • String 和StringBuffer之間的區別是:String是不可變的對象,因此每次對String對象進行修改都是生成新的String對象,然後將指針指向新的String。而對StringBuffer對象進行修改都是對對象本身進行操作。在某些特別情況下,String對象字符串的拼接其實被JVM解釋成StringBuffer對象拼接,所以這些時候,String對象的速度並不會比StringBuffer對象慢。
    以下例子:String a = "This is " + “an” + “apple”;
    StringBuffer b = new StringBuffer(“This is”).append(“an”).append(“apple”);
    這種情況下,生成a的速度比生成b的速度要快是因爲在JVM眼裏,a=“This is an apple”。如果你的字符串來自其他對象,速度就沒這麼快
  • 在運行速度方面一般情況下:StringBuilder>StringBuffer>String
  • 在線程安全方面:如果一個StringBuffer對象在字符串緩衝區被多個線程使用時,StringBuffer中很多方法可以帶有synchronized關鍵字,所以可以保證線程是安全的,但StringBuilder的方法則沒有該關鍵字,所以不能保證線程安全,有可能會出現一些錯誤的操作。所以如果要進行的操作是多線程的,那麼就要使用StringBuffer,但是在單線程的情況下,還是建議使用速度比較快的StringBuilder。

轉自:https://blog.csdn.net/rmn190/article/details/1492013
https://www.cnblogs.com/su-feng/p/6659064.html

7. JVM的內存分配和垃圾回收機制

  • JVM把內存劃分爲6個部分:PC寄存器(也叫程序計數器)、虛擬機棧、堆、方法區、運行時常量池、本地方法棧
    • PC寄存器(程序計數器):用於記錄當前線程運行時的位置,每一個線程都有一個獨立的程序計數器,線程的阻塞、恢復、掛起等一系列操作都需要程序計數器的參與,因此必須是線程私有的。
    • Java虛擬機棧:在創建線程時創建,用來存儲棧幀,因此也是線程私有的。Java程序中的方法在執行時,會創建一個棧幀,用於存儲方法運行時的臨時數據和中間結果,包括局部變量表、操作數棧、動態鏈接、方法出口等信息。如果棧的深度大於虛擬機允許的最大深度,則拋出StackOverflowError異常。
    • Java堆:java堆被所有線程共享。堆的主要作用是存儲對象。如果堆空間不夠,但擴展時又不能申請到足夠的內存時則拋出OutOfMemoryError異常。
    • 方法區:方法區被各個線程共享,用於存儲靜態變量、運行時常量池等信息。
    • 本地方法棧:本地方法棧的主要作用就是支持native方法,比如在java中調用C/C++。
  • GC回收機制
    • 哪些內存需要被回收:堆和方法區,堆和方法區的內存都是動態分配的,所以需要動態回收。這部分內存的回收依賴GC完成。
    • 什麼時候回收:引用計數法可達性分析。但是JVM只用可達性分析。
  • 怎麼回收
    • 標記-清除算法:遍歷所有的GC Root,分別標記可達的對象和不可達的對象。缺點:效率低,回收得到的內存不連貫。
    • 複製算法:將內存分爲兩塊,每次只使用一塊。當這一塊內存滿了,就將還存活的對象複製到另一塊上,並且嚴格按照內存地址排列,然後將已使用的那塊內存統一回收。優點:能夠得到連續的內存。缺點:浪費一半的內存。
    • 分代算法:在Java中,把內存中的對象按生命長短分爲新生代,老年代,永久代。新生代指局部變量這種不能存活太久的變量。老年代指一些生命週期長的。永久代指加載的class信息這種永遠存在的。新生代和老年代存儲在Java虛擬機的堆上,永久代存儲在方法區上
分代 回收方法
新生代 複製算法
老年代 標記-清除算法
永久代 ————————

轉自:https://blog.csdn.net/u010429424/article/details/77333311

8. Java中的深拷貝與淺拷貝

  • 淺拷貝:淺拷貝的按位拷貝對象,它會創建一個新對象,這個對象有着原始對象屬性值的一份精確拷貝。如果屬性是基本類型,拷貝的就是基本類型的值。如果屬性是內存地址,拷貝的就是內存地址。如果其中一個對象改變這個內存地址存儲的值,那麼就會影響另一個對象。
  • 深拷貝:深拷貝會拷貝屬性,並拷貝屬性指向的動態分配的內存。當對象和它所引用的對象一起拷貝時,發生深拷貝。深拷貝比淺拷貝速度慢並且花銷大。
  • 舉例
 class Body implements Cloneable{
    public Head head;
    public Body() {}
    public Body(Head head) {this.head = head;}

    @Override
    protected Object clone() throws CloneNotSupportedException {
        Body newBody =  (Body) super.clone();
        newBody.head = (Head) head.clone();
        return newBody;
    }

}
class Head implements Cloneable{
    public  Face face;

    public Head() {}
    public Head(Face face){this.face = face;}
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
} 

public class Main{
	public static void main(String[] args) throws CloneNotSupportedException {
  		Body body = new Body(new Head());
    	Body body1 = (Body) body.clone();
   		System.out.println("body == body1 : " + (body == body1) );
    	System.out.println("body.head == body1.head : " +  (body.head == body1.head));
	}
}

在上面的例子中原始的Body和拷貝的Body中的Head是不同的對象,但是因爲Head類裏面關於Face的拷貝函數不是深拷貝。示意圖如下:
在這裏插入圖片描述
轉自:https://blog.csdn.net/u014727260/article/details/55003402

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章