天天看點

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