天天看點

Java面試題

android程式員在面試時都會被問到Java方面的知識,本文整理了部分Java方面的面試題,如下:

0、Java垃圾回收和System.gc的關系

Java根據垃圾收集算法,周期性的進行垃圾回收,回收哪些無用的對象。以下情況會觸發GC:

應用程式空閑時,即沒有程式再運作,會GC。因為GC線程的優先級較低,CPU較忙時一般不會執行,以下場景除外:堆記憶體不足。

Java堆記憶體不足時會GC,如果一次GC後記憶體還不夠,會觸發第二次GC,第二次還不夠就會觸發OOM。

System.gc隻是呼叫JVM進行垃圾回收,但這隻是建議而已,不一定立刻執行。

1、Java元注解有哪些

元注解指的是對注解進行注解的注解

,有以下四種:

@Target :

表示該注解用在什麼地方,可能值在枚舉類ElementType中,包括   ElemrntType.CONSTRUCTOR----------構造器

ElemrntType.FIELD----------域聲明

ElemrntType.METHOD--------------方法

@Retention:

在什麼地方儲存該注解資訊,包括:

RetentionPolicy.SOURCE----------------注解在編譯時就被丢棄

RetentionPolicy.CLASS----------------注解在編譯時保留但在VM中丢棄

RetentionPolicy.RUNTIME----------------注解在運作時一直存在,即可以通過反射進行調研

@Documented:

将此注解包含在javadoc 中,即該注解可以被javadoc生成文檔

@Inherited:

允許子類繼承父類中的注解

2、Synchronized詳解

Java用Synchronized來實作線程同步,該關鍵字可以加載方法上,也可以加在對象上,如果他的作用的對象是非靜态的,則它取得的鎖是對象;如果作用的對象是靜态方法或者類,則它取得的鎖是類對象(Class對象)。每個對象之一一把鎖,誰取得這個鎖,誰就可以允許對象的方法。

可重入性:

假設類A有兩個同步方法a()、b(),

Java面試題

可重入性

線程T在通路A的方法a()時會先擷取A的鎖,如果這時候a()方法調用了b()方法,由于b()方法也是同步的,是以也需要擷取A的鎖,由于在調用a()方法時已經擷取到A的對象鎖了,調用b()時就可以直接進入方法不會導緻死鎖,這就是可重入性。

不可繼承性:

如果類A的b()方法是同步的,如果子類B的b()方法不是同步的,那麼子類B的b()方法就不具備同步特性。

對象鎖和類鎖:

Java的所有對象都含有一個鎖,這個鎖由JVM自動擷取和釋放,線程進入Synchronized代碼塊回去擷取該對象的鎖,如果已經有線程擷取了對象鎖,那個目前線程就會等待。

對象鎖

是控制執行個體方法的鎖,

類鎖

是用來控制靜态代碼的同步的,相當于Class對象的鎖。類鎖和對象鎖不同,一個是類的Class對象的鎖,一個是類的執行個體對象的鎖,也就是一個線程通路靜态的Synchronized時,另外的線程可以通路對象執行個體的Synchronized方法。

3、Java引用類型

強引用----------- 垃圾收集器不會收集它,甯可抛出OOM

軟引用(Soft Reference)-----------當垃圾收集器工作時,如果記憶體足夠,就不回收隻被軟引用關聯的對象,如果記憶體不夠,則會進行回收。一旦被回收,這個軟引用就會被加入到關聯的ReferenceQueue中。

弱引用(Weak Reference) -------------- 對象隻能生存到下一次GC,當垃圾收集器工作時無論記憶體是否足夠都會收集隻被弱引用關聯的對象。一旦一個弱引用被回收,便會加入一個注冊引用隊列ReferenceQueue中。

虛引用(Phantom Reference) ------------ 最弱的引用類型,get方法傳回null。主要用來跟蹤對象被垃圾回收的狀态。需引用被回收時會放入ReferenceQueue,進而達到跟蹤對象垃圾回收的作用。

ReferenceQueue-----------是這樣的一個對象,當一個obj被gc掉之後,其相應的包裝類,即ref對象會被放入queue中。我們可以從queue中擷取到相應的對象資訊,同時進行額外的處理。比如反向操作,資料清理等。

WeakReference weakReference=new WeakReference(obj,ReferenceQueue)将引用隊列傳入即可進行觀察。

4、垃圾收集算法

可達性分析算法,通過一系列作為GCroot的對象,虛拟機從GCRoot沿着對象傳遞的引用鍊來尋找,如果找不到,就認為對象不可達,即該對象可以被回收。可以作為GCRoot的對象有虛拟機棧(局部變量表中引用的對象)、方法區内靜态屬性引用的對象、方法區中常量引用的對象、本地方法棧引用的對象。

在垃圾收集器對對象回收之前首先對GCroot不可達的對象進行第一次标記,然後進行篩選(判斷該對象是否有必要執行finalize()方法,finalize()方法隻能被調用一次。如果有必要執行時,就會放在一個處理隊列裡等待虛拟機處理,虛拟機會觸發該對象的finalize(),但是不承諾會等待它結束,防止他進入死循環而阻塞後邊的程序,然後在finalize()執行完之後,在對那些仍舊沒有被引用的對象進行第二次标記,然後開始進行回收,換而言之,該對象可以在自己的finalize()方法裡讓别人引用自己,這樣自己就不會被回收了,進而拯救自己。

判斷一個類是否可以被回收有以下三個條件:該類的所有執行個體已經被回收,該類的ClassLoader已經被回收,該類的Class對象沒有引用。

垃圾收集算法一般分為:

标記-清除算法-----會産生不聯系的記憶體碎片,導緻以後想配置設定大記憶體時,明明剩餘的總記憶體足夠,确不能配置設定。

複制算法----将空間分為兩個部分AB,當A快用完時将A中存活的對象複制到B,然後回收A。

标記-整理算法----類似于标記清除算法,隻是在其基礎上将回收完還存活的對象往一側移動,這樣剩餘的空間就是連續的了。

對應Java堆來說,使用分代收集,新生代使用複制算法,老年代使用标記清除或者标記整理。

可以作為gcroot的對象如下:虛拟機棧中本地變量引用的對象、方法區中靜态屬性引用的對象、方法區中常量引用的對象、本地方法棧jni引用的對象。

5、Java線程池 

Java線程池使用ThreadPoolExecutor類實作,構造函數如下:

Java面試題

ThreadPoolExecutor構造函數

corePoolSize:

線程池中一直存活的線程最小數量,也叫核心線程.

maximumPoolSize:

線程池能夠容納的最大線程數,當送出一個任務到線程池,如果處于RUNNING的線程數量少于corePoolSize,那麼即使一些非核心線程處于空閑等待狀态,系統也會建立一個新的線程來處理這個任務。如果處于運作狀态的線程數大于corePoolSize,但又小于maximumPoolSize,系統就會判斷線程池内部阻塞隊列workQueue是否有空位置,如果有就先存入阻塞隊列,如果沒有就建立一個新線程來執行這個任務。如果将corePoolSize和maximumPoolSize設定為相同,那個該線程池就是一個容量固定的線程池。

keepAliveTime:

空閑線程處于等待的逾時時間,當總線程數大于corePoolSize且allowCoreThreadTimeOut為false時,多出來的空閑等待的線程就會開始計算各自的等待時間,等待時間超過keepAliveTime該線程就會停止工作。如果keepAliveTime為true,那麼不論核心線程還是非核心線程都會受到keepAliveTime的制約。

workQueue:

是一個阻塞隊列,用于儲存等待任務。當送出一個任務到線程池後,線程池會根據目前運作的線程數量做出相應處理,

方式如下:

     1、如果運作的線程數少于corePoolSize,則直接建立一個新的線程來執行任務

     2、如果運作的線程數大于等于corePoolSize,那麼線程池會優先将該任務送出到workQueue隊列中

     3、基于第2點如果任務加入到workQueue(隊列滿了)失敗了,則檢視目前運作的線程數和maximumPoolSize的大小關系,如果目前運作線程數小于maximumPoolSize,則建立一個新線程來執行任務;如果大于或等于maximumPoolSize,說明線程池滿了,就直接拒絕該任務。隊列送出新任務的

三種常見切換政策

     a、直接切換:常用SynchronousQueue同步隊列,隊列内部不會存儲元素,每次插入材質都會進入阻塞狀态,知道别的線程執行了删除操作;反之一樣。 “直接切換”的意思就是, 處理方式由“将任務暫時存入隊列”直接切換為“建立一個線程來處理該任務”. 這種政策适合用來處理多個有互相依賴關系的任務,因為該政策可以避免這些任務因一個沒有及時處理而導緻依賴于該任務的其他任務也不能及時處理而造成的鎖定效果.因為這種政策的目的是要讓幾乎每一個新送出的任務都能得到立即處理, 是以這種政策通常要求最大線程數 maximumPoolSizes是無界的(即:Integer.MAX_VALUE)。Executors.newCachedThreadPool() 使用了這個隊列。

   b、無界隊列:

使用 Integer.MAX_VALUE作為預設容量,例如LinkedBlockingQueue,這種情況下maximumPoolSize的設定其實沒有效果,線程池最大的線程數就是corePoolSize,超過之後新任務總能存入隊列中。Executors.newFixedThreadPool() 使用了這個隊列

  c、有界隊列:限制隊列的長度。例如ArrayBlockingQueue

threadFactory:

線程工廠,用于建立線程,可以給線程設定特定的屬性,了如優先級、名字等。

handler:

送出線程到線程池失敗後的處理政策。當線程池處于下面任意狀态時就會拒絕服務:

     1、線程池處于SHUNDOWN狀态,不論線程池和阻塞隊列是否滿了,都會拒絕服務。

     2、當線程池的所有線程都處于運作狀态(corePoolSize和maximumPoolSize都滿了)并且線程池中的阻塞隊列已經滿了

     線程池預設給我們提供了

以下處理政策

     1、 AbortPolicy:直接抛出RejectedExecutionException 異常,預設就是這種實作方式。

     2、CallerRunsPolicy

     3、DiscardPolicy:直接不執行新送出的任務。

      4、DiscardOldestPolicy:當線程池已經關閉,就不執行這個任務了;當線程池沒有關閉,會将阻塞中的隊首元素一次,然後新送出這個任務到隊尾。

6、HashMap的結構

Java面試題

采用Key-Value形式存儲,如果Node數量小于8,Node的結構就是單連結清單,如果Node的數量大于8,Node的結構就是TreeNode,紅黑樹。

Java面試題
Java面試題

7、String、StringBuffer、StringBuilder的差別

String是字元串常量。Sting的+操作,其實在編譯時轉化成了StringBuilder操作。

StringBuffer是字元串變量,是線程安全的,内部通過Synchronized實作線程同步。

StringBuilder是字元串變量,是非線程安全的。

8、數組和連結清單的差別

數組将資料在記憶體中連續存放,每個元素占用的記憶體相同,可以通過下标迅速通路裡面的元素。數組的插入和删除需要移動大量的元素。如果應用需要快速通路元素且很少做插入和删除操作,就應該使用數組。

連結清單恰巧相反,連結清單中的元素存儲不是有序的,而是通過指針聯系在一起,例如上個元素有個指針指向下個元素,以此類推,直到最後一個元素。如果要通路連結清單,需要從頭開始根據指針進行查找,但是要想删除元素,不需要移動大量元素,隻需要修改指針即可。如果應用經常做插入和删除操作,不常通路元素,就使用連結清單。

9、Volatile關鍵字

http://www.importnew.com/24082.html
Java面試題

Java記憶體模型

Java記憶體模型規定了是以變量必須存在主記憶體中。每條線程有自己的工作記憶體,線程的工作記憶體中儲存了被該線程所使用到的變量(這些變量是從主存拷貝而來的)。線程對變量的讀寫都在工作記憶體中,不同線程之間無法之間通路對方的工作記憶體中的變量,線程将變量的傳遞通過主記憶體來完成。基于這種記憶體模型,便産生了多線程程式設計中的資料“髒讀”問題。

例如在執行i=10;i=i+1的操作時,執行線程首先在自己的工作記憶體對變量進行指派操作,然後再寫入主存,而不是直接寫入到主存。當兩個線程同時執行這段代碼的時候,我們希望執行完成後i的值為12,但實際情況确不一定是這樣,存在了以下場景:

初始時,兩個線程分别将i=10的值寫入到自己的工作記憶體,然後線程1對i執行加1操作,然後線程1将i=11寫入到主存;如果當線程1将i=11寫入之前,線程2執行i+1時,由于主存中的i=10,是以線程2取到i=10,然後執行i+1,執行完成後i的值是11,最後主存中的i=11。

原子性:

原子性指的是一個操作要麼全部執行,要麼全不執行。Java中看似很簡單的x++操作,其實并不是原子操作,編譯後産生了多條指令。Java記憶體模型隻保證基本的讀取和指派的原子性,其他的操作需要程式員自己保證,可以通過synchronized、lock等手段。

可見性:

可見性指的是多個線程同時通路某個變量時,一個線程修改這個變量的值,其餘線程應該能夠馬上看到修改後的值。Java通過volatile關鍵字來保證可見性,它會保證修改的值會被立即更新到主存。

有序性:

Java在編譯的時候,為了提高城鄉的運作效率,會對指令進行重排,但執行結果不會影響。

Java面試題
了解Volatile的工作:

1、保證了可見性:一旦變量聲明為volatile,對變量的修改都在主記憶體,那麼一個線程修改變量後,該修改後的變量對其他線程立即可見。

Java面試題

線程2通過設定stop想要達到中斷線程1的目的并不一定能夠實作,線程1将stop變量儲存到自己的工作記憶體,線程2設定stop後線程1還沒來得及寫入主存就去幹其餘事了。這種場景下就需要使用volatile變量來修飾stop變量,保證線程2修改stop後立即将stop變量強制寫入主存;并且導緻線程1的工作線程中的stop變量的緩存值立即無效;由于線程1工作記憶體中的stop變量已經無效,是以線程1再次讀取stop變量的時候會去主存取資料。

2、保證有序性:Volatile關鍵幀禁止指令重排,是以在一定程度上保證有序。

3、不保證原子性:volatile不能保證原子性。

10、JVM記憶體結構

https://www.cnblogs.com/SaraMoring/p/5713732.html
Java面試題
線程棧(虛拟機棧):

每個線程都有自己的線程棧【線程棧的大小可以通過JVM的-Xss參數進行設定,32位系統在JDK5之後,每個線程棧的大小為1M】,線程棧中存放的資料都是線程私有的。棧裡面存放着棧幀,代表一個函數調用,棧幀裡面存放着函數的形參、局部變量、傳回位址等。

堆:

所有的線程都共享同一個堆空間,堆裡面存放的是對象資料(排除基本類型資料和引用以外的資料)。

方法區:

線程共享的資料區。

本地方法棧:

為Java調用本地方法(JNI)提供的。

Java面試題
程式計數器:

在CPU中有個程式計數器,存放了下一條CPU指令的位址。JAV虛拟機沒有使用CPU的程式計數器,而是在虛拟機記憶體中開辟了一塊區域來模拟CPU的程式計數器功能。JVM中,每個線程都有自己的程式計數器,

JVM 的程式計數器裡面存放的是目前正在執行的位元組碼位址,而不是下一條

Java 虛拟機棧:

虛拟機棧也是線程私有的,描述的是Java方法執行的記憶體模型:每個方法執行的時候都會建立一個棧幀,用于儲存局部變量表、操作數棧等。方法的執行對應着一個棧幀從入棧到出棧的過程。局部變量表中存放着編譯期間可以知道的基本類型(int char…)、對象引用。局部變量表所需要的記憶體在編譯的時候就已經配置設定好了,當進入一個方法時,這個方法在棧幀中需要多少局部變量空間是确定好的,方法運作期間不會改變局部變量表的大小。如果方法遞歸嚴重,虛拟機棧可能會儲存StackOverFollowError的異常。

本地方法棧存放了JNI調用的棧幀。

堆是所有線程共享的區域,是用來存放對象的,幾乎所有的執行個體對象都在裡面配置設定,堆是垃圾收集器主要工作的地方。

方法區也叫永久代,是線程共享的區域,存儲了虛拟機加載的類資訊、常量池等。常量池是方法區的一部分,存放着編譯期間已經确定好的常量(字面量,staticfinal類型)。

JVM共享的資料區域如下:

Java面試題

分為堆(新生代、老年代)、永久代(方法區),新生代可以分為Eden區(存放新生對象)、Survivor幸存區(存放垃圾回收後幸存的對象);永久代(方法區)管理類資訊、常量、靜态對象等。

Jvm的堆的垃圾收集采用分代收集政策,新生代采用複制算法,老年代采用标記整理算法。

複制算法:

将記憶體分為A/B部分:

1. 新生對象被配置設定到A塊中未使用的記憶體當中。當A塊的記憶體用完了, 把A塊的存活對象對象複制到B塊。

2.清理A塊所有對象。

3.新生對象被配置設定的B塊中未使用的記憶體當中。當B塊的記憶體用完了, 把B塊的存活對象對象複制到A塊。

4.清理B塊所有對象。

5. goto 1

11、深拷貝和淺拷貝

淺拷貝:

建立一個新的執行個體,将舊執行個體的成員逐個複制給新的執行個體,類似于複寫clone方法,引用變量沒有重新建立。

深拷貝:

實作類的拷貝構造函數時,不僅複制所有的非引用變量,還要為引用變量建立新的執行個體。

12、泛型的extend和super的差別

假設有這樣一個盤子,實作如下。

Java面試題

現在定義一個水果盤子,理論上可以如下實作:

Java面試題

邏輯上裝水果的盤子肯定可以裝蘋果,但實際上java編譯器卻不允許,編譯器認為蘋果是一種水果,但裝蘋果的盤子并不一定是裝水果的盤子。于是就有了extend和super的概念。

extends:

上界通配符,如下所示,可以存放Fruit的所有子類:

Java面試題

Plate p = new Plate();//存放蘋果

Plate p = new Plate();//存放橘子

Note:

使用extends通配符後,Plate的set方法不能使用,試想一下,Plate <? extends Fruit> p可能是Apple類型的,也可能是Origin類型的,如果p指向Apple類型,而set進去一個Origin類型,在運作時就會報錯,是以不能set。Get方法是可以使用的,因為get出來的肯定是一種Fruit。

super:

下界通配符,存放Fruit的父類。

Plate p = newPlate();

Plate p = new Plate();

使用super通配符後,Plate的get方法不能使用,試想一下,Plate <? super Fruit> p可能是Food類型的,也可能是Object類型的,如果p指向Food類型,而get的時候使用Fruit f = p.get(),Food并不一定是Fruit在運作時就會報錯,是以不能get。set方法是可以使用的,因為set進去Fruit,肯定是一種Food或者Object。

類型擦除:

Java的泛型是在編譯層實作的,編譯完成後class裡面會使用原始類型。例如List、List在編譯完成後,都變成了List,隻能存放Object類型。如果類型變量有限定的話,那麼原始類型就用第一個邊界類型變量來替換,例如Plate<?extends Fruit>就被擦除成Plate。

橋接模式

13、HashMap和HashTable的差別

HashTable是基于陳舊的Dictionary類,完成Map接口;HashMap繼承與AbstractMap。

HashTable的方法是同步的,通過Synchronized實作線程的安全;HashMap是非同步的。

HashTable不允許null類型的key和value進行存儲;HashMap允許null類型的key或者value。

HashTable使用的是Enumeration周遊,HashMap使用的是Iterator。

14、HashMap和HashSet的差別

HashSet實作了Set接口,不允許有重複的值,HashSet内部也是使用HashMap來實作的,隻是使用插入進去的值作為Key,使用固定的PRESENT作為value。

Java面試題

HashMap的put方法如下:

Java面試題
Java面試題

如果HashSet插入進去的值的hashCode已經在map中存在,是以會直接更新Node的值。

HashMap判斷重複的過程:根據Key計算hashCode,根據hashCode查詢Node清單,如果找到Node清單就說明可能存在重複。接着比較Node的Key和put傳入的Key是否完全一緻(引用一緻、equals傳回true、hashCode一緻),HashSet在插入元素的時候,如果插入相同的對象,這邊就完全一緻。如果完全一緻,說明插入的元素存在重複,直接更新舊value即可。如果Key不完全一緻,就查詢Node清單,在清單裡面查詢Key完全一緻的元素,如果找到就更新Value,找不到就插入新的Node。

也就是說存在重複的條件是Key的引用一緻、equals方法一緻、hashCode一緻。

HashSet使用add的對象作為Key,如果是同一個對象,就說明存在重複。

15、Java異常體系

Java面試題

16、修改equals方法的簽名

正确

的equals方法如下:

Java面試題

正确寫法

錯誤

的寫法如下:

Java面試題

錯誤寫法

錯誤的寫法雖然在調用foodA.equals(foodB)的時候能夠正常運作,但當将foodA存到集合的時候就有問題。例如:List.add(foodA)   List.contains(foodA)卻傳回了false。

17、ThreadLocal如何保證local

Java面試題

ThreadLocal

ThreadLocal的set和get方法如上圖所示,它保證了每隔線程有自己的變量副本,假設有如下ThreadLocal:

               ThreadLocal tl =new ThreadLocal();

當線程A和線程B同時通路tl時,每個線程擷取到的都是屬于自己Thread的本地變量,互相不影響。原理就在于ThreadLocal的資料結構。

每個Thread都有一個ThreadLocalMap類型的threadLocals變量,當調用ThreadLocal.set的時候,map裡面存儲了目前ThreadLoacal的引用和value值對象,假設線程A通路了tl,線程A的threadLocals的map中就有Key=tl,value=v的鍵值對,當調用get的時候,會先擷取線程A的threadLocals對象,入得threadLoacal對應的value,每個線程都是取的自己ThreadLocalMap中的ThreadLocal對應的Value,互相不影響。

18、線程的狀态

建立:新建立一個線程對象 

可運作(就緒):線程對象建立後,調用了Thread.start方法,等待被排程

運作:可運作狀态的線程獲得cpu的時間片,執行程式代碼 

阻塞:由于某種原因而放棄cpu的使用權,暫時停止運作。直到進入可運作狀态才有機會再次獲得cpu的時間片。 

死亡:run方法執行結束 

Java面試題

線程狀态

19、未完待續....