更新時間:2022-12-27 12:32:58 來源:動力節點 瀏覽1897次
Stop-the-World:
JVM由于要執行GC而停止了應用程序的執行稱之為Stop-the-World,該情形會在任何一種GC算法中發生。當Stop-the-world發生時,除了GC所需的線程以外,所有線程都處于等待狀態直到GC任務完成。事實上,GC優化很多時候就是指減少Stop-the-world發生的時間,從而使系統具有高吞吐 、低停頓 的特點。
(1)程序計數器
記錄當前線程所執行的字節碼行號,用于獲取下一條執行的字節碼。
當多線程運行時,每個線程切換后需要知道上一次所運行的狀態、位置。
由此也可以看出程序計數器是每個線程私有的。
(2)虛擬機棧
虛擬機棧由一個一個的棧幀組成,棧幀是在每一個方法調用時產生的。
每一個棧幀由局部變量區、操作數棧等組成。每創建一個棧幀壓棧,當一個方法執行完畢之后則出棧。
如果出現方法遞歸調用出現死循環的話就會造成棧幀過多,最終會拋出 StackOverflowError。
若線程執行過程中棧幀大小超出虛擬機棧限制,則會拋出 StackOverflowError。
若虛擬機棧允許動態擴展,但在嘗試擴展時內存不足,或者在為一個新線程初始化新的虛擬機棧時申請不到足夠的內存,則會拋出 OutOfMemoryError。
這塊內存區域也是線程私有的。
(3)Java堆
Java 堆是整個虛擬機所管理的最大內存區域,所有的對象創建都是在這個區域進行內存分配。
可利用參數 -Xms -Xmx 進行堆內存控制。
這塊區域也是垃圾回收器重點管理的區域,由于大多數垃圾回收器都采用分代回收算法,所有堆內存也分為 新生代、老年代,可以方便垃圾的準確回收。
這塊內存屬于線程共享區域。
(4)方法區
方法區主要用于存放已經被虛擬機加載的類信息,如常量,靜態變量。 這塊區域也被稱為永久代。
可利用參數 -XX:PermSize -XX:MaxPermSize 控制初始化方法區和最大方法區大小。
(5)元數據區
在 JDK1.8 中已經移除了方法區(永久代),并使用了一個元數據區域進行代替(Metaspace)。
默認情況下元數據區域會根據使用情況動態調整,避免了在 1.7 中由于加載類過多從而出現 java.lang.OutOfMemoryError: PermGen。
但也不能無線擴展,因此可以使用 -XX:MaxMetaspaceSize來控制最大內存。
(6)運行時常量池
運行時常量池是方法區的一部分,其中存放了一些符號引用。當 new 一個對象時,會檢查這個區域是否有這個符號的引用。
(7)直接內存
直接內存又稱為 Direct Memory(堆外內存),它并不是由 JVM 虛擬機所管理的一塊內存區域。
有使用過 Netty 的朋友應該對這塊并內存不陌生,在 Netty 中所有的 IO(nio) 操作都會通過 Native 函數直接分配堆外內存。
它是通過在堆內存中的 DirectByteBuffer 對象操作的堆外內存,避免了堆內存和堆外內存來回復制交換復制,這樣的高效操作也稱為零拷貝。
既然是內存,那也得是可以被回收的。但由于堆外內存不直接受 JVM 管理,所以常規 GC 操作并不能回收堆外內存。它是借助于老年代產生的 fullGC 順便進行回收。同時也可以顯式調用 System.gc() 方法進行回收(前提是沒有使用 -XX:+DisableExplicitGC 參數來禁止該方法)。
值得注意的是:由于堆外內存也是內存,是由操作系統管理。如果應用有使用堆外內存則需要平衡虛擬機的堆內存和堆外內存的使用占比。避免出現堆外內存溢出。
(8)常用參數
通過上圖可以直觀的查看各個區域的參數設置。
常見的如下:
-Xms64m 最小堆內存 64m.
-Xmx128m 最大堆內存 128m.
-XX:NewSize=30m 新生代初始化大小為30m.
-XX:MaxNewSize=40m 新生代最大大小為40m.
-Xss=256k 線程棧大小。
-XX:+PrintHeapAtGC 當發生 GC 時打印內存布局。
-XX:+HeapDumpOnOutOfMemoryError 發送內存溢出時 dump 內存。
新生代和老年代的默認比例為 1:2,也就是說新生代占用 1/3的堆內存,而老年代占用 2/3 的堆內存。
可以通過參數 -XX:NewRatio=2 來設置老年代/新生代的比例。
下圖便是 Java 對象的創建過程,我建議最好是能默寫出來,并且要掌握每一步在做什么。
Step1:類加載檢查
虛擬機遇到一條 new 指令時,首先將去檢查這個指令的參數是否能在常量池中定位到這個類的符號引用,并且檢查這個符號引用代表的類是否已被加載過、解析和初始化過。如果沒有,那必須先執行相應的類加載過程。
Step2:分配內存
在類加載檢查通過后,接下來虛擬機將為新生對象分配內存。對象所需的內存大小在類加載完成后便可確定,為對象分配空間的任務等同于把一塊確定大小的內存從 Java 堆中劃分出來。分配方式有 “指針碰撞” 和 “空閑列表” 兩種,選擇那種分配方式由 Java 堆是否規整決定,而 Java 堆是否規整又由所采用的垃圾收集器是否帶有壓縮整理功能決定。
內存分配的兩種方式:(補充內容,需要掌握)
選擇以上兩種方式中的哪一種,取決于 Java 堆內存是否規整。而 Java 堆內存是否規整,取決于 GC 收集器的算法是"標記-清除",還是"標記-整理"(也稱作"標記-壓縮"),值得注意的是,復制算法內存也是規整的
內存分配并發問題(補充內容,需要掌握)
在創建對象的時候有一個很重要的問題,就是線程安全,因為在實際開發過程中,創建對象是很頻繁的事情,作為虛擬機來說,必須要保證線程是安全的,通常來講,虛擬機采用兩種方式來保證線程安全:
CAS+失敗重試: CAS 是樂觀鎖的一種實現方式。所謂樂觀鎖就是,每次不加鎖而是假設沒有沖突而去完成某項操作,如果因為沖突失敗就重試,直到成功為止。虛擬機采用 CAS 配上失敗重試的方式保證更新操作的原子性。
TLAB: 為每一個線程預先在 Eden 區分配一塊兒內存,JVM 在給線程中的對象分配內存時,首先在 TLAB 分配,當對象大于 TLAB 中的剩余內存或 TLAB 的內存已用盡時,再采用上述的 CAS 進行內存分配
Step3:初始化零值
內存分配完成后,虛擬機需要將分配到的內存空間都初始化為零值(不包括對象頭),這一步操作保證了對象的實例字段在 Java 代碼中可以不賦初始值就直接使用,程序能訪問到這些字段的數據類型所對應的零值。
Step4:設置對象頭
初始化零值完成之后,虛擬機要對對象進行必要的設置,例如這個對象是那個類的實例、如何才能找到類的元數據信息、對象的哈希碼、對象的 GC 分代年齡等信息。 這些信息存放在對象頭中。 另外,根據虛擬機當前運行狀態的不同,如是否啟用偏向鎖等,對象頭會有不同的設置方式。
以上就是關于“Java數據同步解決方案”的介紹,大家如果想了解更多相關知識,可以關注一下本站的Java在線學習,里面的課程內容細致全面,很適合沒有基礎的小伙伴學習,希望對大家能夠有所幫助。
0基礎 0學費 15天面授
有基礎 直達就業
業余時間 高薪轉行
工作1~3年,加薪神器
工作3~5年,晉升架構
提交申請后,顧問老師會電話與您溝通安排學習