java老司機面試題

這些題目我是徹底根據Java編程進階的思路來設計的,不少同窗可能以爲背背答案就能去應對一些面試,先不說爲何這個是很難靠背的,事實上只要面試官稍有點水平,一般在題目的問法上就會有不少的變化,很容易判斷出面試者的狀況,並且面試官應該是根據面試者自己的背景來問問題,而不是千篇一概,因此我以爲發這些題目,最但願的是你們能夠認真的去學習和掌握背後的知識點,這樣才能以不變應萬變。java

 

  1. 基於BIO實現的Server端,當創建了100個鏈接時,會有多少個線程?若是基於NIO,又會是多少個線程? 爲何?
    答:BIO因爲不是NIO那樣的事件機制,在鏈接的IO讀取上,不管是否真的有讀/寫發生,都須要阻塞住當前的線程,對於基於BIO實現的Server端,一般的實現方法都是用一個線程去accept鏈接,當鏈接創建後,將這個鏈接的IO讀寫放到一個專門的處理線程,因此當創建100個鏈接時,一般會產生1個Accept線程 + 100個處理線程。

    NIO經過事件來觸發,這樣就能夠實如今有須要讀/寫的時候才處理,不用阻塞當前線程,NIO在處理IO的讀寫時,當從網卡緩衝區讀或寫入緩衝區時,這個過程是串行的,因此用太多線程處理IO事件其實也沒什麼意義,鏈接事件因爲一般處理比較快,用1個線程去處理就能夠,IO事件呢,一般會採用cpu core數+1或cpu core數 * 2,這個的緣由是IO線程一般除了從緩衝區讀寫外,還會作些比較輕量的例如解析協議頭等,這些是能夠併發的,爲何不僅用1個線程處理,是由於當併發的IO事件很是多時,1個線程的效率不足以發揮出多core的CPU的能力,從而致使這個地方成爲瓶頸,這種在分佈式cache類型的場景裏會比較明顯,按照這個,也就更容易理解爲何在基於Netty等寫程序時,不要在IO線程裏直接作過多動做,而應該把這些動做轉移到另外的線程池裏去處理,就是爲了能保持好IO事件能被高效處理。

    從上面能夠看出,對於大多數須要創建大量鏈接,但併發讀寫並不會同時的場景而言,NIO的優點是很是明顯的。

    這種關於BIO、NIO的問法的變化空間是很是大的,還能夠進一步拓展問問AIO和BIO、NIO的根本不一樣。
     
  2. 一般來講基於NIO實現的Server端,會用多少個線程去處理IO事件,爲何?
    答:見1裏面的回答。
     
  3. 一個典型的客戶端集羣->LB->服務端集羣這樣的結構中,如客戶端採用鏈接池,長鏈接的方式,這種設計你以爲可能會出現什麼問題?若是客戶端採用的是單個長鏈接的方式呢?若是有問題,你以爲應該怎麼解決?
    答:這題比較開放,會有各類回答,這裏講下當年我本身在這裏碰到的一個很大的坑,血淚教訓,也是我認爲這樣的結構裏最大的風險。


    客戶端採用鏈接池,長鏈接,經過LB去鏈接後端的服務端集羣,在這樣的結構下,因爲客戶端看到的其實只有LB提供出來的vip,會致使的一個嚴重問題是服務端集羣出現不均衡的現象,尤爲是在服務端集羣發佈重啓等狀況下,最惡劣的狀況下搞很差會致使服務端集羣壓根就無法啓動了。
    客戶端採用單個長鏈接,其實也會碰到一樣的問題。
    當年,最先的時候咱們的系統就是採用這樣的方式,致使出現過嚴重故障,服務端發佈的時候啓動不了,由於發佈的分批致使了壓力壓在了少數的機器上,容量不夠就崩了,那次處理的時候只好先把vip disable掉,服務端集羣所有發佈好了,再把vip enable,才勉強扛過去了。

    像這種問題,解決起來很麻煩,例如讓長鏈接到達必定條件下就斷開下,但這樣長鏈接的做用就下降了,比較根本的解決方法是在這樣的場景裏把中間的LB去掉,換成相似經過服務註冊/發現的機制來解決。

    有些同窗回答風險是LB的鏈接會爆掉,這個你們可能小看了LB設備的能力,在必定規模的場景下是徹底沒問題的,畢竟到達阿里這樣規模的企業也不多。
     
  4. CGLIB和Java的動態代理相比,具體有什麼不一樣?
    答:我本身也不是很懂,簡單點講是CGLIB能夠代理類,這很是有助於像Spring AOP加強這樣的場景的實現。
     
  5. 在基於Netty實現FrameDecoder時,下面兩種代碼的表現會有什麼不一樣?
    第一種
    private void callDecode(...) {
           Listresults = new ArrayList();
           while (cumulation.readable()) {
                 int oldReaderIndex = cumulation.readerIndex();
                 Object frame = decode(context, channel, cumulation);
                 if (frame == null) {
                      if (oldReaderIndex == cumulation.readerIndex())
                            break;
                      else
                           continue;
                }
               else if (oldReaderIndex == cumulation.readerIndex()) {
                      throw new IllegalStateException( ".....");
                }
                results.add(frame);
         }
         if(results.size() > 0)
             fireMessageReceived(context, remoteAddress, results);
    }
    第二種
    private void callDecode(...) {
           int oldReaderIndex = cumulation.readerIndex();
           Object frame = decode(context, channel, cumulation);
           if (frame != null)
                  fireMessageReceived(context, remoteAddress, frame);
    }
    答:第一種在併發量很是大時會有很大的優點,緣由是當併發量很是大時,一次流事件裏可能帶了多個可處理的對象,以前也說了一般來講基於NIO的模型都是IO線程池 + 業務處理線程池的模式,怎麼充分的讓IO線程更加高效的併發決定了server的處理能力,第一種的處理方式能夠有效減小IO線程池和業務處理線程池的上下文切換,從而提升IO線程的處理效率。
     
  6. 用Executors.newCachedThreadPool建立的線程池,在運行的過程當中有可能產生的風險是?
     
    答:這題比較簡單,主要是在考察對自帶的這些線程池API的掌握能力,有沒有在用的時候仔細的去了解,newCachedThreadPool最大的風險就是可能會建立超多的線程,致使最後不能建立線程。


    這道題稍微拓展開下能夠順帶問問建立100個線程會耗費多少資源,一個Java進程能建立多少線程池是受什麼限制?
     
  7. new ThreadPoolExecutor(10,100,10,TimeUnit.MILLISECONDS,new LinkedBlockingQueue(10));一個這樣建立的線程池,當已經有10個任務在運行時,第11個任務提交到此線程池執行的時候會發生什麼,爲何?
    答:之因此問這個題,是我本身之前剛學ThreadPoolExecutor的時候就進了這個坑,正常邏輯好像會以爲是當線程數還沒到達max,就應該一直建立線程來處理併發的任務,但事實上ThreadPoolExecutor的實現倒是當coreSize滿了後,會先往Queue裏面塞,只有Queue塞滿了,max又還沒到,纔會去建立線程來處理,因此這道題當第11個任務提交時,會放到Queue裏,因此對於用到的API,千萬別自覺得然,仍是去翻翻它具體的實現比較好。

    這道題拓展的更難一點能夠是問問若是來設計一個相似ThreadPoolExecutor的類,大概怎麼設計?
     
  8. 實現一個自定義的ThreadFactory的做用一般是?
    答:一般的做用是給線程取名字,便於之後查問題,不少查過問題的同窗應該都會發現,看到jstack出來後一堆看不出名字意義的線程是多麼的崩潰。
     
  9. 除了用Object.wait和Object.notifyAll來實現線程間的交互外,你還會經常使用哪些來實現?
    答:這題主要看對線程交互的掌握程度,方法很是的多,j.u.c裏的無論是BlockingQueue的實現,仍是各類相似CountDownLatch、CyclicBarrier,均可以用來實現線程的交互。
     
  10. 爲何ConcurrentHashMap能夠在高併發的狀況下比HashMap更爲高效?
    答:主要是ConcurrentHashMap在實現時採用的拆分鎖,以及巧妙的使用final、volatile,網上有不少相關的解讀的文章,這裏就不展開了。
     
  11. AtomicInteger、AtomicBoolean這些類之因此在高併發時高效,共同的緣由是?
    答:CAS,CAS是硬件級的原語,能夠藉助此實現Lock-free算法,網上解讀的文章一樣很是的多,這裏也不展開了。
     
  12. 請合理的使用Queue來實現一個高併發的生產/消費的場景,給些核心的代碼片斷。
    答:這道題主要是想看看對於各類Queue實現的掌握狀況,例如一般可能會藉助LinkedBlockingQueue來實現簡單的生產/消費,那麼像ArrayBlockingQueue、LinkedBlockingQueue的區別是什麼,或者你本身實現一個Queue你會怎麼作?
     
  13. 請實現讓10個任務同時併發啓動,給些代碼片斷。
    答:藉助CyclicBarrier實現,之因此讓給代碼片斷,是看對代碼的熟練程度,寫代碼寫的多的話,是徹底能夠作到手寫一段簡單的編譯不會出錯,可運行的代碼的。

    一樣,這種題目能夠進一步的問,CyclicBarrier是怎麼實現的。
     
  14. 在Java程序運行階段,能夠用什麼命令行工具來查看當前Java程序的一些啓動參數值,例如Heap Size等。
    答:jinfo -flags,這個主要是看對Java一些查問題的工具的掌握狀況,別的能作到相似效果的工具其實也都ok的。
     
  15. 用什麼命令行工具能夠查看運行的Java程序的GC情況,請具體寫出命令行格式。
    答:一般能夠用jstat -gcutil [pid] [頻率,例如多少毫秒一次] [多少次]來看目前的gc狀況,若是已經打開了gc log,能夠直接查看gc日誌。

    這種問題,稍微拓展下就能夠看gc log一般怎麼打開,具體的命令行參數,一段gc log的解讀等。
     
  16. 用什麼工具,能夠在Java程序運行的狀況下跟蹤某個方法的執行時間,請求參數信息等,並請解釋下工具實現的原理。
    答:btrace,Arthas,主要藉助JVM attach agent,ASM以及Instrumentation來動態的替換字節碼,從而實現動態的對程序運行狀況的跟蹤。

    這題拓展開,能夠問會有什麼限制,這個能夠進一步瞭解對原理的掌握程度,也能夠請實際的講一個藉助這些工具排查的case,來看看實踐狀況。
     
  17. 當一個Java程序接收請求,很長時間都沒響應的話,一般你會怎麼去排查這種問題?
    答:這題很是開放,緣由會不少,一般來講,須要先確認下請求是否是已通過來了,若是確認請求過來了的話,須要梳理下Java程序接收請求的處理過程,而後jstack看看對應的線程池的狀況,看看是否是哪一個環節卡住了。

    一樣,這種題展開的問法就是問講一個實際的case。
     
  18. Java進程忽然消失了,你會怎麼去排查這種問題?
    答:這題也很是開放,一般來講,先去看看java進程的日誌,例若有沒有hs_err_[pid].log,若是有,看看日誌裏的內容,相應的來處理;另外能夠看看有沒有core dump,若是有,用gdb查查看;還能夠用dmesg,看看是否是什麼緣由被os kill了;還有看運維繫統的一些操做日誌。

    一樣,這種題展開的問法就是問講一個實際的case。
     
  19. 如下這段代碼思路,你以爲在運行時可能會產生的風險是,應該如何改進?
    public ListgetUsers(String[] userIds){
           // 從數據庫查找符合userIds的user記錄
          //  將返回的記錄組裝爲User對象,放入List並返回

    }
    答:不少同窗回覆了各類風險,挺好的,我本身回答的話,這題最大的風險是沒有限制userIds的個數,可能會致使從數據庫裏查找大量的數據,並拼裝爲User對象,一方面可能會使得數據庫扛不住,另外一方面也有可能致使Java這邊OOM了,相似的這樣的代碼曾經致使過很是嚴重的故障。

    之因此問這個問題,是想提示你們在寫代碼的過程當中要比較好的進行防護性編程。
     
  20. 如下兩種代碼,在運行時有什麼不一樣?爲何?
    第一種
    private static final boolean isLoggerDebugEnabled = log.isDebugEnabled();
    public void xx(User user){
         if(isLoggerDebugEnabled){
              log.debug("enter xx method, user id is: " + user.getId());
         }
    }
    第二種
    public void xx(User user){
         log.debug("enter xx method, user id is: " + user.getId());
    }
     
    答:若是log的debug級別沒開,第一種不會出現字符串拼接,第二種會出現,形成一些young區的內存浪費,因此第一種寫法是更加好的,緣由是第一種在Java運行時的編譯過程當中會直接優化掉,整段代碼會完全拿掉。
    這題能夠拓展開的問問會有哪些編譯的優化技巧。
     
  21. Java程序爲何一般在剛啓動的時候會執行的比較慢,而處理了一些請求後會變快,AOT能帶來什麼幫助?
    答:由於剛啓動的時候Java還處於解釋執行階段,處理了一些請求後隨着C一、C2編譯的介入,會優化爲機器碼,而且藉助各類運行時數據的高級優化(例如上面20題的那種),使得Java程序逐漸進入一個高速運行的狀態,這也是Java這門語言很大的優點,使得程序員間的差距必定程度縮小了,以及不會出現太爛的Java程序。

    AOT帶來的幫助是在啓動前就將一些代碼直接編譯爲機器碼,從而在啓動瞬間就能夠直接跳過解釋執行,進入比較高效的執行。

    這個話題確實有點大,後面我邀請下專業的JVM同窗來寫一篇,順帶給你們講講阿里的場景裏是怎麼儘量去解決啓動瞬間慢的這個問題的,包括你們也能夠去了解下Azul的ReadyNow!,阿里提的JEP JWarmup。
     
  22. Parallel GC、CMS GC、ZGC、Azul Pauseless GC最主要的不一樣是?背後的原理也請簡單描述下?
    答:這題比上題還大,先簡單回答下,後面專題來寫吧。
    Parallel GC的Young區採用的是Mark-Copy算法,Old區採用的是Mark-Sweep-Compact來實現,Parallel執行,因此決定了Parallel GC在執行YGC、FGC時都會Stop-The-World,但完成GC的速度也會比較快。
    CMS GC的Young區採用的也是Mark-Copy,Old區採用的是Concurrent Mark-Sweep,因此決定了CMS GC在對old區回收時形成的STW時間會更短,避免對應用產生太大的時延影響。
    G1 GC採用了Garbage First算法,比較複雜,實現的好呢,理論上是會比CMS GC能夠更高效,同時對應用的影響也很小。
    ZGC、Azul Pauseless GC採用的算法很不同,尤爲是Pauseless GC,其中的很重要的一個技巧是經過增長Read Barrier來更好的識別對GC而言最關鍵的references變化的狀況。

    這題總的來講偏向於去問對GC很是熟的同窗,這種題目拓展是很是大的,一方面能夠是算法,另外一方面能夠是更具體的實現,例如GC是怎麼實現STW的。
     
  23. 請寫一段程序,讓其運行時的表現爲觸發5次ygc,而後3次fgc,而後3次ygc,而後1次fgc,請給出代碼以及啓動參數。
    答:這個我不寫了,很早之前的文章裏好像也有寫過,須要基於對Java內存管理的分代、GC觸發機制來設計相應的代碼,這種題目變化就更多樣了,一方面能夠調整gc的觸發形式,另外一方面能夠經過調整啓動參數,gc的形式,來看是否是真的完全掌握gc的知識點。
     
  24. Go的Coroutine和Java的線程機制最主要的不一樣是?若是Java語言要透明的實現Coroutine,你以爲主要的難點是? 答:這題也很大,先簡單回答,後面專題寫。 Java的線程機制主要仍是基於Native Thread,Go的Coroutine是進程裏本身管理的一種"Thread",因此在高併發的場景下,Coroutine能夠有效的下降比較重的native的線程上下文切換,從而來提升併發處理能力。 但目前不少的Java版本的Coroutine實現都不是很透明,很是多的限制,致使Java很難用上,比較難的是Java裏有不少相似synchronized、各類鎖、BIO等形成Native Thread直接block住的地方,怎麼讓這些地方在Coroutine環境裏也透明的不block native thread,是關鍵問題,感興趣的你們能夠關注下Openjdk的Project Loom,以及阿里的AJDK Coroutine Wisp。
相關文章
相關標籤/搜索