1. 程式人生 > >架構師成長之路:如何保證訊息佇列的高可用

架構師成長之路:如何保證訊息佇列的高可用

開發十年,就只剩下這套架構體系了! >>>   

問題一:描述一下 JVM 的記憶體區域

程式計數器(PC,Program Counter Register)。在 JVM 規範中,每個執行緒都有它自己的程式計數器,並且任何時間一個執行緒都只有一個方法在執行,也就是所謂的當前方法。程式計數器會儲存當前執行緒正在執行的 Java 方法的 JVM 指令地址;或者,如果是在執行本地方法,則是未指定值(undefined)。

Java 虛擬機器棧(Java Virtual Machine Stack),早期也叫 Java 棧。每個執行緒在建立時都會建立一個虛擬機器棧,其內部儲存一個個的棧幀(Stack Frame),對應著一次次的 Java 方法呼叫。前面談程式計數器時,提到了當前方法;同理,在一個時間點,對應的只會有一個活動的棧幀,通常叫作當前幀,方法所在的類叫作當前類。如果在該方法中呼叫了其他方法,對應的新的棧幀會被創建出來,成為新的當前幀,一直到它返回結果或者執行結束。JVM 直接對 Java 棧的操作只有兩個,就是對棧幀的壓棧和出棧。棧幀中儲存著區域性變量表、運算元(operand)棧、動態連結、方法正常退出或者異常退出的定義等。

堆(Heap),它是 Java 記憶體管理的核心區域,用來放置 Java 物件實例,幾乎所有建立的Java 物件實例都是被直接分配在堆上。堆被所有的執行緒共享,在虛擬機器啟動時,我們指定的“Xmx”之類引數就是用來指定最大堆空間等指標。理所當然,堆也是垃圾收集器重點照顧的區域,所以堆內空間還會被不同的垃圾收集器進行進一步的細分,最有名的就是新生代、老年代的劃分。

方法區(Method Area)。這也是所有執行緒共享的一塊記憶體區域,用於儲存所謂的元(Meta)資料,例如類結構資訊,以及對應的運行時常量池、欄位、方法程式碼等。由於早期的 Hotspot JVM 實現,很多人習慣於將方法區稱為永久代(Permanent Generation)。Oracle JDK 8 中將永久代移除,同時增加了元資料區(Metaspace)。

運行時常量池(Run-Time Constant Pool),這是方法區的一部分。如果仔細分析過反編譯的類檔案結構,你能看到版本號、欄位、方法、超類、介面等各種資訊,還有一項資訊就是常量池。Java 的常量池可以存放各種常量資訊,不管是編譯期生成的各種字面量,還是需要在運行時決定的符號引用,所以它比一般語言的符號表儲存的資訊更加寬泛。

本地方法棧(Native Method Stack)。它和 Java 虛擬機器棧是非常相似的,支援對本地方法的呼叫,也是每個執行緒都會建立一個。在 Oracle Hotspot JVM 中,本地方法棧和 Java 虛擬機器棧是在同一塊兒區域,這完全取決於技術實現的決定,並未在規範中強制。

問題二:造成OOM的原因有哪幾種?

堆記憶體不足是最常見的 OOM 原因之一,丟擲的錯誤資訊是“java.lang.OutOfMemoryError:Java heap space”,原因可能千奇百怪,例如,可能存在記憶體洩漏問題;也很有可能就是堆的大小不合理,比如我們要處理比較可觀的資料量,但是沒有顯式指定 JVM 堆大小或者指定數值偏小;或者出現 JVM 處理引用不及時,導致堆積起來,記憶體無法釋放等。

虛擬機器棧和本地方法棧,這里要稍微複雜一點。如果我們寫一段程式不斷的進行遞迴呼叫,而且沒有退出條件,就會導致不斷地進行壓棧。類似這種情況,JVM 實際會丟擲StackOverFlowError;當然,如果 JVM 試圖去擴充套件棧空間的的時候失敗,則會丟擲OutOfMemoryError。

對於老版本的 Oracle JDK,因為永久代的大小是有限的,並且 JVM 對永久代垃圾回收(如,常量池回收、解除安裝不再需要的型別)非常不積極,所以當我們不斷新增新型別的時候,永久代出現OutOfMemoryError 也非常多見,尤其是在運行時存在大量動態型別生成的場合;類似 Intern 字元串快取佔用太多空間,也會導致 OOM 問題。對應的異常資訊,會標記出來和永久代相關:“java.lang.OutOfMemoryError: PermGenspace

問題三:GC 演算法

複製(Copying)演算法,我前面講到的新生代 GC,基本都是基於複製演算法,將活著的物件複製到 to 區域,拷貝過程中將物件順序放置,就可以避免記憶體碎片化。這麼做的代價是,既然要進行複製,既要提前預留記憶體空間,有一定的浪費;另外,對於 G1 這種分拆成為大量 region 的 GC,複製而不是移動,意味著 GC 需要維護 region 之間物件引用關係,這個開銷也不小,不管是記憶體佔用或者時間開銷。

標記 - 清除(Mark-Sweep)演算法,首先進行標記工作,標識出所有要回收的物件,然後進行清除。這麼做除了標記、清除過程效率有限,另外就是不可避免的出現碎片化問題,這就導致其不適合特別大的堆;否則,一旦出現 Full GC,暫停時間可能根本無法接受。

標記 - 整理(Mark-Compact),類似於標記 - 清除,但為避免記憶體碎片化,它會在清理過程中將物件移動,以確保移動後的物件佔用連續的記憶體空間。

問題四: G1 垃圾回收器採用的是什麼垃圾回收演算法?

從 GC 演算法的角度,G1 選擇的是複合演算法,可以簡化理解為:

在新生代,G1 採用的仍然是並行的複製演算法,所以同樣會發生 Stop-The-World 的暫停。

在老年代,大部分情況下都是併發標記,而整理(Compact)則是和新生代 GC 時捎帶進行,並且不是整體性的整理,而是增量進行的。

問題五:GC 調優思路

從效能的角度看,通常關注三個方面,記憶體佔用(footprint)、延時(latency)和吞吐量(throughput),大多數情況下調優會側重於其中一個或者兩個方面的目標,很少有情況可以兼顧三個不同的角度。當然,除了上面通常的三個方面,也可能需要考慮其他 GC 相關的場景,例如,OOM 也可能與不合理的 GC 相關引數有關;或者,應用啟動速度方面的需求,GC 也會是個考慮的方面。 基本的調優思路可以總結為:

理解應用需求和問題,確定調優目標。假設,我們開發了一個應用服務,但發現偶爾會出現效能抖動,出現較長的服務停頓。評估使用者可接受的響應時間和業務量,將目標簡化為,希望 GC 暫停盡量控制在 200ms 以內,並且保證一定標準的吞吐量。

掌握 JVM 和 GC 的狀態,定位具體的問題,確定真的有 GC 調優的必要。具體有很多方法,比如,通過 jstat 等工具檢視 GC 等相關狀態,可以開啟 GC 日誌,或者是利用作業系統提供的診斷工具等。例如,通過追蹤 GC 日誌,就可以查詢是不是 GC 在特定時間發生了長時間的暫停,進而導致了應用響應不及時。

選擇的 GC 型別是否符合我們的應用特徵,如果是,具體問題表現在哪里,是 Minor GC 過長,還是 Mixed GC 等出現異常停頓情況;如果不是,考慮切換到什麼型別,如 CMS 和 G1 都是更側重於低延遲的 GC 選項。

通過分析確定具體調整的引數或者軟硬體配置。驗證是否達到調優目標,如果達到目標,即可以考慮結束調優;否則,重複完成分析、調整、驗證這 個過程。

問題六:如何提高JVM的效能?

新物件預留在年輕代 通過設定一個較大的年輕代預留新物件,設定合理的 Survivor 區並且提供 Survivor 區的使用率,可以將年輕物件儲存在年輕代。

大物件進入年老代 使用引數-XX:PetenureSizeThreshold 設定大物件直接進入年老代的閾值

設定物件進入年老代的年齡 這個閾值的最大值可以通過引數-XX:MaxTenuringThreshold 來設定,預設值是 15

穩定的 Java 堆 獲得一個穩定的堆大小的方法是使-Xms 和-Xmx 的大小一致,即最大堆和最小堆 (初始堆) 一樣。

增大吞吐量提升系統性能 –Xmx380m –Xms3800m:設定 Java 堆的最大值和初始值。一般情況下,為了避免堆記憶體的頻繁震盪,導致系統性能下降,我們的做法是設定最大堆等於最小堆。假設這裡把最小堆減少為最大堆的一半,即 1900m,那麼 JVM 會盡可能在 1900MB 堆空間中執行,如果這樣,發生 GC 的可能性就會比較高; -Xss128k:減少執行緒棧的大小,這樣可以使剩餘的系統記憶體支援更多的執行緒; -Xmn2g:設定年輕代區域大小為 2GB; –XX:+UseParallelGC:年輕代使用並行垃圾回收收集器。這是一個關注吞吐量的收集器,可以儘可能地減少 GC 時間。 –XX:ParallelGC-Threads:設定用於垃圾回收的執行緒數,通常情況下,可以設定和 CPU 數量相等。但在 CPU 數量比較多的情況下,設定相對較小的數值也是合理的; –XX:+UseParallelOldGC:設定年老代使用並行回收收集器。

嘗試使用大的記憶體分頁 –XX:+LargePageSizeInBytes:設定大頁的大小。 記憶體分頁 (Paging) 是在使用 MMU 的基礎上,提出的一種記憶體管理機制。它將虛擬地址和實體地址按固定大小(4K)分割成頁 (page) 和頁幀 (page frame),並保證頁與頁幀的大小相同。這種機制,從資料結構上,保證了訪問記憶體的高效,並使 OS 能支援非連續性的記憶體分配。

使用非佔有的垃圾回收器 為降低應用軟體的垃圾回收時的停頓,首先考慮的是使用關注系統停頓的 CMS 回收器,其次,為了減少 Full GC 次數,應儘可能將物件預留在年輕代。

問題七:system.gc() 的作用是什麼?

問題八:JVM類載入過程

問題九:類載入器的型別

問題十一:上下文類載入器

問題十二:自定義類載入器

問題十三:動態代理的原理

問題十四:動態代理:JDK動態代理和CGLIB代理的區別?

問題十五:CGlib比JDK快?

總結

由於篇幅過長的原因,為了不影響大家的閱讀效果,文中沒有給到所有的答案。我這裡以檔案的形式整理好了,需要借閱的程式設計師朋友可以免費來領取。還有我的JVM學習筆記Xmind檔案也免費分享給有需要朋友!(縮圖未展開)

 

分享免費架構學習資料

 

歡迎工作一到五年的Java工程師朋友們加入Java高階架構:705127209

裡面提供免費的Java架構學習資料(裡面有高可用、高併發、高效能及分散式、Jvm效能調優、MyBatis,Netty,Redis,Kafka,Mysql,Zookeeper,Tomcat,Docker,Dubbo,Nginx等多個知識點的架構資料)

合理利用自己每一分每一秒的時間來學習提升自己,不要再用"沒有時間“來掩飾自己思想上的懶惰!趁年輕,使勁拼,給未來的自己一