JVM再理解 + 調優。

在這邊看這篇之前,需要去看:
類的加載
類加載器
虛擬機內存結構
以上的都要了解。

最近看了一個視頻。講的挺好的。決定跟着它的節奏再來一遍。
詳細細節,參考上面。另外以下的圖都是來自百度和視頻截圖。我們只說結論。最後我詳細說說如何調優。
爲什麼要寫這篇。硬背總是不得要領。而且時常和別人聊起時混亂了。這裏提出“想說話,先畫圖”,腦子裏也好、動手畫也好。所以以下的圖都要記住並能畫出。
類加載的圖:(這個就是上面鏈接1中的過程總結)
在這裏插入圖片描述
分析虛擬機內存結構就必須瞭解類的加載過程。上圖就是。
1、類加載、連接(驗證、準備、解析)、初始化。 這裏面涉及很多細節,詳細看鏈接1。
2、加載成功後會在方法區生成一個java.lang.class對象,供外部訪問創建使用。

虛擬機棧內存結構:
在這裏插入圖片描述
內存結構分析:(這個圖一定要刻在腦子裏,詳細細節看上面得鏈接)
1、方法區、堆是多線程共享的,虛擬機棧、本地方法棧、程序計數器是每個線程獨有一份的。
垃圾回收也是針對方法區、堆。其他的沒有這個說法的。

這句話如下圖:
在這裏插入圖片描述
引擎加入運行數據區:
在這裏插入圖片描述
阿里結構(java8)圖:
在這裏插入圖片描述
重要點:虛擬機棧。
在這裏插入圖片描述
虛擬機棧:每個線程在創建時都會創建一個虛擬機棧,其內部保存一個個的棧幀(Stack Frame),對應着一次次Java方法調用。
生命週期和線程一致。
作用:
主管Java程序的運行,它保方法的局部變量、部分結構,並參與方法的調用和返回。
只有存在於壓棧、出棧,沒有垃圾回收的說法。
每個線程都有自己的棧,棧中的數據都是以棧幀的格式存在的。棧幀是個內存區塊,是個數據集。
對應的就是debug視圖:
在這裏插入圖片描述
每個棧幀:
局部變量、操作數棧、動態鏈接(指向運行時常量池的方法引用)、方法返回地址。
在這裏插入圖片描述
局部變量表,包括基本類型,對象引用以及returnAddress。它的單位是變量槽slot。
虛擬機棧都是線程私有的,不存在數據的安全問題。
用完棧幀釋放,以上都會釋放。
變量槽slot:
局部變量表,最基本的單位Solt。32爲佔一個Solt,64佔兩個Slot,所以long、double佔兩個變量槽。
回收:直接引用和簡潔引用的對象都不會被回收。
操作數棧
在方法執行的過程中,根據字節碼指令,往棧中寫入數據或提取數據即出棧和入棧。
這裏就涉及到直接罵指令。請看上面第二篇。
棧頂緩存技術
爲了避免上面的大執行效率量入棧出棧,這裏採用了“棧頂緩存技術”,即將棧頂元素全部緩存到爲u裏的CPU的寄存器
中,一次降低堆內存的讀寫次數,提升執行引擎的執行效率。

動態鏈接
每個棧幀內部都包含一個執行運行時常量池中該幀所屬的方法的引用,包含這個引用的母的
就是爲了支持當前方法的代碼能夠實現動態鏈接。
在這裏插入圖片描述
非虛方法與非虛方法的概念,參照上面鏈接1。

方法返回地址
即返回下一條指令的地址。用於恢復上層方法。

常見問題:
1、棧溢出的情況:遞歸不跳出、設值-Xss0都可以觸發棧溢出。
2、調整棧的大小也不能保證棧不會溢出。
3、棧內存的分配?越大越好嗎?不是,對它自己是好的,
但是對整體而言,可分配的線程總數就減少了。
4、方法中的局部變量,是線程安全的嗎?看情況,如果只有一個線程使用是可以;
如果多線程操作此數據,屬於共享數據。則線程不安全。

本地方法接口
即是native method就是一個java調用非java代碼的接口。實現外部環境交互。本地方法棧是管理本地方法的。

(最重要): 綜述
1、如何設置堆內存的大小、OOM、年輕代、老年代、Minor GC、FULL GC、Major GC;
2、堆空間的分代思想、內存分配策略、爲對象分配內存TLAB、小結堆空間的參數設置。

一個JVM實例只存在一個堆內存,堆也是java內存管理的核心區域。
Java堆區在JVM啓動的時候被創建,其空間大小也就確定了。
《Java虛擬機規範》規定,堆可以處於物理上不連續的內存空間中,但在邏輯上它應該被視爲連續的。
所有的線程共享Java堆,在這林還可以劃分線程的私有緩衝區TLAB.

《java虛擬機規範》中對java堆的描述:所有的對象實例以及數組都應當分配在堆上。
數組和對象可能永遠不會存儲在棧上,因爲棧幀保存引用,這個引用指向對象或數組在堆中的位置。
在堆中的對象不會馬上被移除,僅僅在垃圾收集的時候纔會被移除。
堆是GC執行垃圾回收的重點區域。
Java8堆空間細分:
新生區+養老區+元空間。
新生區(young Generation space):young/new
養老區(tenure Generation space):old/tenure
元空間(Meta space):Meta

默認情況下,初始內存大小(-Xms):電腦物理內存大小/64
最大內存(-Xmx):電腦物理內存大小/4
設置格式舉例:-Xms1024k,-Xms1m
一般設成同樣大小,從而避免GC每次區的調整大小。

OOM: old Gen滿了報outOfMemory
在這裏插入圖片描述
一般不會考慮修改:
配置新生代與老年代在對結構上的佔比:
1、默認-XX:NewRatio=2,表示新生代:老年代= 1:2
2、可以修改-XX::NewRatio=4,表示新生代:老年代=1:4

新生代中:Eden:Survivor0:Survivor1 = 8:1:1
初始:-XX:SurvivorRatio=8
幾乎所有的java對象都是在Eden區被new出來的。
絕大部分的java對象的銷燬都在新生代進行了。
在這裏插入圖片描述在這裏插入圖片描述

爲新對象分配內存的過程:(正常情況下)
1、new的對象先放Eden區。大對象直接分配到老年代
2、Eden區滿時候,會垃圾回收,稱爲Minor GC(YGC)。
3、將垃圾回收,將還在用的放入s0區。
4、重複1、2,將垃圾回收,會將Eden區的放入s1區,將s0區垃圾回收,還在用的放入s1。(s0和s1是相對的)
同時把標誌+1。
5、重複上面1、2、3、4。當標誌達到閾值15(默認15)的時候,加1放入old區
注:當Eden區滿時候,會將Eden區和s0或s1中回收。
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
但是也有可能直接進入old的:
對象分配的特殊情況:
1、超大對象:直接放入old區。如果old也放不下,執行major GC執行後還放不下,報OOM。
2、如果s區中相同年齡的所喲對象大小的總和大於s空間的一般,年齡大於或等於該年齡的對象直接進入老年代,無需等到要求的最大年齡
在這裏插入圖片描述
Minor GC: 新生代垃圾回收。(大部分都在這裏被回收)
Major GC:老年代的垃圾回收。
Full GC: 收集整個java堆和元空間垃圾收集。上面兩種是部分收集。
一般Major GC和Full GC混合使用。
Major GC的速度一般Minor GC慢10倍。STW時間更長。如果Major GC回收還是內存不足,就報OOM.
Full GC開發中或調優中儘量避免。

爲對象分配TLAB:
堆是線程共享區域,任何線程都剋在堆區中共享數據。
由於對象實例的創建在JVM中非常頻繁,併發情況下從堆區中劃分內存是線程不安全的。
爲了避免多個線程操作同一個地址,需要使用加鎖機制,進而影響分配速度。
什麼是TLAB:
1、從內存模型而不是垃圾回收的角度,對Eden區繼續進行劃分,JVM爲每個線程分配了一個私有緩存
緩存區域,它包含在Eden內。
2、多線程同時分配內存時,使用TLAB可以避免一系列的非線程安全問題。同時還能夠提升內存分配的
吞吐量。這是一種快速分配策略。
但不是所有線程都有TLAB,它只佔有Eden區的1%。如果沒有JVM嘗試加鎖解決安全問題
在這裏插入圖片描述
在這裏插入圖片描述

根絕逃逸分析結果,發現一個對象沒有發生逃逸分析,可以分配放入棧中。
逃逸分析開啓,運行速度更快。就目前來說逃逸分析還是不是很穩定。

從線程共享的角度來看:
在這裏插入圖片描述
元空間、java棧、堆的交互:
在這裏插入圖片描述
方法區:多線程共享。
《Java虛擬機規範》中明確說明:“儘管所有的方法區在邏輯上屬於堆的一部分,但一些簡單的實現可能不會選擇進行垃圾收集或者進行壓縮。”
方法區用於存儲已被虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯後的代碼緩存等。
方法區看作一塊獨立於Java堆的內存空間。
方法區的大小,跟堆空間一樣可選擇固定或可擴展。
方法區的大小決定了系統可以保存多少個類,如果系統定義了太多的類,導致方法區溢出OOM。
JDK8開始使用元空間取代了永久代,爲什麼取代?因爲永久代容易OOM。
元空間 ≈ 方法區。
元空間不在虛擬機內存中,而是使用本地內存。(最大區別)

設置方法區(元空間)的大小:
-XX:MetaspaceSize=100m 默認值依賴於平臺,初始值
-XX:MaxMetaspaceSize=100m 默認值依賴於平臺,最大值。爲了避免Full GC,最大值給大點

溢出一樣報錯OutOfMemery: Metaspace
在這裏插入圖片描述
如何解決OOM:
1、要解決OOM異常或者heap space的異常,一般的手段時首先通過內存映像分析工具對dump出來的堆
轉儲快照進行分析。重點時確認內存中的對象是否必要,也就是要先分析清楚到底是出現了內存泄漏
(Memory Leak)還是內存溢出(Memory Overflow)。
2、如果是內存泄漏,進一步通過工具查看泄漏對象到GC Roots的引用鏈。於是就能找到泄漏對象是通
過怎樣的的路勁與GC Roots相關聯並導致垃圾收集器無法自動回收他們的。掌握了泄漏對象的類型信息,
以及GC Roots引用鏈就可以準確找出泄漏代碼的位置。
3、如果不存在內存泄漏,換句話說就是內存中的對象確實都還必須存活着,那就應當檢查虛擬機的堆
參數(-Xmx、-Xms),與機器物理內存對比看是否還可以調大,從代碼上檢查是否存在某些對象生命週期
過長、持有狀態時間過長的情況,嘗試減少程序運行期的內存消耗。

方法區的內存結構:
在這裏插入圖片描述
方法區內包含運行時常量池。字節碼文件內部包含了常量池。
運行時常量池VS常量池。
運行時常量池是方法去中的一部分;
常量池是class文件的一部分,用於存放編譯期生成的各種字面量和符號引用。
但是靜態引用對象始終存在堆空間中。
在這裏插入圖片描述
元空間的回收,不同的JVM不同分析。有的有,是將不用的常量回收。
創建對象的方式:
在這裏插入圖片描述
字節碼角度看創建對象:
在這裏插入圖片描述
對象內存佈局:
在這裏插入圖片描述
實際舉例分析:
在這裏插入圖片描述

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