高級Java開發面試常用題的答案2

三、JVM

· JVM堆的基本結構。

 

java_heap_struct.jpg

參考閱讀:JVM內存堆佈局圖解分析

· JVM的垃圾算法有哪幾種?CMS垃圾回收的基本流程?

基本的算法有:

  1. 標記-清除算法
  2. 等待被回收對象在被標記後直接對對象進行清除,會帶來另一個新的問題——內存碎片化。如果下次有比較大的對象實例需要在堆上分配較大的內存空間時,可能會出現無法找到足夠的連續內存而不得不再次觸發垃圾回收。
  3. 複製算法(Java堆中新生代的垃圾回收算法)
  4. 此GC算法實際上解決了標記-清除算法帶來的“內存碎片化”問題。首先還是先標記處待回收內存和不用回收的內存,下一步將不用回收的內存複製到新的內存區域,這樣舊的內存區域就可以全部回收,而新的內存區域則是連續的。它的缺點就是會損失掉部分系統內存,因爲你總要騰出一部分內存用於複製。
  5. 標記-壓縮算法(或稱爲標記-整理算法,Java堆中老年代的垃圾回收算法)
  6. 對於新生代,大部分對象都不會存活,所以在新生代中使用複製算法較爲高效,而對於老年代來講,大部分對象可能會繼續存活下去,如果此時還是利用複製算法,效率則會降低。標記-壓縮算法首先還是“標記”,標記過後,將不用回收的內存對象壓縮到內存一端,此時即可直接清除邊界處的內存,這樣就能避免複製算法帶來的效率問題,同時也能避免內存碎片化的問題。老年代的垃圾回收稱爲“Major GC”。

 

  1.  
  2. jvm_gc.png

CMS的基本流程:

  1. 初始標記 :在這個階段,需要虛擬機停頓正在執行的任務,官方的叫法STW(Stop The Word)。這個過程從垃圾回收的"根對象"開始,只掃描到能夠和"根對象"直接關聯的對象,並作標記。所以這個過程雖然暫停了整個JVM,但是很快就完成了。
  2. 併發標記 :這個階段緊隨初始標記階段,在初始標記的基礎上繼續向下追溯標記。併發標記階段,應用程序的線程和併發標記的線程併發執行,所以用戶不會感受到停頓。
  3. 併發預清理 :併發預清理階段仍然是併發的。在這個階段,虛擬機查找在執行併發標記階段新進入老年代的對象(可能會有一些對象從新生代晉升到老年代, 或者有一些對象被分配到老年代)。通過重新掃描,減少下一個階段"重新標記"的工作,因爲下一個階段會Stop The World。
  4. 重新標記 :這個階段會暫停虛擬機,收集器線程掃描在CMS堆中剩餘的對象。掃描從"跟對象"開始向下追溯,並處理對象關聯。
  5. 併發清理 :清理垃圾對象,這個階段收集器線程和應用程序線程併發執行。
  6. 併發重置 :這個階段,重置CMS收集器的數據結構,等待下一次垃圾回收

延伸說明:

  • CMS(Concurrent Mark-Sweep) 在啓動JVM參數加上-XX:+UseConcMarkSweepGC,這個參數表示對於老年代的回收採用CMS。CMS採用的基礎算法是:標記—清除(Mark-Sweep)。CMS不會整理、壓縮堆空間。這樣就會有一個問題:經過CMS收集的堆會產生空間碎片。CMS的另一個缺點是它需要更大的堆空間。
  • 如果你的應用程序對停頓比較敏感,並且在應用程序運行的時候可以提供更大的內存和更多的CPU(也就是硬件牛逼),那麼使用CMS來收集會給你帶來好處。還有,如果在JVM中,有相對較多存活時間較長的對象(老年代比較大)會更適合使用CMS。

· JVM有哪些常用啓動參數可以調整,描述幾個?

各主要JVM啓動參數的作用如下:

-Xms:設置jvm內存的初始大小

-Xmx:設置jvm內存的最大值

-Xmn:設置新域的大小(這個似乎只對jdk1.4來說是有效的,後來就廢棄了)

-Xss:設置每個線程的堆棧大小(也就是說,在相同物理內存下,減小這個值能生成更多的線程)

-XX:NewRatio:設置新域與舊域之比,如-XX:NewRatio=4就表示新域與舊域之比爲1:4

-XX:NewSize:設置新域的初始值

-XX:MaxNewSize:設置新域的最大值

-XX:MaxPermSize:設置永久域的最大值

-XX:SurvivorRatio=n:設置新域中Eden區與兩個Survivor區的比值。(Eden區主要是用來存放新生的對象,而兩個Survivor區則用來存放每次垃圾回收後存活下來的對象)

-XX:+PrintGC -XX:+PrintGCDetails -XX:+PrintGCTimestamps -XX:+PrintGCApplicationStopedTime

JVM啓動參數使用中常見的錯誤:

java.lang.OutOfMemoryError相信很多開發人員都用到過,這個主要就是JVM參數沒有配好引起的,但是這種錯誤又分兩種:java.lang.OutOfMemoryError:Javaheapspace和java.lang.OutOfMemoryError:PermGenspace,其中前者是有關堆內存的內存溢出,可以同過配置-Xms和-Xmx參數來設置,而後者是有關永久域的內存溢出,可以通過配置-XX:MaxPermSize來設置。

· 如何查看JVM的內存使用情況?

jstat,jmap,jstack,j viusalvm

· Java程序是否會內存溢出,內存泄露情況發生?舉幾個例子。

  1. 靜態集合類引起內存泄漏:

 
Static Vector v = new Vector(10); for (int i = 1; i<100; i++) { Object o = new Object(); v.add(o); o = null; } //o的對象還在Vector中,因此需要從中移除,或者直接把vector=null 
  1. 各種資源性連接沒有正確關閉.比如數據庫連接(dataSourse.getConnection()),網絡連接(socket)和io連接,除非其顯式的調用了其close()方法將其連接關閉,否則是不會自動被GC 回收的。
  2. 不正確使用單例模式是引起內存泄漏的一個常見問題.
  3. 當集合裏面的對象屬性被修改後,造成該對象的Hashcode改變了,再調用remove()方法時不起作用。

· 你常用的JVM配置和調優參數都有哪些?分別什麼作用?

  1. Trace跟蹤參數(跟蹤GC、類、變量的內存變化情況)
  • 打開GC跟蹤日誌(每次執行GC的信息都能打印,獲得執行時間,空間大小):
  • -verbose:gc 或 -XX:+printGC 或 -XX:+printGCDetails
  • 類加載監控:(監控類加載的順序)-XX:+TraceClassLoading
  1. 堆的分頻參數
  • -Xmx1024M 指定最大堆,JVM最多能夠使用的堆空間 (超過該空間引發OOM)
  • -Xms128M 指定最小堆,JVM至少會有的堆空間(儘可能維持在最小堆)
  • -Xmn 128M(new) 設置新生代大小
  1. 棧的分配參數
  2. -Xss 每個線程都有獨立的棧空間(幾百k,比較小)需要大量線程時,需要儘可能減小棧空間
  3. 棧空間太小-----StackOverFlow棧溢出(一般遞歸時產生大量局部變量導致)
  4. 總結:
  5. 1.根據實際情況調整新生代和倖存代的大小
  6. 2.官方推薦:新生代佔堆空間3/8
  7. 3.倖存代佔新生代1/10
  8. 4.OOM時,dump出堆到文件,方便排查

· 常用的GC策略,什麼時候會觸發YGC,什麼時候觸發FGC?

常見內存回收策略:

  1. 串行&並行
  2. 串行:單線程執行內存回收工作。十分簡單,無需考慮同步等問題,但耗時較長,不適合多cpu。
  3. 並行:多線程併發進行回收工作。適合多CPU,效率高。
  4. 併發& stop the world
  5. stop the world:jvm裏的應用線程會掛起,只有垃圾回收線程在工作進行垃圾清理工作。簡單,無需考慮回收不乾淨等問題。
  6. 併發:在垃圾回收的同時,應用也在跑。保證應用的響應時間。會存在回收不乾淨需要二次回收的情況。
  7. 壓縮&非壓縮&copy
  8. 壓縮:在進行垃圾回收後,會通過滑動,把存活對象滑動到連續的空間裏,清理碎片,保證剩餘的空間是連續的。
  9. 非壓縮:保留碎片,不進行壓縮。
  10. copy:將存活對象移到新空間,老空間全部釋放。(需要較大的內存。)
  • YGC :對新生代堆進行GC: edn空間不足
  • FGC的時機:old空間不足; 2.perm空間不足;3.顯示調用System.gc() ,包括RMI等的定時觸發; 4.YGC時的悲觀策略;dump live的內存信息時(jmap –dump:live)。

四、多線程/併發

· 如何創建線程?如何保證線程安全?

保證線程安全: 競爭與原子操作、同步與鎖、可重入、使用valatile防止CPU過度優化。

  1. 繼承Thread類,必須重寫run方法,在run方法中定義需要執行的任務。
  2. 實現Runnable接口。

 
MyRunnable runnable = new MyRunnable(); Thread thread = new Thread(runnable); thread.start(); class MyRunnable implements Runnable{... } 

Note: Java如何創建進程:

第一種方式是通過Runtime.exec()方法來創建一個進程,第二種方法是通過ProcessBuilder的start方法來創建進程。

· 什麼是死鎖?如何避免死鎖

  • 線程死鎖是指由於兩個或者多個線程互相持有對方所需要的資源,導致這些線程處於等待狀態,無法前往執行。
  1. 按同樣的順序訪問(鎖定)資源
  2. 嘗試獲取鎖的時候加一個超時時間,這也就意味着在嘗試獲取鎖的過程中若超過了這個時限該線程則放棄對該鎖請求。 3. 使用Atom原子操作(無鎖模式)

· Volatile關鍵字的作用?

volatile特性一:內存可見性,即線程A對volatile變量的修改,其他線程獲取的volatile變量都是最新的。volatile特性二:可以禁止指令重排序,避免重排序指令後訪問數據錯亂.

Volatile可以看做一種輕量級的鎖,但又和鎖有些不同。

a) 它對於多線程,不是一種互斥(mutex)關係。

b) 用volatile修飾的變量,不能保證該變量狀態的改變對於其他線程來說是一種“原子化操作”。

· HashMap在多線程環境下使用需要注意什麼?爲什麼?

  • 併發操作HashMap,是有可能帶來死循環以及數據丟失的問題的。當我們調用get()這個鏈表中不存在的元素的時候,就會出現死循環。
  • HashMap在併發執行put操作時會引起死循環,導致CPU利用率接近100%。因爲多線程會導致HashMap的Node鏈表形成環形數據結構,一旦形成環形數據結構,Node的next節點永遠不爲空,就會在獲取Node時產生死循環。
  • 一句話總結就是,併發環境下的rehash過程可能會帶來循環鏈表,導致死循環致使線程掛掉。
  • HashTable,它並未使用分段鎖,而是鎖住整個數組,高併發環境下效率非常的低,會導致大量線程等待。同樣的,Synchronized關鍵字、Lock性能都不如分段鎖實現的ConcurrentHashMap。

· Java程序中啓動一個線程是用run還是start?

start方法:通過該方法啓動線程的同時也創建了一個線程,真正實現了多線程。而run方法只是當初普通的方法調用.

· 什麼是守護線程?有什麼用?

守護線程(即daemon thread)是服務線程,處理後臺事務,如垃圾回收等,JVM內部的實現是如果運行的程序只剩下守護線程的話,程序將終止運行,直接結束。所以守護線程是作爲輔助線程存在的。應用程序裏的線程,一般都是用戶自定義線程,用戶也可以在應用程序代碼自定義守護線程,只需要調用Thread類的t.setDaemon(true);設置一下即可。

· 線程和進程的差別是什麼?

  • 進程是系統進行資源分配的基本單位,有獨立的內存地址空間; 線程是CPU調度的基本單位,沒有單獨地址空間,有獨立的棧,局部變量,寄存器, 程序計數器等。
  • 創建進程的開銷大,包括創建虛擬地址空間等需要大量系統資源; 創建線程開銷小,基本上只有一個內核對象和一個堆棧。
  • 一個進程無法直接訪問另一個進程的資源;同一進程內的多個線程共享進程的資源。
  • 進程切換開銷大,線程切換開銷小;進程間通信開銷大,線程間通信開銷小。
  • 線程屬於進程,不能獨立執行。每個進程至少要有一個線程,成爲主線程

· Java裏面的Threadlocal是怎樣實現的?

  • 作用:在併發環境下避免競爭、簡化編程,高效.在併發環境下提供了一個邏輯上全局的訪問點訪問線程本地對象
  • 原理: 每個線程內部都有一個hastable作爲存儲存儲器保存線程本地對象集,ThreadLocal實例對象作爲key可以被所有線程共享,這個實例對象就是我們所說得全局的訪問點,通過它可以訪問線程本地對象。所以我們可以這樣說:通過ThreadLocal提供了邏輯上全局的線程本地對象。

· ConcurrentHashMap的實現原理是?

在ConcurrentHashMap中,它使用了多個鎖代替HashTable中的單個鎖,也就是鎖分離技術(Lock Stripping)。 每個hash區間使用的ReentrantLock鎖。

ConcurrentHashMap源碼剖析

· sleep和wait區別

  1. 這兩個方法來自不同的類分別是Thread和Object
  2. 最主要是sleep方法沒有釋放鎖,而wait方法釋放了鎖,使得其他線程可以使用同步控制塊或者方法。
  3. wait,notify和notifyAll只能在同步控制方法或者同步控制塊裏面使用,而sleep可以在任何地方使用.

 
synchronized(x){ x.notify() //或者wait() } 
  1. sleep必須捕獲異常,而wait,notify和notifyAll不需要捕獲異常

· notify和notifyAll區別

  • wait此方法導致當前線程(稱之爲 T)將其自身放置在對象的等待集中,然後放棄此對象上的所有同步要求。
  • 被wait的線程,想要繼續運行的話,它必須滿足2個條件:
  1. 由其他線程notify或notifyAll了,並且當前線程被通知到了
  2. 經過和其他線程進行鎖競爭,成功獲取到鎖了
  • 2個條件,缺一不可。其實在實現層面,notify和notifyAll都達到相同的效果,都只會有一個線程繼續運行。但notifyAll免去了,線程運行完了通知其他線程的必要,因爲已經通知過了。什麼時候用notify,什麼時候使用notifyAll,這就得看實際的情況了。

· 什麼是條件鎖、讀寫鎖、自旋鎖、可重入鎖?

  1. 條件鎖
  2. 在lock中提供了與之關聯的條件,一個鎖可能關聯一個或多個條件,這些條件通過condition接口聲明。目的是運行線程獲取鎖並且查看等待某一個條件是否滿足,如果不滿足則掛起直到某個線程喚醒它們。condition接口提供了掛起線程和喚起線程的機制;
  3. 讀寫鎖
  4. Java中讀寫鎖有個接口java.util.concurrent.locks.ReadWriteLock,也有具體的實現ReentrantReadWriteLock,它們不是繼承關係,但都是基於 AbstractQueuedSynchronizer來實現。ReentrantReadWriteLock的鎖策略有兩種,分爲公平策略和非公平策略

注意: 在同一線程中,持有讀鎖後,不能直接調用寫鎖的lock方法 ,否則會造成死鎖。。

  1. 可重入(Reentrant)鎖
  2. 如果鎖具備可重入性,則稱作爲可重入鎖。像synchronized和 ReentrantLock都是可重入鎖。假如某一時刻,線程A執行到了method1,此時線程 A獲取了這個對象的鎖,而由於method2也是synchronized方法,因爲可重入,線程A不需要申請加鎖即可執行。(可以理解鎖的維度是線程,所以已擁有鎖不需要再次申請加鎖)。不可重入鎖(自旋鎖):不可以再次進入方法A,也就是說獲得鎖進入方法A是此線程在釋放鎖錢唯一的一次進入方法A。
  3. 可中斷鎖
  4. 可中斷鎖:顧名思義,就是可以相應中斷的鎖。在Java中,synchronized就不是可中斷鎖,而Lock是可中斷鎖。

· 線程池ThreadPoolExecutor的幾個參數說明?

  • corePoolSize 核心線程池大小
  • maximumPoolSize 線程池最大容量大小
  • keepAliveTime 線程池空閒時,線程存活的時間,
  • TimeUnit 時間單位
  • ThreadFactory 創建線程的工廠
  • BlockingQueue任務隊列,用於保存等待執行的任務的阻塞隊列。比如基於數組的有界ArrayBlockingQueue、,基於鏈表的無界LinkedBlockingQueue,最多隻有一個元素的同步隊列SynchronousQueue,優先級隊列PriorityBlockingQueue
  • RejectedExecutionHandler 線程拒絕策略
  • 線程池在執行excute方法時,主要有以下四種情況:
  1. 如果當前運行的線程少於corePoolSize,則創建新線程來執行任務(需要獲得全局鎖)
  2. 如果運行的線程等於或多於corePoolSize ,則將任務加入BlockingQueue
  3. 如果無法將任務加入BlockingQueue(隊列已滿),則創建新的線程來處理任務(需要獲得全局鎖)
  4. 如果創建新線程將使當前運行的線程超出maxiumPoolSize,任務將被拒絕,並調用RejectedExecutionHandler.rejectedExecution()方法。
  5. 線程池採取上述的流程進行設計是爲了減少獲取全局鎖的次數。在線程池完成預熱(當前運行的線程數大於或等於corePoolSize)之後,幾乎所有的excute方法調用都執行步驟2。

”我自己是一名從事了十餘年的後端的老程序員,辭職後目前在做講師,近期我花了一個月整理了一份最適合2018年學習的JAVA乾貨(裏面有高可用、高併發、高性能及分佈式、Jvm性能調優、Spring源碼,MyBatis,Netty,Redis,Kafka,Mysql,Zookeeper,Tomcat,Docker,Dubbo,Nginx等多個知識點的架構資料)從事後端的小夥伴們都可以來了解一下的,這裏是程序員祕密聚集地,各位還在架構師的道路上掙扎的小夥伴們速來。“

加QQ羣:585550789(名額有限哦!)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章