java

小白必看,JVM精華


一. JVM記憶體區域

Java虛擬機器在執行時,會把記憶體空間分為若干個區域,根據《Java虛擬機器規範( SE 7 版)》的規定,Java虛擬機器所管理的記憶體區域分為如下部分:方法區、堆記憶體、虛擬機器棧、本地方法棧、程式計數器。在這裡插入圖片描述
1、方法區
方法區主要用於儲存虛擬機器載入的類資訊、常量、靜態變數,以及編譯器編譯後的程式碼等資料。方法區是堆的一個“邏輯部分”(一片連續的堆空間),但為了與堆做區分,方法區還有個名字叫“非堆”,也有人用“永久代”(HotSpot對方法區的實現方法)來表示方法區。

2、堆記憶體
堆記憶體主要用於存放物件和陣列,它是JVM管理的記憶體中最大的一塊區域,堆記憶體和方法區都被所有執行緒共享,在虛擬機器啟動時建立。在垃圾收集的層面上來看,由於現在收集器基本上都採用分代收集演算法,因此堆還可以分為新生代(YoungGeneration)和老年代(OldGeneration),新生代還可以分為 Eden、From Survivor、To Survivor。

3、程式計數器
程式計數器是一塊非常小的記憶體空間,可以看做是當前執行緒執行位元組碼的行號指示器,每個執行緒都有一個獨立的程式計數器,因此程式計數器是執行緒私有的一塊空間,此外,程式計數器是Java虛擬機器規定的唯一不會發生記憶體溢位的區域。

4、虛擬機器棧
虛擬機器棧也是每個執行緒私有的一塊記憶體空間,它描述的是方法的記憶體模型,直接看下圖所示:
在這裡插入圖片描述

虛擬機器會為每個執行緒分配一個虛擬機器棧,每個虛擬機器棧中都有若干個棧幀,每個棧幀中儲存了局部變量表、運算元棧、動態連結、返回地址等。一個棧幀就對應 Java 程式碼中的一個方法,當執行緒執行到一個方法時,就代表這個方法對應的棧幀已經進入虛擬機器棧並且處於棧頂的位置,每一個 Java 方法從被呼叫到執行結束,就對應了一個棧幀從入棧到出棧的過程。

5、本地方法棧
本地方法棧與虛擬機器棧的區別是,虛擬機器棧執行的是 Java 方法,本地方法棧執行的是本地方法(Native Method),其他基本上一致,在 HotSpot 中直接把本地方法棧和虛擬機器棧合二為一,這裡暫時不做過多敘述。

6、元空間
上面說到,1.8 中,已經不存在永久代(方法區),替代它的一塊空間叫做 “ 元空間 ”,和永久代類似,都是 規範對方法區的實現,但是元空間並不在虛擬機器中,而是使用本地記憶體,元空間的大小僅受本地記憶體限制,但可以透過 -XX:MetaspaceSize 和 -XX:MaxMetaspaceSize 來指定元空間的大小。

二. JVM記憶體溢位

1、堆記憶體溢位
堆記憶體中主要存放物件、陣列等,只要不斷地建立這些物件,並且保證 GC Roots 到物件之間有可達路徑來避免垃圾收集回收機制清除這些物件,當這些物件所佔空間超過最大堆容量時,就會產生 OutOfMemoryError 的異常。
執行後會報異常,在堆疊資訊中可以看到,java.lang.OutOfMemoryError: Java heap space 的資訊,說明在堆記憶體空間產生記憶體溢位的異常。

新產生的物件最初分配在新生代,新生代滿後會進行一次 Minor GC,如果 Minor GC 後空間不足會把該物件和新生代滿足條件的物件放入老年代,老年代空間不足時會進行 Full GC,之後如果空間還不足以存放新物件則丟擲 OutOfMemoryError 異常。

常見原因:記憶體中載入的資料過多如一次從資料庫中取出過多資料;集合對物件引用過多且使用完後沒有清空;程式碼中存在死迴圈或迴圈產生過多重複物件;堆記憶體分配不合理;網路連線問題、資料庫問題等

2、虛擬機器棧/本地方法棧溢位
(1)StackOverflowError:當執行緒請求的棧的深度大於虛擬機器所允許的最大深度,則丟擲StackOverflowError,簡單理解就是虛擬機器棧中的棧幀數量過多(一個執行緒巢狀呼叫的方法數量過多)時,就會丟擲StackOverflowError異常。

(2)OutOfMemoryError:如果虛擬機器在擴充套件棧時無法申請到足夠的記憶體空間,則丟擲 OutOfMemoryError。
我們可以這樣理解,虛擬機器中可以供棧佔用的空間≈可用實體記憶體 – 最大堆記憶體 – 最大方法區記憶體,比如一臺機器記憶體為 4G,系統和其他應用佔用 2G,虛擬機器可用的實體記憶體為 2G,最大堆記憶體為 1G,最大方法區記憶體為 512M,那可供棧佔有的記憶體大約就是 512M,假如我們設定每個執行緒棧的大小為 1M,那虛擬機器中最多可以建立 512個執行緒,超過 512個執行緒再建立就沒有空間可以給棧了,就報 OutOfMemoryError 異常了。

總結:線上程較少的時候,某個執行緒請求深度過大,會報 StackOverflow 異常,解決這種問題可以適當加大棧的深度(增加棧空間大小),也就是把 -Xss 的值設定大一些,但一般情況下是程式碼問題的可能性較大;在虛擬機器產生執行緒時,無法為該執行緒申請棧空間了,會報 OutOfMemoryError 異常,解決這種問題可以適當減小棧的深度,也就是把 -Xss 的值設定小一些,每個執行緒佔用的空間小了,總空間一定就能容納更多的執行緒,但是作業系統對一個程序的執行緒數有限制,經驗值在 3000~5000 左右。

在 jdk1.5 之前 -Xss 預設是 256k,jdk1.5 之後預設是 1M,這個選項對系統硬性還是蠻大的,設定時要根據實際情況,謹慎操作。

3、方法區溢位
前面說到,方法區主要用於儲存虛擬機器載入的類資訊、常量、靜態變數,以及編譯器編譯後的程式碼等資料,所以方法區溢位的原因就是沒有足夠的記憶體來存放這些資料。

由於在 jdk1.6 之前字串常量池是存在於方法區中的,所以基於 jdk1.6 之前的虛擬機器,可以透過不斷產生不一致的字串(同時要保證和 GC Roots 之間保證有可達路徑)來模擬方法區的 OutOfMemoryError 異常;但方法區還儲存載入的類資訊,所以基於 jdk1.7 的虛擬機器,可以透過動態不斷建立大量的類來模擬方法區溢位。

4、本機直接記憶體溢位
本機直接記憶體(DirectMemory)並不是虛擬機器執行時資料區的一部分,也不是 Java 虛擬機器規範中定義的記憶體區域,但 Java 中用到 NIO 相關操作時(比如 ByteBuffer 的 allocteDirect 方法申請的是本機直接記憶體),也可能會出現記憶體溢位的異常。

三. JVM垃圾回收

垃圾回收,就是透過垃圾收集器把記憶體中沒用的物件清理掉。垃圾回收涉及到的內容有:

1、判斷物件是否已死;
2、選擇垃圾收集演算法;
3、選擇垃圾收集的時間;
4、選擇適當的垃圾收集器清理垃圾(已死的物件)。

1、判斷物件是否已死
判斷物件是否已死就是找出哪些物件是已經死掉的,以後不會再用到的,就像地上有廢紙、飲料瓶和百元大鈔,掃地前要先判斷出地上廢紙和飲料瓶是垃圾,百元大鈔不是垃圾。判斷物件是否已死有引用計數演算法和可達性分析演算法。

(1)引用計數演算法

給每一個物件新增一個引用計數器,每當有一個地方引用它時,計數器值加 1;每當有一個地方不再引用它時,計數器值減 1,這樣只要計數器的值不為 0,就說明還有地方引用它,它就不是無用的物件。如下圖,物件 2 有 1 個引用,它的引用計數器值為 1,物件 1有兩個地方引用,它的引用計數器值為 2 。
在這裡插入圖片描述

這種方法看起來非常簡單,但目前許多主流的虛擬機器都沒有選用這種演算法來管理記憶體,原因就是當某些物件之間互相引用時,無法判斷出這些物件是否已死,如下圖,物件 1 和物件 2 都沒有被堆外的變數引用,而是被對方互相引用,這時他們雖然沒有用處了,但是引用計數器的值仍然是 1,無法判斷他們是死物件,垃圾回收器也就無法回收。

在這裡插入圖片描述
(2)方法區回收

上面說的都是對堆記憶體中物件的判斷,方法區中主要回收的是廢棄的常量和無用的類。

判斷常量是否廢棄可以判斷是否有地方引用這個常量,如果沒有引用則為廢棄的常量。

判斷類是否廢棄需要同時滿足如下條件:

該類所有的例項已經被回收(堆中不存在任何該類的例項)。

載入該類的 ClassLoader 已經被回收。

該類對應的 java.lang.Class 物件在任何地方沒有被引用(無法透過反射訪問該類的方法)。

2、常用垃圾回收演算法
常用的垃圾回收演算法有三種:標記-清除演算法、複製演算法、標記-整理演算法。

(1)標記-清除演算法:分為標記和清除兩個階段,首先標記出所有需要回收的物件,標記完成後統一回收所有被標記的物件,如下圖。

缺點:標記和清除兩個過程效率都不高;標記清除之後會產生大量不連續的記憶體碎片。

在這裡插入圖片描述
在這裡插入圖片描述

(2)複製演算法:把記憶體分為大小相等的兩塊,每次儲存只用其中一塊,當這一塊用完了,就把存活的物件全部複製到另一塊上,同時把使用過的這塊記憶體空間全部清理掉,往復迴圈,如下圖。

缺點:實際可使用的記憶體空間縮小為原來的一半,比較適合。

在這裡插入圖片描述
在這裡插入圖片描述
(3)標記-整理演算法:先對可用的物件進行標記,然後所有被標記的物件向一段移動,最後清除可用物件邊界以外的記憶體,如下圖。

在這裡插入圖片描述
在這裡插入圖片描述

3、常見垃圾收集器
現在常見的垃圾收集器有如下幾種:

新生代收集器:Serial、ParNew、Parallel Scavenge。

老年代收集器:Serial Old、CMS、Parallel Old。

堆記憶體垃圾收集器:G1。

每種垃圾收集器之間有連線,表示他們可以搭配使用。

在這裡插入圖片描述

本文章已修改原文用詞符合繁體字使用者習慣使其容易閱讀

版權宣告:此處為CSDN博主「故揚」的原創文章,依據CC 4.0 BY-SA版權協議,轉載請附上原文出處連結及本宣告。

原文連結:https://blog.csdn.net/GuYang_skr/article/details/107455877