閱讀 深入理解JVM虛擬機器筆記一
今日開始拜讀大作 深入理解JVM虛擬機器 在此做一些筆記記錄:
1.關於java執行時資料區域,其實遠不止堆和棧,在這裡粗淺的有個認知:
1)程式計數器:一塊比較小的記憶體區域。位元組碼直譯器需要通過計數器去執行下一條指令。考慮一下多執行緒處理的問題,為了能夠讓執行緒切換間還能找到原先執行的位置,有必要為每一個執行緒開闢一個記憶體區域。我們稱這樣的記憶體區域是執行緒私有的。[額外注意:當執行一個java程式時,計數器記錄位元組碼執行位置,當執行本地方法,記錄空(undifined)。這個記憶體區域沒有OutOfMemoryError。]
2)java虛擬機器棧:也是執行緒私有的記憶體區域。一個方法的執行會在此記憶體建立一個棧幀(基本資料結構,後會詳述),一個方法的執行到結束,對應了棧幀的入棧和出棧。其中區域性變量表是我們通常所說的堆和棧中的棧了,記錄了基本型別、引用型別(可以是指標,也可以是控制代碼)。這個記憶體區域會丟擲StackOverflowError和OutOfMemoryError。
3)本地方法棧:與虛擬機器棧功能類似,只不過這塊記憶體是為了native方法服務的。與虛擬機器棧一樣丟擲兩種異常。
4)java堆:執行緒共享的記憶體塊,最大的一塊記憶體區域,用於管理建立的物件,也是GC垃圾處理器主要管轄範圍。堆中無法繼續為新例項分配空間,也無法擴充套件記憶體,會丟擲OutOfMemoryError。
5)方法區:可以認為是堆的一個邏輯部分,用來存放類資訊、常量、靜態變數、即時編譯器編譯後的程式碼。由於GC管轄的劃分方式,堆可以分為新生代和老年代,從而有人認為方法區是永久代(並不合理,也不靠譜)。會丟擲OutOfMemoryError。[注:此處有執行時常量池。類檔案中包含了常量資訊,我們稱為類常量池,執行時他們會被加入執行時常量池,同時執行時也能通過諸如String的intern()加入常量池。]
6)之外:直接記憶體:也是需要留意的,實體記憶體決定了上限。
2.淺談一個物件的建立過程:
一句簡單的new: 虛擬機器在堆上為新例項開闢空間(執行緒間的衝突問題,一方面可以CAS保證原子性,也可以為執行緒單獨先開闢一個小的執行緒堆,完成了建立再同步)--->除了物件頭外所有值初始化為零值--->物件的類資訊、雜湊碼值、GC年代等放入物件頭---><init>你所寫的物件初始化邏輯。
物件:物件頭+資料資訊+對其填充(沒啥用)。
3.堆溢位的解決方式:
在java啟動項中加入引數-XX:+HeapDumpOnOutOfMemoryError,可以獲得堆記憶體快照,運用eclipse memory analyzer開啟分析。如果是記憶體洩漏,可以根據GC root引用鏈找到洩露源頭。如果不存在洩露,那麼就需要考慮擴大記憶體,或者儘可能的減少一些生命週期過長,過於龐大的例項物件。
4.棧溢位(java棧和本地方法棧)
在單執行緒程式中,無論如何都會丟擲StackOverflowError,這樣很好理解,無論棧幀過大還是虛擬棧記憶體太小,都可以認為是棧記憶體不足/深度不足。在多執行緒程式中,為每個執行緒棧分配過多的記憶體將會丟擲OutOfMemoryError,一般來說遇到這種情況,只能通過考慮減少堆記憶體、棧記憶體來更多的獲得執行緒(作業系統所給的記憶體是有限的,將會被堆疊、程序所共享)。
5.關於常量池溢位:
首先記錄一個很有趣的問題:
在JDK1.6下輸出的是兩個false,而到了JDK1.7下輸出變成了true,false。在1.6時,intern方法會將首次遇到的字串複製到常量池中,並返回引用。StringBuilder例項與常量池例項一定不是同一個東西,必然是false。而在1.7後,intern將首次遇到的字串新增到常量池,但不復制,因此第一條輸出true。但是考慮java這個字串已經存在與常量池中,所以第二個必然返回false。
方法區溢位是需要注意的:往往回收類十分的苛刻,對於動態產生類,或者CGLib這類加強類或者大量jsp檔案...的地方,一定要格外小心。
6.直接記憶體洩露
如果發現OOM下HeapDump檔案很小,基本可以考慮是直接記憶體洩露了。