標籤:
深入理解Java虛擬機器:JVM進階特性與最佳實務 閱讀筆記(記憶體原理、異常處理):
1. Jvm運行時,記憶體劃分:
2. 程式計數器:
Jvm將這個計數看作當前線程執行某條位元組碼的行數,會根據計數器的值來選取需要執行的動作陳述式。這個屬於線程私人,不可共用,如果共用會導致計數混亂,無法準確的執行當前線程需要執行的語句。
該地區不會出現任何OutOfMemoryError的情況。
3. 虛擬機器棧
經常說到的棧記憶體就是指虛擬機器棧。Java中每一個方法從調用直至執行完成的過程,就對應著一個棧幀在虛擬機器棧中入棧到出棧的過程。
如果線程請求的棧深度大於虛擬機器所允許的深度,將拋出StackOverflowError異常;如果虛擬機器棧可以動態擴充(當前大部分的Java虛擬機器都可動態擴充,只不過Java虛擬機器規範中也允許固定長度的虛擬機器棧),如果擴充時無法申請到足夠的記憶體,就會拋出OutOfMemoryError異常。
4. 本地方法棧
虛擬機器棧用來執行java方法,而本地方法棧用來執行本地方法。
拋出異常的情況和虛擬機器棧一樣。
5. 堆
是jvm中記憶體最大、線程共用的一塊地區。唯一的目的是儲存物件執行個體。這裡也是垃圾收集器主要收集的地區。由於現代垃圾收集器都採用的是分代收集演算法,所以java堆也分為新生代和老年代。
可以通過參數-Xmx(jvm最大可用記憶體)和-Xms(jvm初始記憶體)來調整堆記憶體,如果擴大至無法繼續擴充時,會出現OutOfMemoryError的錯誤。
6. 方法區
Jvm中記憶體共用的一片地區,用來存儲類資訊、常量、靜態變數、class檔案。垃圾收集器也會對這部分地區進行回收,比如常量池的清理和類型的卸載,但是效果不理想。
方法區記憶體不夠用的時候,也會拋出OutOfMemoryError錯誤。
7. 對象建立
虛擬機器遇到一條new指令時,首先將去檢查這個指令的參數是否能在常量池中定位到一個類的符號引用,並且檢查這個符號引用代表的類是否已被載入、解析和初始化過。如果沒有,那必須先執行相應的類載入過程,載入檢查之後會在堆中劃分出一定的記憶體。
在完成new指令之後,緊接著會調用<init>方法將對象初始化,這時一個完整的對象才算建立了出來。
8. 對象的訪問
在sun的jdk中採用的是指標訪問方式,在reference中直接儲存了對象的地址。
9. OutOfMemoryError錯誤
Java堆記憶體的OutOfMemoryError異常是實際應用中常見的記憶體溢出異常情況。當出現Java堆記憶體溢出時,異常堆棧資訊“java.lang.OutOfMemoryError”會跟著進一步提示“Java heap space”。
當產生的對象過多時,會出現這個錯誤資訊。解決辦法:調整虛擬機器堆參數(-Xmx和-Xms)。
Java方法區會儲存類資訊、常量、靜態變數等。如果產生了大量類,比如某個ssh項目因為載入了架構和大量jar包,這樣class檔案都會載入記憶體的方法區,這樣如果出現記憶體無法繼續擴充的情況,也會出現java.lang.OutOfMemoryError,然後緊跟著PermGenspace資訊。通過-XX:PermSize和-XX:MaxPermSize可以限制方法區大小。
10. StackOverflow錯誤
Java中棧記憶體溢出,通常是由於棧深度超過限制深度,導致出現該問題。很常見的情況是,使用遞迴的時候,不小心忘了指定遞迴結束的時刻,導致遞迴深度超過限制深度,出現棧記憶體溢出。
11. 判斷對象是否存活的演算法
主流jvm虛擬機器不會採用引用計數法,因為有種情況它無法判斷是否存活。比如互成環路的幾個對象,沒有其他對象引用他們了,這樣按道理來說是死對象,可以被回收,但是按照引用計數法的判定,他們計數還不為0,也就是還不能被回收。
主流商用jvm目前採用的是可達性演算法。利用有向圖的原理,從根節點(原點)GC ROOT可達的所有其他節點表示目前仍然被引用,無法被回收。而從它無法達到的對象節點(距離為∞)則表示該對象可以被回收。GC ROOT表示虛擬機器棧中引用指向的對象或者方法區中的常量對象和靜態對象,或者jni(java本地方法)中引用的對象。
12. 引用
引用代表的是資料A中儲存了一個地址,這個地址指向另一快記憶體的起始地址,這個A就是一個引用。
強引用:無論何時都不會被回收。普遍存在,如Object o=new Object();
弱引用:當垃圾收集器工作時,就會回收弱引用指向的對象。
13. 回收流程
回收時系統會執行System.gc(),找到不可達對象,標記該對象為待回收對象,並查看該對象是否覆蓋finalize()方法,如果覆蓋了並且該方法之前沒有被調用過,然後將他放到一個隊列中,排著長隊等著finalize()回收。即使進入finalize()方法,也不能保證該方法可以回收對象,因為對象可以在該方法裡面關聯其他引用,完成自救。綜合而言,finalize()每次執行消耗成本高,但不能保證回收對象,所以效果並不好,並不推薦手動調用finalize()方法。
14. 垃圾收集演算法
主流jvm都採用分代收集演算法(將java堆分而治之):
新生代:大量對象死亡,少量存活,採用複製演算法
java堆分成
老年代:少量死亡,大量存活,採用標記整理法或標記清除法
複製演算法:將記憶體劃分為兩片地區A和B,每次都只佔用A部分。這樣A放了所有對象,B空著。每次收集的時候標記A中的存活部分,複製到B中,然後統一清除A中的內容即可。缺點:記憶體佔用高!適用:新生代。
標記清除演算法:標記所有垃圾對象,完成標記後統一清除這些垃圾對象。缺點:效率低,產生片段多。適用:老年代。
標記整理演算法:標記所有垃圾對象,將存活對象移動到一邊,清掉另一邊的垃圾對象。適用:老年代。
深入java虛擬機器閱讀筆記(jvm記憶體原理、異常處理部分)