典型java面試題


  面試主要分為兩塊:一塊是考查工程師對基礎知識(包括了技術廣度、深度、對技術的熱情度等)的掌握程度,因為基礎知識決定了一個技術人員發展的上限;另一塊是考察工程師的工程能力,比如:做過哪些項目?遇到最難的問題怎樣解決的?說說最有成就感的一項任務?工程能力是考察工程師當下能為公司帶來的利益。其它考核方面:抗壓性、合作能力等等。

  Java只是一門語言,即使是Java工程師也不能局限於Java,要從面向對象語言本身,甚至從整個計算機體系,從工程實際出發看Java。

  很多知識在一般公司的開發中是用不到的,常有人戲稱:“面試造火箭,工作擰螺絲”,但這只是通常情況下公司對程序員的標准——迅速產出,完成任務。從長遠看:工程師為了自己職業的發展不能局限於公司對自己的要求,不能停留在應用層面,要能夠很好地掌握基礎知識,要多看源碼,自己多實踐,學成記得產出,比如多為開源社區貢獻代碼,幫助初學者指路等。

1,private修飾的方法可以通過反射訪問,那么private的意義是什么

  兩點:java的private修飾符並不是為了絕對安全性設計的,更多是對用戶常規使用java的一種約束;從外部對對象進行常規調用時,能夠看到清晰的類結構

2,Java類初始化順序

  基類靜態代碼塊,基類靜態成員字段(並列優先級,按照代碼中出現的先后順序執行,且只有第一次加載時執行)——>派生類靜態代碼塊,派生類靜態成員字段(並列優先級,按照代碼中出現的先后順序執行,且只有第一次加載時執行)——>基類普通代碼塊,基類普通成員字段(並列優點級,按代碼中出現先后順序執行)——>基類構造函數——>派生類普通代碼塊,派生類普通成員字段(並列優點級,按代碼中出現先后順序執行)——>派生類構造函數

驗證:

 1 class Log {  2     public static String initLog(String log) { System.out.println(log);return null; }  3 }  4 /**
 5  * 基類  6 */
 7 class Base {  8     static { System.out.println("Base Static Block 1"); }  9     private static String staticValue = Log.initLog("Base Static Fiels"); 10     static { System.out.println("Base Static Block 2"); } 11     { System.out.println("Base Normal Block 1"); } 12     private String value = Log.initLog("Base Normal Field"); 13     { System.out.println("Base Normal Block 2"); } 14     Base() { System.out.println("Base Constructor"); } 15 } 16 /**
17  * 派生類 18  */
19 public class Derived extends Base { 20     static { System.out.println("Static Block 1"); } 21     private static String staticValue = Log.initLog("Static Fiels"); 22     static { System.out.println("Static Block 2"); } 23     { System.out.println("Normal Block 1"); } 24     private String value = Log.initLog("Normal Field"); 25     { System.out.println("Normal Block 2"); } 26     Derived() { System.out.println("Derived Constructor"); } 27 /**
28  * 主線程 29  */
30 public static void main(String[] args) { 31     Derived derived = new Derived(); 32 }

輸出結果

 1 Base Static Block 1
 2 Base Static Fiels  3 Base Static Block 2
 4 Static Block 1
 5 Static Fiels  6 Static Block 2
 7 Base Normal Block 1
 8 Base Normal Field  9 Base Normal Block 2
10 Base Constructor 11 Normal Block 1
12 Normal Field 13 Normal Block 2
14 Derived Constructor

3,對方法區和永久區的理解以及它們之間的關系

  方法區是jvm規范里要求的,永久區是Hotspot虛擬機對方法區的具體實現,前者是規范,后者是實現方式。jdk1.8作了改變,取消了永久區,使用了元數據區當做方法區的實現。

4,一個java文件有3個類,編譯后有幾個class文件

  文件中有幾個類編譯后就有幾個class文件。

5,局部變量使用前需要顯式地賦值,否則編譯通過不了,為什么這么設計

  成員變量是可以不經初始化的,在類加載過程的准備階段即可給它賦予默認值,但局部變量使用前需要顯式賦予初始值,javac不是推斷不出不可以這樣做,而是沒有這樣做,對於成員變量而言,其賦值和取值訪問的先后順序具有不確定性,對於成員變量可以在一個方法調用前賦值,也可以在方法調用后進行,這是運行時發生的,編譯器確定不了,交給jvm去做比較合適。而對於局部變量而言,其賦值和取值訪問順序是確定的。這樣設計是一種約束,盡最大程度減少使用者犯錯的可能(假使局部變量可以使用默認值,可能總會無意間忘記賦值,進而導致不可預期的情況出現)。

6,ReadWriteLock讀寫之間互斥嗎

  ReadWriteRock 讀寫鎖,使用場景可分為讀/讀、讀/寫、寫/寫,除了讀和讀之間是共享的,其它都是互斥的。

7,Semaphore拿到執行權的線程之間是否互斥

  Semaphore可有多把鎖,可允許多個線程同時擁有執行權,這些有執行權的線程如並發訪問同一對象,會產生線程安全問題。

8,寫一個你認為最好的單例模式

  單例一共有幾種實現方式:餓漢、懶漢、靜態內部類、枚舉、雙檢鎖,要是寫了簡單的懶漢式可能就會問:要是多線程情況下怎樣保證線程安全呢,面試者可能說雙檢鎖,那么聊聊為什么要兩次校驗,接着會問光是雙檢鎖還會有什么問題,這時候基礎好的面試者就會說了:對象在定義的時候加上volatile關鍵字,其實沒有最好,枚舉方式、靜態內部類、雙檢鎖都是可以的,就想聽下對不同的單例寫法認識程度,寫個雙檢鎖的方式吧:

 1 public class Singleton {  2     private Singleton() {  3  }  4     private volatile static Singleton instance;  5     public static Singleton getInstance() {  6         if (null == instance) {  7             synchronized (Singleton.class) {  8                 if (null == instance) {  9                     instance = new Singleton(); 10  } 11  } 12  } 13         return instance; 14  } 15 }

9,B樹和B+樹是解決什么樣的問題的,怎樣演化過來,之間區別

  B樹和B+樹,這題既問mysql索引的實現原理,也問數據結構基礎,首先從二叉樹說起,因為會產生退化現象,提出了平衡二叉樹,再提出怎樣讓每一層放的節點多一些來減少遍歷高度,引申出m叉樹,m叉搜索樹同樣會有退化現象,引出m叉平衡樹,也就是B樹,這時候每個節點既放了key也放了value,怎樣使每個節點放盡可能多的key值,以減少遍歷高度呢(訪問磁盤次數),可以將每個節點只放key值,將value值放在葉子結點,在葉子結點的value值增加指向相鄰節點指針,這就是優化后的B+樹。然后談談數據庫索引失效的情況,為什么給離散度低的字段(如性別)建立索引是不可取的,查詢數據反而更慢,如果將離散度高的字段和性別建立聯合索引會怎樣,有什么需要注意的?

10,寫一個生產者消費者模式

  生產者消費者模式,synchronized鎖住一個LinkedList,一個生產者,只要隊列不滿,生產后往里放,一個消費者只要隊列不空,向外取,兩者通過wait()和notify()進行協調,寫好了會問怎樣提高效率,最后會聊一聊消息隊列設計精要思想及其使用。

11,寫一個死鎖

  寫一個死鎖,覺得這個問題真的很不錯,經常說的死鎖四個條件,背都能背上,那寫一個看看,思想為:定義兩個ArrayList,將他們都加上鎖A,B,線程1,2,1拿住了鎖A ,請求鎖B,2拿住了鎖B請求鎖A,在等待對方釋放鎖的過程中誰也不讓出已獲得的鎖。

 1 public class DeadLock {  2     public static void main(String[] args) {  3         final List<Integer> list1 = Arrays.asList(1, 2, 3);  4         final List<Integer> list2 = Arrays.asList(4, 5, 6);  5         new Thread(new Runnable() {  6  @Override  7             public void run() {  8                 synchronized (list1) {  9                     for (Integer i : list1) { 10  System.out.println(i); 11  } 12                     try { 13                         Thread.sleep(1000); 14                     } catch (InterruptedException e) { 15  e.printStackTrace(); 16  } 17                     synchronized (list2) { 18                         for (Integer i : list2) { 19  System.out.println(i); 20  } 21  } 22  } 23  } 24  }).start(); 25         new Thread(new Runnable() { 26  @Override 27             public void run() { 28                 synchronized (list2) { 29                     for (Integer i : list2) { 30  System.out.println(i); 31  } 32                     try { 33                         Thread.sleep(1000); 34                     } catch (InterruptedException e) { 35  e.printStackTrace(); 36  } 37                     synchronized (list1) { 38                         for (Integer i : list1) { 39  System.out.println(i); 40  } 41  } 42  } 43  } 44  }).start(); 45  } 46 }

12,cpu 100%怎樣定位

先用top定位最耗cpu的java進程 例如: 12430

工具:top或者 htop(高級)

方法:top -c 顯示進程運行詳細列表

鍵入 P (大寫P),按照cpu進行排序

然后用top -p 12430 -H 定位到最耗cpu的線程 的ID 例如:12483

工具:top

方法:top -Hp 1865 ,顯示一個進程的線程運行信息列表

鍵入P (大寫p),線程按照CPU使用率排序

把第二步定位的線程ID,轉成16進制,printf “%x\n” 12483 得到 :30c3

工具:printf

方法:printf “%x\n” 2747

從jstack 輸出的線程快照中找到線程的對堆棧信息 jstack 12430 |grep 30c3 -A 60 |less

工具:pstack/jstack/grep

方法:jstack 10765 | grep ‘0x2a34’ -C5 --color`

13,String a = "ab"; String b = "a" + "b"; a == b 是否相等,為什么

  String a = "ab"; String b = "a" + "b"; a ,b 是相等的。

14,int a = 1; 是原子性操作嗎

  int a = 1; 是原子性操作。

15,可以用for循環直接刪除ArrayList的特定元素嗎?可能會出現什么問題?怎樣解決

  for循環直接刪除ArrayList中的特定元素是錯的,不同的for循環會發生不同的錯誤,泛型for會拋出 ConcurrentModificationException,普通的for想要刪除集合中重復且連續的元素,只能刪除第一個。

  錯誤原因:打開JDK的ArrayList源碼,看下ArrayList中的remove方法(注意ArrayList中的remove有兩個同名方法,只是入參不同,這里看的是入參為Object的remove方法)是怎么實現的,一般情況下程序的執行路徑會走到else路徑下最終調用faseRemove方法,會執行System.arraycopy方法,導致刪除元素時涉及到數組元素的移動。針對普通for循環的錯誤寫法,在遍歷第一個字符串b時因為符合刪除條件,所以將該元素從數組中刪除,並且將后一個元素移動(也就是第二個字符串b)至當前位置,導致下一次循環遍歷時后一個字符串b並沒有遍歷到,所以無法刪除。針對這種情況可以倒序刪除的方式來避免

  解決方案:用 Iterator。

1 List<String> list = new  ArrayList(Arrays.asList("a", "b",  "b" , "c", "d")); 2 Iterator<String> iterator = list.iterator(); 3 while(iterator.hasNext()) { 4     String element = iterator.next(); 5     if(element.equals("b")) { 6  iterator.remove(); 7  } 8 }

16,新的任務提交到線程池,線程池是怎樣處理

第一步 :線程池判斷核心線程池里的線程是否都在執行任務。如果不是,則創建一個新的工作線程來執行任務。如果核心線程池里的線程都在執行任務,則執行第二步。

第二步 :線程池判斷工作隊列是否已經滿。如果工作隊列沒有滿,則將新提交的任務存儲在這個工作隊列里進行等待。如果工作隊列滿了,則執行第三步。

第三步 :線程池判斷線程池的線程是否都處於工作狀態。如果沒有,則創建一個新的工作線程來執行任務。如果已經滿了,則交給飽和策略來處理這個任務。

17,AQS和CAS原理

  抽象隊列同步器AQS(AbstractQueuedSychronizer),如果說java.util.concurrent的基礎是CAS的話,那么AQS就是整個Java並發包的核心了,ReentrantLock、CountDownLatch、Semaphore等都用到了它。AQS實際上以雙向隊列的形式連接所有的Entry,比方說ReentrantLock,所有等待的線程都被放在一個Entry中並連成雙向隊列,前面一個線程使用ReentrantLock好了,則雙向隊列實際上的第一個Entry開始運行。AQS定義了對雙向隊列所有的操作,而只開放了tryLock和tryRelease方法給開發者使用,開發者可以根據自己的實現重寫tryLock和tryRelease方法,以實現自己的並發功能。

  比較並替換CAS(Compare and Swap),假設有三個操作數:內存值V、舊的預期值A、要修改的值B,當且僅當預期值A和內存值V相同時,才會將內存值修改為B並返回true,否則什么都不做並返回false,整個比較並替換的操作是一個原子操作。CAS一定要volatile變量配合,這樣才能保證每次拿到的變量是主內存中最新的相應值,否則舊的預期值A對某條線程來說,永遠是一個不會變的值A,只要某次CAS操作失敗,下面永遠都不可能成功。

  CAS雖然比較高效的解決了原子操作問題,但仍存在三大問題:循環時間長開銷很大。只能保證一個共享變量的原子操作。ABA問題。

 18,synchronized底層實現原理

  synchronized (this)原理:涉及兩條指令:monitorenter,monitorexit;再說同步方法,從同步方法反編譯的結果來看,方法的同步並沒有通過指令monitorenter和monitorexit來實現,相對於普通方法,其常量池中多了ACC_SYNCHRONIZED標示符。

  JVM就是根據該標示符來實現方法的同步的:當方法被調用時,調用指令將會檢查方法的 ACC_SYNCHRONIZED 訪問標志是否被設置,如果設置了,執行線程將先獲取monitor,獲取成功之后才能執行方法體,方法執行完后再釋放monitor。在方法執行期間,其他任何線程都無法再獲得同一個monitor對象。

  這個問題會接着追問:java對象頭信息,偏向鎖,輕量鎖,重量級鎖及其他們相互間轉化。

19,volatile作用,指令重排相關

  理解volatile關鍵字的作用的前提是要理解Java內存模型,volatile關鍵字的作用主要有兩點:

  • 1,多線程主要圍繞可見性和原子性兩個特性而展開,使用volatile關鍵字修飾的變量,保證了其在多線程之間的可見性,即每次讀取到volatile變量,一定是最新的數據
  • 2,代碼底層執行不像我們看到的高級語言—-Java程序這么簡單,它的執行是Java代碼–>字節碼–>根據字節碼執行對應的C/C++代碼–>C/C++代碼被編譯成匯編語言–>和硬件電路交互,現實中,為了獲取更好的性能JVM可能會對指令進行重排序,多線程下可能會出現一些意想不到的問題。使用volatile則會對禁止語義重排序,當然這也一定程度上降低了代碼執行效率

  從實踐角度而言,volatile的一個重要作用就是和CAS結合,保證了原子性,詳細的可以參見java.util.concurrent.atomic包下的類,比如AtomicInteger。

20,AOP和IOC原理

  AOP 和 IOC是Spring精華部分,AOP可以看做是對OOP的補充,對代碼進行橫向的擴展,通過代理模式實現,代理模式有靜態代理,動態代理,Spring利用的是動態代理,在程序運行過程中將增強代碼織入原代碼中。IOC是控制反轉,將對象的控制權交給Spring框架,用戶需要使用對象無需創建,直接使用即可。AOP和IOC最可貴的是它們的思想。

21,Spring怎樣解決循環依賴的問題

  什么是循環依賴,怎樣檢測出循環依賴,Spring循環依賴有幾種方式,使用基於setter屬性的循環依賴為什么不會出現問題,接下來會問:Bean的生命周期。

22,dispatchServlet怎樣分發任務的

1). 用戶發請求-->DispatcherServlet,前端控制器收到請求后自己不進行處理,而是委托給其他的解析器進行處理,作為統一訪問點,進行全局的流程控制。

2).DispatcherServlet-->HandlerMapping,HandlerMapping將會把請求映射為HandlerExecutionChain對象(包含一個Handler處理器,多個HandlerInterceptor攔截器)。

3).DispatcherServlet-->HandlerAdapter,HandlerAdapter將會把處理器包裝為適配器,從而支持多種類型的處理器。

4).HandlerAdapter-->處理器功能處理方法的調用,HandlerAdapter將會根據適配的結果調用真正的處理器的功能處理方法,完成功能處理,並返回一個ModelAndView對象(包含模型數據,邏輯視圖名)

5).ModelAndView的邏輯視圖名-->ViewResolver,ViewResoler將把邏輯視圖名解析為具體的View。

6).View-->渲染,View會根據傳進來的Model模型數據進行渲染,此處的Model實際是一個Map數據結構

7).返回控制權給DispatcherServlet,由DispatcherServlet返回響應給用戶。

23,mysql給離散度低的字段建立索引會出現什么問題,具體說下原因

   先上結論:重復性較強的字段,不適合添加索引。mysql給離散度低的字段,比如性別設置索引,再以性別作為條件進行查詢反而會更慢。

  一個表可能會涉及兩個數據結構(文件),一個是表本身,存放表中的數據,另一個是索引。索引是什么?它就是把一個或幾個字段(組合索引)按規律排列起來,再附上該字段所在行數據的物理地址(位於表中)。比如我們有個字段是年齡,如果要選取某個年齡段的所有行,那么一般情況下可能需要進行一次全表掃描。但如果以這個年齡段建個索引,那么索引中會按年齡值根據特定數據結構建一個排列,這樣在索引中就能迅速定位,不需要進行全表掃描。為什么性別不適合建索引呢?因為訪問索引需要付出額外的IO開銷,從索引中拿到的只是地址,要想真正訪問到數據還是要對表進行一次IO。假如你要從表的100萬行數據中取幾個數據,那么利用索引迅速定位,訪問索引的這IO開銷就非常值了。但如果是從100萬行數據中取50萬行數據,就比如性別字段,那你相對需要訪問50萬次索引,再訪問50萬次表,加起來的開銷並不會比直接對表進行一次完整掃描小。

  當然如果把性別字段設為表的聚集索引,那么就肯定能加快大約一半該字段的查詢速度了。聚集索引指的是表本身數據按哪個字段的值來進行排序。因此,聚集索引只能有一個,而且使用聚集索引不會付出額外IO開銷。當然你得能舍得把聚集索引這么寶貴資源用到性別字段上。

  可以根據業務場景需要,將性別和其它字段建立聯合索引,比如時間戳,但是建立索引記得把時間戳字段放在性別前面。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM