1. 程式人生 > >JAVA面試必備的知識寶典(三)

JAVA面試必備的知識寶典(三)

java

數據類型相關

java中int char,long各占多少字節?

|類型|位數|字節數| |-|-|-| |short|2|16| |int|4|32| |long|8|64| |float|4|32 |double|8|64| |char|2|16|

64位的JVM當中,int的長度是多少?

Java 中,int 類型變量的長度是一個固定值,與平臺無關,都是 32 位。意思就是說,在 32 位 和 64 位 的Java 虛擬機中,int 類型的長度是相同的。

java int和Integer的區別

Integer是int的包裝類型,在拆箱和裝箱中,而知自動轉換.int是基本類型,直接存數值,而integer是對象,用一個引用指向這個對象.

int 和Integer誰占用的內存更多?

Integer 對象會占用更多的內存。Integer是一個對象,需要存儲對象的元數據。但是 int 是一個原始類型的數據,所以占用的空間更少。

String,StringBuffer和StringBuilder區別

String是字符串常量,final修飾;StringBuffer字符串變量(線程安全); StringBuilder 字符串變量(線程不安全).

String和StringBuffer

String和StringBuffer主要區別是性能:String是不可變對象,每次對String類型進行操作都等同於產生了一個新的String對象,然後指向新的String對象.所以盡量不在對String進行大量的拼接操作,否則會產生很多臨時對象,導致GC開始工作,影響系統性能.

StringBuffer是對對象本身操作,而不是產生新的對象,因此在通常在有大量拼接的情況下我們建議使用StringBuffer.

但是需要註意現在JVM會對String拼接做一定的優化:String s=“This is only ”+”simple”+”test”會被虛擬機直接優化成String s=“This is only simple test”,此時就不存在拼接過程.

StringBuffer和StringBuilder

StringBuffer是線程安全的可變字符串,其內部實現是可變數組.StringBuilder是java 5.0新增的,其功能和StringBuffer類似,但是非線程安全.因此,在沒有多線程問題的前提下,使用StringBuilder會取得更好的性能.

什麽是編譯器常量?使用它有什麽風險?

公共靜態不可變(public static final )變量也就是我們所說的編譯期常量,這裏的 public 可選的。實際上這些變量在編譯時會被替換掉,因為編譯器知道這些變量的值,並且知道這些變量在運行時不能改變。這種方式存在的一個問題是你使用了一個內部的或第三方庫中的公有編譯時常量,但是這個值後面被其他人改變了,但是你的客戶端仍然在使用老的值,甚至你已經部署了一個新的jar。為了避免這種情況,當你在更新依賴 JAR 文件時,確保重新編譯你的程序。

java當中使用什麽類型表示價格比較好?

如果不是特別關心內存和性能的話,使用BigDecimal,否則使用預定義精度的 double 類型。

如何將byte轉為String

可以使用 String 接收 byte[] 參數的構造器來進行轉換,需要註意的點是要使用的正確的編碼,否則會使用平臺默認編碼,這個編碼可能跟原來的編碼相同,也可能不同。

可以將int強轉為byte類型麽?會產生什麽問題?

我們可以做強制轉換,但是Java中int是32位的而byte是8 位的,所以,如果強制轉化int類型的高24位將會被丟棄,byte 類型的範圍是從-128.到128

關於垃圾回收

你知道哪些垃圾回收算法?

垃圾回收從理論上非常容易理解,具體的方法有以下幾種:

標記-清除

標記-復制

標記-整理

分代回收 更詳細的內容參見深入理解垃圾回收算法

如何判斷一個對象是否應該被回收

這就是所謂的對象存活性判斷,常用的方法有兩種:1.引用計數法;2:對象可達性分析.由於引用計數法存在互相引用導致無法進行GC的問題,所以目前JVM虛擬機多使用對象可達性分析算法.

簡單的解釋一下垃圾回收

Java 垃圾回收機制最基本的做法是分代回收。內存中的區域被劃分成不同的世代,對象根據其存活的時間被保存在對應世代的區域中。一般的實現是劃分成3個世代:年輕、年老和永久。內存的分配是發生在年輕世代中的。當一個對象存活時間足夠長的時候,它就會被復制到年老世代中。對於不同的世代可以使用不同的垃圾回收算法。進行世代劃分的出發點是對應用中對象存活時間進行研究之後得出的統計規律。一般來說,一個應用中的大部分對象的存活時間都很短。比如局部變量的存活時間就只在方法的執行過程中。基於這一點,對於年輕世代的垃圾回收算法就可以很有針對性.

調用System.gc()會發生什麽?

通知GC開始工作,但是GC真正開始的時間不確定.

進程,線程相關

說說進程,線程,協程之間的區別

簡而言之,進程是程序運行和資源分配的基本單位,一個程序至少有一個進程,一個進程至少有一個線程.進程在執行過程中擁有獨立的內存單元,而多個線程共享內存資源,減少切換次數,從而效率更高.線程是進程的一個實體,是cpu調度和分派的基本單位,是比程序更小的能獨立運行的基本單位.同一進程中的多個線程之間可以並發執行.

你了解守護線程嗎?它和非守護線程有什麽區別

程序運行完畢,jvm會等待非守護線程完成後關閉,但是jvm不會等待守護線程.守護線程最典型的例子就是GC線程

什麽是多線程上下文切換

多線程的上下文切換是指CPU控制權由一個已經正在運行的線程切換到另外一個就緒並等待獲取CPU執行權的線程的過程。

創建兩種線程的方式?他們有什麽區別?

通過實現java.lang.Runnable或者通過擴展java.lang.Thread類.相比擴展Thread,實現Runnable接口可能更優.原因有二:

Java不支持多繼承.因此擴展Thread類就代表這個子類不能擴展其他類.而實現Runnable接口的類還可能擴展另一個類.

類可能只要求可執行即可,因此集成整個Thread類的開銷過大.

Runnable和Callable的區別

Runnable接口中的run()方法的返回值是void,它做的事情只是純粹地去執行run()方法中的代碼而已;Callable接口中的call()方法是有返回值的,是一個泛型,和Future、FutureTask配合可以用來獲取異步執行的結果。 這其實是很有用的一個特性,因為多線程相比單線程更難、更復雜的一個重要原因就是因為多線程充滿著未知性,某條線程是否執行了?某條線程執行了多久?某條線程執行的時候我們期望的數據是否已經賦值完畢?無法得知,我們能做的只是等待這條多線程的任務執行完畢而已。而Callable+Future/FutureTask卻可以獲取多線程運行的結果,可以在等待時間太長沒獲取到需要的數據的情況下取消該線程的任務,真的是非常有用。

什麽導致線程阻塞

阻塞指的是暫停一個線程的執行以等待某個條件發生(如某資源就緒),學過操作系統的同學對它一定已經很熟悉了。Java 提供了大量方法來支持阻塞,下面讓我們逐一分析。

方法說明

sleep()sleep() 允許 指定以毫秒為單位的一段時間作為參數,它使得線程在指定的時間內進入阻塞狀態,不能得到CPU 時間,指定的時間一過,線程重新進入可執行狀態。 典型地,sleep() 被用在等待某個資源就緒的情形:測試發現條件不滿足後,讓線程阻塞一段時間後重新測試,直到條件滿足為止

suspend() 和 resume()兩個方法配套使用,suspend()使得線程進入阻塞狀態,並且不會自動恢復,必須其對應的resume() 被調用,才能使得線程重新進入可執行狀態。典型地,suspend() 和 resume() 被用在等待另一個線程產生的結果的情形:測試發現結果還沒有產生後,讓線程阻塞,另一個線程產生了結果後,調用 resume() 使其恢復。

yield()yield() 使得線程放棄當前分得的 CPU 時間,但是不使線程阻塞,即線程仍處於可執行狀態,隨時可能再次分得 CPU 時間。調用 yield() 的效果等價於調度程序認為該線程已執行了足夠的時間從而轉到另一個線程

wait() 和 notify()兩個方法配套使用,wait() 使得線程進入阻塞狀態,它有兩種形式,一種允許 指定以毫秒為單位的一段時間作為參數,另一種沒有參數,前者當對應的 notify() 被調用或者超出指定時間時線程重新進入可執行狀態,後者則必須對應的 notify() 被調用.

wait(),notify()和suspend(),resume()之間的區別

初看起來它們與 suspend() 和 resume() 方法對沒有什麽分別,但是事實上它們是截然不同的。區別的核心在於,前面敘述的所有方法,阻塞時都不會釋放占用的鎖(如果占用了的話),而這一對方法則相反。上述的核心區別導致了一系列的細節上的區別。

首先,前面敘述的所有方法都隸屬於 Thread 類,但是這一對卻直接隸屬於 Object 類,也就是說,所有對象都擁有這一對方法。初看起來這十分不可思議,但是實際上卻是很自然的,因為這一對方法阻塞時要釋放占用的鎖,而鎖是任何對象都具有的,調用任意對象的 wait() 方法導致線程阻塞,並且該對象上的鎖被釋放。而調用 任意對象的notify()方法則導致因調用該對象的 wait() 方法而阻塞的線程中隨機選擇的一個解除阻塞(但要等到獲得鎖後才真正可執行)。

其次,前面敘述的所有方法都可在任何位置調用,但是這一對方法卻必須在 synchronized 方法或塊中調用,理由也很簡單,只有在synchronized 方法或塊中當前線程才占有鎖,才有鎖可以釋放。同樣的道理,調用這一對方法的對象上的鎖必須為當前線程所擁有,這樣才有鎖可以釋放。因此,這一對方法調用必須放置在這樣的 synchronized 方法或塊中,該方法或塊的上鎖對象就是調用這一對方法的對象。若不滿足這一條件,則程序雖然仍能編譯,但在運行時會出現IllegalMonitorStateException 異常。

wait() 和 notify() 方法的上述特性決定了它們經常和synchronized 方法或塊一起使用,將它們和操作系統的進程間通信機制作一個比較就會發現它們的相似性:synchronized方法或塊提供了類似於操作系統原語的功能,它們的執行不會受到多線程機制的幹擾,而這一對方法則相當於 block 和wakeup 原語(這一對方法均聲明為 synchronized)。它們的結合使得我們可以實現操作系統上一系列精妙的進程間通信的算法(如信號量算法),並用於解決各種復雜的線程間通信問題。

關於 wait() 和 notify() 方法最後再說明兩點: 第一:調用 notify() 方法導致解除阻塞的線程是從因調用該對象的 wait() 方法而阻塞的線程中隨機選取的,我們無法預料哪一個線程將會被選擇,所以編程時要特別小心,避免因這種不確定性而產生問題。

第二:除了 notify(),還有一個方法 notifyAll() 也可起到類似作用,唯一的區別在於,調用 notifyAll() 方法將把因調用該對象的 wait() 方法而阻塞的所有線程一次性全部解除阻塞。當然,只有獲得鎖的那一個線程才能進入可執行狀態。

談到阻塞,就不能不談一談死鎖,略一分析就能發現,suspend() 方法和不指定超時期限的 wait() 方法的調用都可能產生死鎖。遺憾的是,Java 並不在語言級別上支持死鎖的避免,我們在編程中必須小心地避免死鎖。

以上我們對 Java 中實現線程阻塞的各種方法作了一番分析,我們重點分析了 wait() 和 notify() 方法,因為它們的功能最強大,使用也最靈活,但是這也導致了它們的效率較低,較容易出錯。實際使用中我們應該靈活使用各種方法,以便更好地達到我們的目的。

為什麽wait()方法和notify()/notifyAll()方法要在同步塊中被調用

這是JDK強制的,wait()方法和notify()/notifyAll()方法在調用前都必須先獲得對象的鎖

wait()方法和notify()/notifyAll()方法在放棄對象監視器時有什麽區別

wait()方法和notify()/notifyAll()方法在放棄對象監視器的時候的區別在於:wait()方法立即釋放對象監視器,notify()/notifyAll()方法則會等待線程剩余代碼執行完畢才會放棄對象監視器。

wait()與sleep()的區別

關於這兩者已經在上面進行詳細的說明,這裏就做個概括好了:

sleep()來自Thread類,和wait()來自Object類.調用sleep()方法的過程中,線程不會釋放對象鎖。而 調用 wait 方法線程會釋放對象鎖

sleep()睡眠後不出讓系統資源,wait讓其他線程可以占用CPU

sleep(milliseconds)需要指定一個睡眠時間,時間一到會自動喚醒.而wait()需要配合notify()或者notifyAll()使用

synchronized和ReentrantLock的區別

synchronized是和if、else、for、while一樣的關鍵字,ReentrantLock是類,這是二者的本質區別。既然ReentrantLock是類,那麽它就提供了比synchronized更多更靈活的特性,可以被繼承、可以有方法、可以有各種各樣的類變量,ReentrantLock比synchronized的擴展性體現在幾點上: (1)ReentrantLock可以對獲取鎖的等待時間進行設置,這樣就避免了死鎖 (2)ReentrantLock可以獲取各種鎖的信息 (3)ReentrantLock可以靈活地實現多路通知 另外,二者的鎖機制其實也是不一樣的:ReentrantLock底層調用的是Unsafe的park方法加鎖,synchronized操作的應該是對象頭中mark word.

JAVA行業交流,歡迎新人和大佬共同入駐,裏面有很多免費教學資源,視頻資源,書籍資源,歡迎索取,群號240448376


JAVA面試必備的知識寶典(三)