天天看點

小白學java-JVM知識點總結

1.什麼是JVM?

JVM——Java 虛拟機,它是 Java 實作平台⽆關性的基⽯。 Java 程式運⾏的時候,編譯器将 Java ⽂件編譯成平台⽆關的 Java 位元組碼⽂件( .class ) , 接下來對 應平台 JVM 對位元組碼⽂件進⾏解釋,翻譯成對應平台比對的機器指令并運⾏。 同時 JVM 也是⼀個跨語⾔的平台,和語⾔⽆關,隻和 class 的⽂件格式關聯,任何語⾔,隻要 能翻譯成符合規範的位元組碼⽂件,都能被 JVM 運⾏。

小白學java-JVM知識點總結
小白學java-JVM知識點總結

JVM 記憶體分為線程私有區和線程共享區,其中 ⽅法區 和 堆 是線程共享區, 虛拟機棧 、 本地 ⽅法棧 和 程式計數器 是線程隔離的資料區。 1 、程式計數器 程式計數器( Program Counter Register )也被稱為 PC 寄存器,是⼀塊較⼩的記憶體空間。 它可以看作是目前線程所執⾏的位元組碼的⾏号指⽰器。 2 、 Java 虛拟機棧 Java 虛拟機棧( Java Virtual Machine Stack )也是線程私有的,它的⽣命周期與線程相同。 Java 虛拟機棧描述的是 Java ⽅法執⾏的線程記憶體模型:⽅法執⾏時, JVM 會同步建立⼀個棧 幀,⽤來存儲局部變量表、操作數棧、動态連接配接等。

小白學java-JVM知識點總結

3 、本地⽅法棧 本地⽅法棧( Native Method Stacks )與虛拟機棧所發揮的作⽤是⾮常相似的,其差別隻是虛 拟機棧為虛拟機執⾏ Java ⽅法(也就是位元組碼)服務,⽽本地⽅法棧則是為虛拟機使⽤到的本 地( Native )⽅法服務。 Java 虛拟機規範允許本地⽅法棧被實作成固定⼤⼩的或者是根據計算動态擴充和收縮的。 4 、 Java 堆 對于 Java 應⽤程式來說, Java 堆( Java Heap )是虛拟機所管理的記憶體中最⼤的⼀塊。 Java 堆是 被所有線程共享的⼀塊記憶體區域,在虛拟機啟動時建立。此記憶體區域的唯⼀⽬的就是存放對 象執行個體, Java ⾥ “ ⼏乎 ” 所有的對象執行個體都在這⾥配置設定記憶體。 Java 堆是垃圾收集器管理的記憶體區域,是以⼀些資料中它也被稱作 “GC 堆 ” ( Garbage Collected Heap ,)。從回收記憶體的⾓度看,由于現代垃圾收集器⼤部分都是基于分代收集理論設計 的,是以 Java 堆中經常會出現 新⽣代 、 ⽼年代 、 Eden 空間 、 From Survivor 空間 、 To Survivor 空間 等名詞,需要注意的是這種劃分隻是根據垃圾回收機制來進⾏的劃分,不是 Java 虛拟機規範本⾝制定的。

小白學java-JVM知識點總結

5. ⽅法區 ⽅法區是⽐較特别的⼀塊區域,和堆類似,它也是各個線程共享的記憶體區域,⽤于存儲已被 虛拟機加載的類型資訊、常量、靜态變量、即時編譯器編譯後的代碼緩存等資料。 它特别在 Java 虛拟機規範對它的限制⾮常寬松,是以⽅法區的具體實作曆經了許多變遷,例如 jdk1.7 之前使⽤永久代作為⽅法區的實作。

小白學java-JVM知識點總結
小白學java-JVM知識點總結

4. 為什麼使⽤元空間替代永久代作為⽅法區的實作? Java 虛拟機規範規定的⽅法區隻是換種⽅式實作。有客觀和主觀兩個原因。 客觀上使⽤永久代來實作⽅法區的決定的設計導緻了 Java 應⽤更容易遇到記憶體溢出的問題 (永久代有 -XX : MaxPermSize 的上限,即使不設定也有預設⼤⼩,⽽ J9 和 JRockit 隻要沒 有觸碰到程序可⽤記憶體的上限,例如 32 位系統中的 4GB 限制,就不會出問題),⽽且有極 少數⽅法 (例如 String::intern() )會因永久代的原因⽽導緻不同虛拟機下有不同的表現。 主觀上當 Oracle 收購 BEA 獲得了 JRockit 的所有權後,準備把 JRockit 中的優秀功能,譬如 Java Mission Control 管理⼯具,移植到 HotSpot 虛拟機時,但因為兩者對⽅法區實作的差異 ⽽⾯臨諸多困難。考慮到 HotSpot 未來的發展,在 JDK 6 的 時候 HotSpot 開發團隊就有放棄 永久代,逐漸改為采⽤本地記憶體( Native Memory )來實作⽅法區的計劃了,到了 JDK 7 的 HotSpot ,已經把原本放在永久代的字元串常量池、靜态變量等移出,⽽到了 JDK 8 ,終于 完全廢棄了永久代的概念,改⽤與 JRockit 、 J9 ⼀樣在本地記憶體中實作的元空間( Meta- 206 space )來代替,把 JDK 7 中永久代還剩餘的内容(主要是類型資訊)全部移到元空間中。

5. 對象建立的過程了解嗎? 在 JVM 中對象的建立,我們從⼀個 new 指令開始: ⾸先檢查這個指令的參數是否能在常量池中定位到⼀個類的符号引⽤ 檢查這個符号引⽤代表的類是否已被加載、解析和初始化過。如果沒有,就先執⾏相應的 類加載過程 類加載檢查通過後,接下來虛拟機将為新⽣對象配置設定記憶體。 記憶體配置設定完成之後,虛拟機将配置設定到的記憶體空間(但不包括對象頭)都初始化為零值。 接下來設定對象頭,請求頭⾥包含了對象是哪個類的執行個體、如何才能找到類的中繼資料信 息、對象的哈希碼、對象的 GC 分代年齡等資訊。

小白學java-JVM知識點總結

6. 什麼是指針碰撞?什麼是空閑清單? 記憶體配置設定有兩種⽅式, 指針碰撞 ( Bump The Pointer )、 空閑清單 ( Free List )。

指針碰撞:假設 Java 堆中記憶體是絕對規整的,所有被使⽤過的記憶體都被放在⼀邊,空閑的 記憶體被放在另⼀邊,中間放着⼀個指針作為分界點的指⽰器,那所配置設定記憶體就僅僅是把那 個指針向空閑空間⽅向挪動⼀段與對象⼤⼩相等的距離,這種配置設定⽅式稱為 “ 指針碰撞 ” 。 空閑清單:如果 Java 堆中的記憶體并不是規整的,已被使⽤的記憶體和空閑的記憶體互相交錯在 ⼀起,那就沒有辦法簡單地進⾏指針碰撞了,虛拟機就必須維護⼀個清單,記錄上哪些内 存塊是可⽤的,在配置設定的時候從清單中找到⼀塊⾜夠⼤的空間劃分給對象執行個體,并更新列 表上的記錄,這種配置設定⽅式稱為 “ 空閑清單 ” 。 兩種⽅式的選擇由 Java 堆是否規整決定, Java 堆是否規整是由選擇的垃圾收集器是否具有 壓縮整理能⼒決定的。

小白學java-JVM知識點總結
小白學java-JVM知識點總結
小白學java-JVM知識點總結
小白學java-JVM知識點總結

14.Java 中可作為 GC Roots 的對象有哪⼏種? 可以作為 GC Roots 的主要有四種對象: 虛拟機棧 ( 棧幀中的本地變量表 ) 中引⽤的對象 ⽅法區中類靜态屬性引⽤的對象 ⽅法區中常量引⽤的對象 本地⽅法棧中 JNI 引⽤的對象

15. 說⼀下對象有哪⼏種引⽤? Java 中的引⽤有四種,分為強引⽤( Strongly Reference )、軟引⽤( Soft Reference )、弱引⽤ ( Weak Reference )和虛引⽤( Phantom Reference ) 4 種,這 4 種引⽤強度依次逐漸減弱。

小白學java-JVM知識點總結

16.finalize() ⽅法了解嗎?有什麼作⽤? ⽤⼀個不太貼切的⽐喻,垃圾回收就是古代的秋後問斬, finalize() 就是⼑下留⼈,在⼈犯被處 決之前,還要做最後⼀次審計,青天⼤⽼爺看看有沒有什麼冤情,需不需要⼑下留⼈。 如果對象在進⾏可達性分析後發現沒有與 GC Roots 相連接配接的引⽤鍊,那它将會被第⼀次标 記,随後進⾏⼀次篩選,篩選的條件是此對象是否有必要執⾏ finalize() ⽅法。如果對象在在 finalize() 中成功拯救⾃⼰ —— 隻要重新與引⽤鍊上的任何⼀個對象建⽴關聯即可,譬如把⾃⼰ ( this 關鍵字)指派給某個類變量或者對象的成員變量,那在第⼆次标記時它就 ” 逃過⼀劫 “ ; 但是如果沒有抓住這個機會,那麼對象就真的要被回收了。

17.Java 堆的記憶體分區了解嗎? 按照垃圾收集,将 Java 堆劃分為 新⽣代 ( Young Generation ) 和 ⽼年代( Old Generation ) 兩個區域,新⽣代存放存活時間短的對象,⽽每次回收後存活的少量對象,将會逐漸晉升到 ⽼年代中存放。 ⽽新⽣代又可以分為三個區域, eden 、 from 、 to ,⽐例是 8 : 1 : 1 ,⽽新⽣代的記憶體分區同樣 是從垃圾收集的⾓度來配置設定的。

小白學java-JVM知識點總結

18. 垃圾收集算法了解嗎? 垃圾收集算法主要有三種: 1. 标記 - 清除算法 見名知義, 标記 - 清除 ( Mark-Sweep )算法分為兩個階段: 标記 : 标記出所有需要回收的對象 清除 :回收所有被标記的對象 标記 - 清除算法⽐較基礎,但是主要存在兩個缺點: 執⾏效率不穩定,如果 Java 堆中包含⼤量對象,⽽且其中⼤部分是需要被回收的,這時必 須進⾏⼤量标記和清除的動作,導緻标記和清除兩個過程的執⾏效率都随對象數量增長⽽ 降低。 記憶體空間的碎⽚化問題,标記、清除之後會産⽣⼤量不連續的記憶體碎⽚,空間碎⽚太多可 能會導緻當以後在程式運⾏過程中需要配置設定較⼤對象時⽆法找到⾜夠的連續記憶體⽽不得不 提前觸發另⼀次垃圾收集動作。 2. 标記 - 複制算法 标記 - 複制算法解決了标記 - 清除算法⾯對⼤量可回收對象時執⾏效率低的問題。 過程也⽐較簡單:将可⽤記憶體按容量劃分為⼤⼩相等的兩塊,每次隻使⽤其中的⼀塊。當這 ⼀塊的記憶體⽤完了,就将還存活着的對象複制到另外⼀塊上⾯,然後再把已使⽤過的記憶體空 間⼀次清理掉。 這種算法存在⼀個明顯的缺點:⼀部分空間沒有使⽤,存在空間的浪費。 新⽣代垃圾收集主要采⽤這種算法,因為新⽣代的存活對象⽐較少,每次複制的隻是少量的 存活對象。當然,實際新⽣代的收集不是按照這個⽐例。 3. 标記 - 整理算法 為了降低記憶體的消耗,引⼊⼀種針對性的算法: 标記 - 整理 ( Mark-Compact )算法。 其中的标記過程仍然與 “ 标記 - 清除 ” 算法⼀樣,但後續步驟不是直接對可回收對象進⾏清理, ⽽是讓所有存活的對象都向記憶體空間⼀端移動,然後直接清理掉邊界以外的記憶體。 标記 - 整理算法主要⽤于⽼年代,移動存活對象是個極為負重的操作,⽽且這種操作需要 Stop The World 才能進⾏,隻是從整體的吞吐量來考量,⽼年代使⽤标記 - 整理算法更加合适。 19. 說⼀下新⽣代的區域劃分? 新⽣代的垃圾收集主要采⽤标記 - 複制算法,因為新⽣代的存活對象⽐較少,每次複制少量的 存活對象效率⽐較⾼。 基于這種算法,虛拟機将記憶體分為⼀塊較⼤的 Eden 空間和兩塊較⼩的 Survivor 空間,每次配置設定 記憶體隻使⽤ Eden 和其中⼀塊 Survivor 。發⽣垃圾收集時,将 Eden 和 Survivor 中仍然存活的對象 ⼀次性複制到另外⼀塊 Survivor 空間上,然後直接清理掉 Eden 和已⽤過的那塊 Survivor 空間。 預設 Eden 和 Survivor 的⼤⼩⽐例是 8 ∶ 1 。

小白學java-JVM知識點總結
小白學java-JVM知識點總結
小白學java-JVM知識點總結

Young GC 之前檢查⽼年代 :在要進⾏ Young GC 的時候,發現 ⽼年代可⽤的連續記憶體空 間 < 新⽣代曆次 Young GC 後升⼊⽼年代的對象總和的平均⼤⼩ ,說明本次 Young GC 後可 能升⼊⽼年代的對象⼤⼩,可能超過了⽼年代目前可⽤記憶體空間 , 那就會觸發 Full GC 。 Young GC 之後⽼年代空間不⾜ :執⾏ Young GC 之後有⼀批對象需要放⼊⽼年代,此時⽼ 年代就是沒有⾜夠的記憶體空間存放這些對象了,此時必須⽴即觸發⼀次 Full GC ⽼年代空間不⾜ ,⽼年代記憶體使⽤率過⾼,達到⼀定⽐例,也會觸發 Full GC 。 空間配置設定擔保失敗 ( Promotion Failure ),新⽣代的 To 區放不下從 Eden 和 From 拷貝過 來對象,或者新⽣代對象 GC 年齡到達門檻值需要晉升這兩種情況,⽼年代如果放不下的話 都會觸發 Full GC 。 ⽅法區記憶體空間不⾜ :如果⽅法區由永久代實作,永久代空間不⾜ Full GC 。 System.gc() 等指令觸發 : System.gc() 、 jmap -dump 等指令會觸發 full gc

小白學java-JVM知識點總結

長期存活的對象将進⼊⽼年代

在對象的對象頭資訊中存儲着對象的疊代年齡 , 疊代年齡會在每次 YoungGC 之後對象的移區操 作中增加 , 每⼀次移區年齡加⼀ . 當這個年齡達到 15( 預設 ) 之後 , 這個對象将會被移⼊⽼年代。 可以通過這個參數設定這個年齡值。 - XX:MaxTenuringThreshold ⼤對象直接進⼊⽼年代 有⼀些占⽤⼤量連續記憶體空間的對象在被加載就會直接進⼊⽼年代 . 這樣的⼤對象⼀般是⼀些 數組 , 長字元串之類的對。 HotSpot 虛拟機提供了這個參數來設定。 - XX : PretenureSizeThreshold 動态對象年齡判定 為了能更好地适應不同程式的記憶體狀況, HotSpot 虛拟機并不是永遠要求對象的年齡必須達到 - XX : MaxTenuringThreshold 才能晉升⽼年代,如果在 Survivor 空間中相同年齡所有對象⼤⼩的 總和⼤于 Survivor 空間的⼀半,年齡⼤于或等于該年齡的對象就可以直接進⼊⽼年代。 空間配置設定擔保 假如在 Young GC 之後,新⽣代仍然有⼤量對象存活,就需要⽼年代進⾏配置設定擔保,把 Survivor ⽆法容納的對象直接送⼊⽼年代。

小白學java-JVM知識點總結
小白學java-JVM知識點總結

CMS 收集器 CMS ( Concurrent Mark Sweep )收集器是⼀種以擷取最短回收停頓時間為⽬标的收集器,同 樣是⽼年代的收集器,采⽤标記 - 清除算法。 Garbage First 收集器 Garbage First (簡稱 G1 )收集器是垃圾收集器的⼀個颠覆性的産物,它開創了局部收集的設計 思路和基于 Region 的記憶體布局形式。

小白學java-JVM知識點總結
小白學java-JVM知識點總結

27.G1 垃圾收集器了解嗎? Garbage First (簡稱 G1 )收集器是垃圾收集器的⼀個颠覆性的産物,它開創了局部收集的設計 思路和基于 Region 的記憶體布局形式。 雖然 G1 也仍是遵循分代收集理論設計的,但其堆記憶體的布局與其他收集器有⾮常明顯的差 異。以前的收集器分代是劃分新⽣代、⽼年代、持久代等。 G1 把連續的 Java 堆劃分為多個⼤⼩相等的獨⽴區域( Region ),每⼀個 Region 都可以根據需 要,扮演新⽣代的 Eden 空間、 Survivor 空間,或者⽼年代空間。收集器能夠對扮演不同⾓⾊的 Region 采⽤不同的政策去處理。

這樣就避免了收集整個堆,⽽是按照若⼲個 Region 集進⾏收集,同時維護⼀個優先級清單, 跟蹤各個 Region 回收的 “ 價值,優先收集價值⾼的 Region 。 G1 收集器的運⾏過程⼤緻可劃分為以下四個步驟: 初始标記 ( initial mark ),标記了從 GC Root 開始直接關聯可達的對象。 STW ( Stop the World )執⾏。 并發标記 ( concurrent marking ),和⽤戶線程并發執⾏,從 GC Root 開始對堆中對象進⾏ 可達性分析,遞歸掃描整個堆⾥的對象圖,找出要回收的對象、 最終标記 ( Remark ), STW ,标記再并發标記過程中産⽣的垃圾。 篩選回收 ( Live Data Counting And Evacuation ),制定回收計劃,選擇多個 Region 構成 回收集,把回收集中 Region 的存活對象複制到空的 Region 中,再清理掉整個舊 Region 的全 部空間。需要 STW 。

小白學java-JVM知識點總結

28. 有了 CMS ,為什麼還要引⼊ G1 ? 優點: CMS 最主要的優點在名字上已經展現出來 —— 并發收集、低停頓。 缺點: CMS 同樣有三個明顯的缺點。 Mark Sweep 算法會導緻記憶體碎⽚⽐較多 CMS 的并發能⼒⽐較依賴于 CPU 資源,并發回收時垃圾收集線程可能會搶占⽤戶線程的資 源,導緻⽤戶程式性能下降。 并發清除階段,⽤戶線程依然在運⾏,會産⽣所謂的理 “ 浮動垃圾 ” ( Floating Garbage ), 本次垃圾收集⽆法處理浮動垃圾,必須到下⼀次垃圾收集才能處理。如果浮動垃圾太多, 會觸發新的垃圾回收,導緻性能降低。 G1 主要解決了記憶體碎⽚過多的問題。

29. 你們線上⽤的什麼垃圾收集器?為什麼要⽤它?

采⽤ Parallel New + CMS 的組合,我們⽐較關注服務的響應速度,是以采⽤了 CMS 來降低 停頓時間。 或者⼀步到位: 我們線上采⽤了設計⽐較優秀的 G1 垃圾收集器,因為它不僅滿⾜我們低停頓的要求,⽽且解 決了 CMS 的浮動垃圾問題、記憶體碎⽚問題。

30. 垃圾收集器應該如何選擇? 垃圾收集器的選擇需要權衡的點還是⽐較多的 —— 例如運⾏應⽤的基礎設施如何?使⽤ JDK 的 發⾏商是什麼?等等 …… 這⾥簡單地列⼀下上⾯提到的⼀些收集器的适⽤場景: Serial :如果應⽤程式有⼀個很⼩的記憶體空間(⼤約 100 MB )亦或它在沒有停頓時間要求 的單線程處理器上運⾏。 Parallel :如果優先考慮應⽤程式的峰值性能,并且沒有時間要求要求,或者可以接受 1 秒 或更長的停頓時間。 CMS/G1 :如果響應時間⽐吞吐量優先級⾼,或者垃圾收集暫停必須保持在⼤約 1 秒以 内。 ZGC :如果響應時間是⾼優先級的,或者堆空間⽐較⼤。

31. 對象⼀定配置設定在堆中嗎?有沒有了解逃逸分析技術? 對象⼀定配置設定在堆中嗎? 不⼀定的。 随着 JIT 編譯期的發展與逃逸分析技術逐漸成熟,所有的對象都配置設定到堆上也漸漸變得不那麼 “ 絕對 ” 了。其實,在編譯期間, JIT 會對代碼做很多優化。其中有⼀部分優化的⽬的就是減少 記憶體堆配置設定壓⼒,其中⼀種重要的技術叫做逃逸分析。 什麼是逃逸分析? 逃逸分析 是指分析指針動态範圍的⽅法,它同編譯器優化原理的指針分析和外形分析相關 聯。當變量(或者對象)在⽅法中配置設定後,其指針有可能被傳回或者被全局引⽤,這樣就會 被其他⽅法或者線程所引⽤,這種現象稱作指針(或者引⽤)的逃逸 (Escape) 。 通俗點講,當⼀個對象被 new 出來之後,它可能被外部所調⽤,如果是作為參數傳遞到外部 了,就稱之為⽅法逃逸

逃逸分析的好處 棧上配置設定 如果确定⼀個對象不會逃逸到線程之外,那麼久可以考慮将這個對象在棧上配置設定,對象占⽤ 的記憶體随着棧幀出棧⽽銷毀,這樣⼀來,垃圾收集的壓⼒就降低很多。 同步消除 線程同步本⾝是⼀個相對耗時的過程,如果逃逸分析能夠确定⼀個變量不會逃逸出線程,⽆ 法被其他線程通路,那麼這個變量的讀寫肯定就不會有競争, 對這個變量實施的同步措施也 就可以安全地消除掉。 标量替換 如果⼀個資料是基本資料類型,不可拆分,它就被稱之為标量。把⼀個 Java 對象拆散,将其⽤ 到的成員變量恢複為原始類型來通路,這個過程就稱為标量替換。假如逃逸分析能夠證明⼀ 個對象不會被⽅法外部通路,并且這個對象可以被拆散,那麼可以不建立對象,直接⽤建立 若⼲個成員變量代替,可以讓對象的成員變量在棧上配置設定和讀寫。

32. 有哪些常⽤的指令⾏性能監控和故障處理⼯具? 作業系統⼯具 top :顯⽰系統整體資源使⽤情況 vmstat :監控記憶體和 CPU iostat :監控 IO 使⽤ netstat :監控⽹絡使⽤ JDK 性能監控⼯具 jps :虛拟機程序檢視 jstat :虛拟機運⾏時資訊檢視 jinfo :虛拟機配置檢視 jmap :記憶體映像(導出) jhat :堆轉儲快照分析 jstack : Java 堆棧跟蹤 jcmd :實作上⾯除了 jstat 外所有指令的功能

除此之外,還有⼀些第三⽅的⼯具: MAT Java 堆記憶體分析⼯具。 GChisto GC ⽇志分析⼯具。 GCViewer GC ⽇志分析⼯具。 JProfiler 商⽤的性能分析利器。 arthas 阿⾥開源診斷⼯具。 async-profiler Java 應⽤性能分析⼯具,開源、⽕焰圖、跨平台。

34.JVM 的常見參數配置知道哪些? ⼀些常見的參數配置: 堆配置: -Xms: 初始堆⼤⼩ -Xmx :最⼤堆⼤⼩ -XX:NewSize=n: 設定年輕代⼤⼩ -XX:NewRatio=n: 設定年輕代和年⽼代的⽐值。如:為 3 表⽰年輕代和年⽼代⽐值為 1 : 3 , 年輕代占整個年輕代年⽼代和的 1/4 -XX:SurvivorRatio=n: 年輕代中 Eden 區與兩個 Survivor 區的⽐值。注意 Survivor 區有兩個。 如 3 表⽰ Eden : 3 Survivor : 2 ,⼀個 Survivor 區占整個年輕代的 1/5 -XX:MaxPermSize=n: 設定持久代⼤⼩ 收集器設定: -XX:+UseSerialGC: 設定串⾏收集器 -XX:+UseParallelGC: 設定并⾏收集器 -XX:+UseParalledlOldGC: 設定并⾏年⽼代收集器 -XX:+UseConcMarkSweepGC: 設定并發收集器

并⾏收集器設定 -XX:ParallelGCThreads=n: 設定并⾏收集器收集時使⽤的 CPU 數。并⾏收集線程數 -XX:MaxGCPauseMillis=n: 設定并⾏收集最⼤的暫停時間(如果到這個時間了,垃圾回收 器依然沒有回收完,也會停⽌回收) -XX:GCTimeRatio=n: 設定垃圾回收時間占程式運⾏時間的百分⽐。公式為: 1/(1+n) -XX:+CMSIncrementalMode: 設定為增量模式。适⽤于單 CPU 情況 -XX:ParallelGCThreads=n: 設定并發收集器年輕代⼿機⽅式為并⾏收集時,使⽤的 CPU 數。 并⾏收集線程數 列印 GC 回收的過程⽇志資訊 -XX:+PrintGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -Xloggc:filename

小白學java-JVM知識點總結

實際上, JVM 調優是不得已⽽為之,有那功夫,好好把爛代碼重構⼀下不⽐瞎調 JVM 強。 但是,⾯試官⾮要問怎麼辦?可以從處理問題的⾓度來回答(對應圖中事後),這是⼀個中 規中矩的案例:電商公司的營運背景系統,偶發性的引發 OOM 異常,堆記憶體溢出。 1 、因為是偶發性的,是以第⼀次簡單的認為就是堆記憶體不⾜導緻,單⽅⾯的加⼤了堆記憶體從 4G 調整到 8G -Xms8g 。 2 、但是問題依然沒有解決,隻能從堆記憶體資訊下⼿,通過開啟了 - XX:+HeapDumpOnOutOfMemoryError 參數 獲得堆記憶體的 dump ⽂件。 3 、⽤ JProfiler 對 堆 dump ⽂件進⾏分析,通過 JProfiler 檢視到占⽤記憶體最⼤的對象是 String 對 象,本來想跟蹤着 String 對象找到其引⽤的地⽅,但 dump ⽂件太⼤,跟蹤進去的時候總是卡 死,⽽ String 對象占⽤⽐較多也⽐較正常,最開始也沒有認定就是這⾥的問題,于是就從線程 資訊⾥⾯找突破點。 4 、通過線程進⾏分析,先找到了⼏個正在運⾏的業務線程,然後逐⼀跟進業務線程看了下代 碼,有個⽅法引起了我的注意, 導出訂單資訊 。 5 、因為訂單資訊導出這個⽅法可能會有⼏萬的資料量,⾸先要從資料庫⾥⾯查詢出來訂單信 息,然後把訂單資訊⽣成 excel ,這個過程會産⽣⼤量的 String 對象。 6 、為了驗證⾃⼰的猜想,于是準備登入背景去測試下,結果在測試的過程中發現導出訂單的 按鈕前端居然沒有做點選後按鈕置灰互動事件,後端也沒有做防⽌重複送出,因為導出訂單 資料本來就⾮常慢,使⽤的⼈員可能發現點選後很久後頁⾯都沒反應,然後就⼀直點,結果 就⼤量的請求進⼊到背景,堆記憶體産⽣了⼤量的訂單對象和 EXCEL 對象,⽽且⽅法執⾏⾮常 慢,導緻這⼀段時間内這些對象都⽆法被回收,是以最終導緻記憶體溢出。 7 、知道了問題就容易解決了,最終沒有調整任何 JVM 參數,隻是做了兩個處理: 在前端的導出訂單按鈕上加上了置灰狀态,等後端響應之後按鈕才可以進⾏點選 後端代碼加分布式鎖,做防重處理 這樣雙管齊下,保證導出的請求不會⼀直打到服務端,問題解決!

小白學java-JVM知識點總結
小白學java-JVM知識點總結
小白學java-JVM知識點總結
小白學java-JVM知識點總結
小白學java-JVM知識點總結

41. 有沒有處理過記憶體溢出問題? 記憶體洩漏和記憶體溢出⼆者關系⾮常密切,記憶體溢出可能會有很多原因導緻,記憶體洩漏最可能 的罪魁禍⾸之⼀。 排查過程和排查記憶體洩漏過程類似。 虛拟機執⾏ 42. 能說⼀下類的⽣命周期嗎? ⼀個類從被加載到虛拟機記憶體中開始,到從記憶體中解除安裝,整個⽣命周期需要經過七個階段: 加載 ( Loading )、驗證( Verification )、準備( Preparation )、解析( Resolution )、初始化 ( Initialization )、使⽤( Using )和解除安裝( Unloading ),其中驗證、準備、解析三個部分統稱 為連接配接( Linking )。

小白學java-JVM知識點總結
小白學java-JVM知識點總結

1 )通過⼀個類的全限定名來擷取定義此類的⼆進制位元組流。 2 )将這個位元組流所代表的靜态存儲結構轉化為⽅法區的運⾏時資料結構。 3 )在記憶體中⽣成⼀個代表這個類的 java.lang.Class 對象,作為⽅法區這個類的各種資料的 通路⼊⼜。 加載階段結束後, Java 虛拟機外部的⼆進制位元組流就按照虛拟機所設定的格式存儲在⽅法區之 中了,⽅法區中的資料存儲格式完全由虛拟機實作⾃⾏定義,《 Java 虛拟機規範》未規定此區 域的具體資料結構。 類型資料妥善安置在⽅法區之後,會在 Java 堆記憶體中執行個體化⼀個 java.lang.Class 類的對象, 這 個對象将作為程式通路⽅法區中的類型資料的外部接⼜。

44. 類加載器有哪些? 主要有四種類加載器 : 啟動類加載器 (Bootstrap ClassLoader) ⽤來加載 java 核⼼類庫,⽆法被 java 程式直接引⽤。 擴充類加載器 (extensions class loader): 它⽤來加載 Java 的擴充庫。 Java 虛拟機的實作會提 供⼀個擴充庫⽬錄。該類加載器在此⽬錄⾥⾯查找并加載 Java 類。 系統類加載器 ( system class loader ):它根據 Java 應⽤的類路徑( CLASSPATH )來加載 Java 類。⼀般來說, Java 應⽤的類都是由它來完成加載的。可以通過 ClassLoader.getSystemClassLoader() 來擷取它。 ⽤戶⾃定義類加載器 (user class loader) ,⽤戶通過繼承 java.lang.ClassLoader 類的⽅式⾃⾏ 實作的類加載器。

小白學java-JVM知識點總結

雙親委派模型的⼯作過程:如果⼀個類加載器收到了類加載的請求,它⾸先不會⾃⼰去嘗試 加載這個類,⽽是把這個請求委派給⽗類加載器去完成,每⼀個層次的類加載器都是如此, 是以所有的加載請求最終都應該傳送到最頂層的啟動類加載器中,隻有當⽗加載器回報⾃⼰ ⽆法完成這個加載請求時,⼦加載器才會嘗試⾃⼰去完成加載。

答案是為了保證應⽤程式的穩定有序。 例如類 java.lang.Object ,它存放在 rt.jar 之中,通過雙親委派機制,保證最終都是委派給處于模 型最頂端的啟動類加載器進⾏加載,保證 Object 的⼀緻。反之,都由各個類加載器⾃⾏去加載 的話,如果⽤戶⾃⼰也編寫了⼀個名為 java.lang.Object 的類,并放在程式的 ClassPath 中,那系 統中就會出現多個不同的 Object 類。

47. 如何破壞雙親委派機制? 如果不想打破雙親委派模型,就重寫 ClassLoader 類中的 fifindClass() ⽅法即可,⽆法被⽗類加 載器加載的類最終會通過這個⽅法被加載。⽽如果想打破雙親委派模型則需要重寫 loadClass() ⽅法

小白學java-JVM知識點總結
小白學java-JVM知識點總結
小白學java-JVM知識點總結

Tomcat 實際上也是破壞了雙親委派模型的。 Tomact 是 web 容器,可能需要部署多個應⽤程式。不同的應⽤程式可能會依賴同⼀個第三⽅類 庫的不同版本,但是不同版本的類庫中某⼀個類的全路徑名可能是⼀樣的。如多個應⽤都要 依賴 hollis.jar ,但是 A 應⽤需要依賴 1.0.0 版本,但是 B 應⽤需要依賴 1.0.1 版本。這兩個版本中 都有⼀個類是 com.hollis.Test.class 。如果采⽤預設的雙親委派類加載機制,那麼⽆法加載多個 相同的類。 是以, Tomcat 破壞了 雙親委派原則 ,提供隔離的機制,為每個 web 容器單獨提供⼀個 WebAppClassLoader 加載器。每⼀個 WebAppClassLoader 負責加載本⾝的⽬錄下的 class ⽂件, 加載不到時再交 CommonClassLoader 加載,這和雙親委派剛好相反。