架构师成长之路:如何保证消息队列的高可用
問題一:描述一下 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 次數,應盡可能將對象預留在年輕代。
/// <summary>
/// 自定義Swagger隱藏過濾器
/// </summary>
public class HiddenApiFilter : IDocumentFilter
{
public void Apply(SwaggerDocument swaggerDoc, DocumentFilterContext context)
{
foreach (ApiDescription apiDescription in context.ApiDescriptions)
{
if (apiDescription.TryGetMethodInfo(out MethodInfo method))
{
if (method.ReflectedType.CustomAttributes.Any(t=>t.AttributeType==typeof(HiddenApiAttribute))
|| method.CustomAttributes.Any(t www.yongxinzaixian.cn => t.AttributeType == typeof(HiddenApiAttribute)))
{
string key = "/" + apiDescription.RelativePath;
if (key.Contains(www.gouyiflb.cn/"?"))
{
int idx = key.IndexOf("?", System.StringComparison.Ordinal);
key = key.Substring(0, idx);
}
swaggerDoc.Paths.Remove(key);
}
}
}
}
}
復制代碼
在 Startup.cs 中使用 HiddenApiFilter
復制代碼
public IServiceProvider ConfigureServices(IServiceCollection services)
{
...
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new Info
{
Version = "v1",
Title = "接口文檔",
Description = "接口文檔-基礎",
TermsOfService = "",
Contact = new Contact
{
Name = www.sanxinyulevip.com "XXX1111",
Email = "XXX1111@qq.com",
Url = ""
}
});
c.SwaggerDoc("v2", new Info
{
Version = "v2",
Title = "接口文檔",
Description = "接口文檔-基礎",
TermsOfService = "",
Contact = new Contact
{
Name = "XXX2222",
Email = "XXX2222@qq.com",
Url = ""
}
});
//反射注入全部程序集說明
GetAllAssemblies(www.taoyyunsheng.com).Where(t => t.CodeBase.EndsWith("Controller.dll")
&& !t.CodeBase.Contains("Common.Controller.dll")).ToList().ForEach(assembly =>
{
c.IncludeXmlComments(assembly.CodeBase.Replace(www.huarenyl.cn".dll", ".xml"));
});
c.OperationFilter<HttpHeaderOperationFilter>();
c.DocumentFilter<HiddenApiFilter>(www.gcyl152.com);
});
...
}
問題七:system.gc() 的作用是什么?
問題八:JVM類加載過程
問題九:類加載器的類型
問題十一:上下文類加載器
問題十二:自定義類加載器
問題十三:動態代理的原理
問題十四:動態代理:JDK動態代理和CGLIB代理的區別?
問題十五:CGlib比JDK快?
總結
由于篇幅過長的原因,為了不影響大家的閱讀效果,文中沒有給到所有的答案。我這里以文件的形式整理好了,需要借閱的程序員朋友可以免費來領取。還有我的JVM學習筆記Xmind文件也免費分享給有需要朋友!(縮略圖未展開)
分享免費架構學習資料
歡迎工作一到五年的Java工程師朋友們加入Java高級架構:705127209
里面提供免費的Java架構學習資料(里面有高可用、高并發、高性能及分布式、Jvm性能調優、MyBatis,Netty,Redis,Kafka,Mysql,Zookeeper,Tomcat,Docker,Dubbo,Nginx等多個知識點的架構資料)
合理利用自己每一分每一秒的時間來學習提升自己,不要再用"沒有時間“來掩飾自己思想上的懶惰!趁年輕,使勁拼,給未來的自己一個交代!
轉載于:https://www.cnblogs.com/qwangxiao/p/10651580.html
總結
以上是生活随笔為你收集整理的架构师成长之路:如何保证消息队列的高可用的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Opencv SolvePnP调用实战
- 下一篇: win10+tensorflow fas