1. 程式人生 > >【JVM】記憶體區域分配機制和記憶體溢位異常

【JVM】記憶體區域分配機制和記憶體溢位異常

前言:

   對於Java和C類語言來說,它們二者之間存在一個很大的區別:記憶體動態分配和垃圾回收機制(GC)。對於C 或C++來說,它們的記憶體管理是有開發人員決定的,也就是收一個物件的生存週期各個部分都具有開發人員的影子。而Java就不同了,它將這些工作全部交由Java虛擬機器來管理,記憶體如何分配和如何回收垃圾都由虛擬機器自主管理,極大節省了開發人員的工作。但是這樣一來它的弊端就出現了,一是耗費了一定的資源來供虛擬機器進行運轉(可以理解~~不是大毛病)。二是一旦虛擬機器工作出誤,造成記憶體洩漏或者溢位方面的問題,排查錯誤將會是一件比較麻煩的錯誤。所以學習好Java虛擬機器是怎樣管理記憶體和進行GC工作是很重要的。

Java的記憶體分配機制:

   Java虛擬機器在執行Java程式時會把它管理的記憶體劃分為若干不同的資料區域。這些區域具有不同的用途、建立銷燬時間,如圖:

 

      程式計數器:

       PC暫存器是用於儲存每個執行緒下一步將執行的JVM指令,如該方法為native的,則PC暫存器中不儲存任何資訊(一個java的方法:該方法的實現由非java語言實現)。

       程式計數器是Java執行緒活的靈魂。它負責當前執行緒所執行的位元組碼的行號指示器(Java中通過改變這個計數器的值來進行指令的選擇和跳轉)。

       簡單來說,不如Java的多執行緒,我們都知道多執行緒本質上是處理器分時複用的一種體現,而且我們都知道對於一個處理器來說,同一個時間,只能處理一個指令,那麼問題來了?當執行緒切換分別執行不同的指令後,如何保證下一個執行緒恢復到被切換之前的執行位置呢?沒錯,就是這個程式計數器,每個執行緒之中都擁有一個獨立的程式計數器。

       Java虛擬機器棧:

       記憶體模型:Java方法被執行時都會建立一個棧幀(stack frame)用於儲存區域性變量表、操作棧、動態連結、方法出口等資訊。二者生命週期相同。

       Java方法從執行到結束對應著棧幀在虛擬機器棧的入棧到出棧。

       我們日常所說的“棧記憶體”是指虛擬機器棧中的區域性變量表,存放者編譯期中可知的基本資料型別、物件引用和returnAddress型別(指向了一條位元組碼指令的地址)。區域性變量表所需的記憶體空間在編譯期間完成,也就是說當執行時自動分配記憶體時是有限制的。

        在區域性變數空間 中規定了兩種異常:當執行緒請求的棧深度大於虛擬機器所允許的深度,丟擲StackOverflowError;當虛擬機器棧可以動態擴充套件,但是擴充套件時無法申請到足夠的記憶體時會丟擲OutOfMemoryEror的異常。

       本地方法棧:

       本地方法棧和虛擬機器棧功能類似,區別在於虛擬機器棧為Java方法服務。本地方法棧為native方法服務(一個java呼叫非java程式碼的介面:該方法的實現由非java語言實現)。

       Java堆:

       Java堆(Java heap)是jvm所管理的最大一塊記憶體,他是所有執行緒共享的一塊記憶體區域,在虛擬機器啟動建立時,用來存放物件例項。 

       可以認為Java中所有通過new建立的物件的記憶體都在此分配,Heap中的物件的記憶體需要等待GC進行回收。

Java堆細分可以為:新生代和老年代;再細緻分有Eden空間、from survivor空間、to survivor 空間等。

       方法區:

       和Java堆一樣,被各個執行緒共享的記憶體區域,他用於儲存已被虛擬機器載入的類的資訊、常量、靜態變數、即時編譯期編譯後的程式碼等資料。 

       在Sun JDK中這塊區域對應的為PermanetGeneration,又稱為持久代。例如:Hotspot虛擬機器。

       當方法區無法滿足記憶體分配需求時,將丟擲outofmemoryerror的異常。

       執行時常量池:

       它方法區的一部分。用於存放編譯期生成的各種字面量和符號引用資訊(存放的為類中的固定的常量資訊、方法和Field的引用資訊等,其空間從方法區域中分配)。

       當常量池無法再申請到足夠的記憶體時會丟擲outofmemoryerror的異常。

     總結:

      可以看出,主要異常分為OutOfMemoryError和StackOverflowError兩種。各個記憶體區域,在編譯時期,如果申請不到所需的記憶體會丟擲StackOverflowError;在執行時期,動態分配無法擴充套件到申請的空間就會丟擲OutOfMemoryError的異常。

      學習之處,不足之處,請多指正!

補充堆記憶體溢位的常見和解決方案:

【情況一】:

  Java.lang.OutOfMemoryError: Java heap space:

 【解釋】:這種是java堆記憶體不夠,一個原因是真不夠,另一個原因是程式中有死迴圈;

 【解決方案】:如果是java堆記憶體不夠的話,可以通過調整JVM下面的配置來解決:

  < jvm-arg>-Xms3062m < / jvm-arg>

  < jvm-arg>-Xmx3062m < / jvm-arg>

【情況二】

  java.lang.OutOfMemoryError: GC overhead limit exceeded

  【解釋】:JDK6新增錯誤型別,當GC為釋放很小空間佔用大量時間時丟擲;一般是因為堆太小,導致異常的原因,沒有足夠的記憶體。

  【解決方案】:

  1、檢視系統是否有使用大記憶體的程式碼或死迴圈;

  2、通過新增JVM配置,來限制使用記憶體:

  < jvm-arg>-XX:-UseGCOverheadLimit< /jvm-arg>

【情況三】:

  java.lang.OutOfMemoryError: PermGen space:這種是P區記憶體不夠,可通過調整JVM的配置:

  < jvm-arg>-XX:MaxPermSize=128m< /jvm-arg>

  < jvm-arg>-XXermSize=128m< /jvm-arg>

  【注】:

  JVM的Perm區主要用於存放Class和Meta資訊的,Class在被Loader時就會被放到PermGen space,這個區域成為年老代,GC在主程式執行期間不會對年老區進行清理,預設是64M大小,當程式需要載入的物件比較多時,超過64M就會報這部分記憶體溢位了,需要加大記憶體分配,一般128m足夠。

【情況四】:

  java.lang.OutOfMemoryError: Direct buffer memory

  調整-XX:MaxDirectMemorySize= 引數,如新增JVM配置:

  < jvm-arg>-XX:MaxDirectMemorySize=128m< /jvm-arg>

【情況五】:

  java.lang.OutOfMemoryError: unable to create new native thread

  【原因】:Stack空間不足以建立額外的執行緒,要麼是建立的執行緒過多,要麼是Stack空間確實小了。

  【解決】:由於JVM沒有提供引數設定總的stack空間大小,但可以設定單個執行緒棧的大小;而系統的使用者空間一共是3G,除了Text/Data/BSS /MemoryMapping幾個段之外,Heap和Stack空間的總量有限,是此消彼長的。因此遇到這個錯誤,可以通過兩個途徑解決:

  1.通過 -Xss啟動引數減少單個執行緒棧大小,這樣便能開更多執行緒(當然不能太小,太小會出現StackOverflowError);

  2.通過-Xms -Xmx 兩引數減少Heap大小,將記憶體讓給Stack(前提是保證Heap空間夠用)。

【情況六】:

  java.lang.StackOverflowError

  【原因】:這也記憶體溢位錯誤的一種,即執行緒棧的溢位,要麼是方法呼叫層次過多(比如存在無限遞迴呼叫),要麼是執行緒棧太小。

  【解決】:優化程式設計,減少方法呼叫層次;調整-Xss引數增加執行緒棧大小。