JVM原理及內存優化方向


通俗易懂理解JVM結構

   說明:本篇內容是結合網上各位大牛的關於JVM的文章,通過作者的理解,希望以一種比較易懂的方式,讓各位朋友們理解JVM到底是怎么一回事兒,其中部分圖片和內容引用來自於網絡,如有雷同,請見諒~~

一、JVM內存區域模型是啥樣?

這個是JVM大致的內存分布模型,看起來比較直觀:

wKioL1UaFevTgHeyAAEMwddBwKI637.jpg

這個是更精細化的JVM內存模型,區別主要是方法區和堆是公共內存區,其他是私有的:

wKiom1UaFK3CIqP1AAEHU6eDngI511.jpg

1.方法區:

也稱"永久代” 、“非堆”, 它用於存儲虛擬機加載的類信息、常量、靜態變量、是各個線程共享的內存區域。可以說方法區就是公共存放常量等靜態的常量池。

運行時常量池:是方法區的一部分,Class文件中除了有類的版本、字段、方法、接口等描述信息外,還有一項信息是常量池,用於存放編譯器生成的各種符號引用,這部分內容將在類加載后放到方法區的運行時常量池中。

方法區默認大小:16MB,最大值為64MB(補充:看到還有資料說是根據物理內存大小調整的,)

-XX:PermSize  設置方法區大小

-XX:MaxPermSize  設置方法區最大限制

------說明:方法區新人看的時候,容易看不明白,本人理解是方法區就是堆(heap)中的永久代,兩個稱呼都稱呼同一種內存區

2.虛擬機棧

描述的是java方法執行的內存模型:每個方法被執行的時候 都會創建一個“棧幀”用於存儲局部變量表(包括參數)、操作棧、方法出口等信息。每個方法被調用到執行完的過程,就對應着一個棧幀在虛擬機棧中從入棧到出棧的過程。聲明周期與線程相同,是線程私有的。

 局部變量表存放了編譯器可知的各種基本數據類型(boolean、byte、char、short、int、float、long、 double)、對象引用(引用指針,並非對象本身),其中64位長度的long和double類型的數據會占用2個局部變量的空間,其余數據類型只占1個。局部變量表所需的內存空間在編譯期間完成分配,當進入一個方法時,這個方法需要在棧幀中分配多大的局部變量是完全確定的,在運行期間棧幀不會改變局部 變量表的大小空間。

棧的默認大小是1M

-Xss2m 這樣設置成2M

異常:Fatal:Stack size too small

異常的引起一般是線程數目太多

3.本地方法棧

即為一些Native方法分配的stack

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

一般也是由線程太多引起,增加棧空間,同上方法

 與虛擬機棧基本類似,區別在於虛擬機棧為虛擬機執行的java方法服務,而本地方法棧則是為Native方法服務。

------說明:VM棧和native棧一般是不用調整的,使用默認即可

4. 

也叫做java堆、GC堆是java虛擬機所管理的內存中最大的一塊內存區域,也是被各個線程共享的內存區域,在JVM啟動時創建。該內存區域存放了對象實例及數組(所有new的對象)。

堆將會作為下節重點講解

------說明:堆則是整個JVM調優的重點

二、JVM調優重點區域:堆

如下圖所示,為Java堆中的各代分布:

wKiom1UaFLDhXIyiAAEBvrfKMmY029.jpg

Young(年輕代)
新生代進一步划分為3個區域:一個相對大點的區域,稱為”伊甸園區(Eden)”;兩個相對小點的區域稱為”From 幸存區(survivor)”和”To 幸存區(survivor)”。按照規定,新對象會首先分配在 Eden 中(如果新對象過大,會直接分配在老年代中)。在GC中,Eden 中的對象會被移動到survivor中,直至對象滿足一定的年紀(定義為熬過GC的次數),會被移動到老年代。

wKioL1UaFe2j7QbLAACJ0huAPzA467.jpg

上圖演示GC過程,***表示死對象,綠色表示剩余空間,紅色表示幸存對象

如果還沒有明白,則看這個圖

分代垃圾回收過程演示 
當一個URL被訪問時,內存申請過程如下:

A. JVM會試圖為相關Java對象在Eden中初始化一塊內存區域

B. 當Eden空間足夠時,內存申請結束。否則到下一步

C. JVM試圖釋放在Eden中所有不活躍的對象(這屬於1或更高級的垃圾回收), 釋放后若Eden空間仍然不足以放入新對象,則試圖將部分Eden中活躍對象放入Survivor區

D. Survivor區被用來作為Eden及OLD的中間交換區域,當OLD區空間足夠時,Survivor區的對象會被移到Old區,否則會被保留在Survivor區

E. 當OLD區空間不夠時,JVM會在OLD區進行完全的垃圾收集(0級)

F. 完全垃圾收集后,若Survivor及OLD區仍然無法存放從Eden復制過來的部分對象,導致JVM無法在Eden區為新對象創建內存區域,則出現"out of memory錯誤"

wKiom1UaFLPSK_o1AAFLAiSczB8073.jpg

wKiom1UaFLbDj85QAAFLlA9KHrw903.jpg

wKioL1UaFfSj9ifsAAFyV3-gqVQ723.jpg

wKiom1UaFLrSRPtfAAGN9wQZ1ck117.jpg 

        總結一下,對象一般出生在Eden區,年輕代GC過程中,對象在2個幸存區之間移動,如果對象存活到適當的年齡,會被移動到老年代。當對象在老年代死亡時,就需要更高級別的GC,更重量級的GC算法(復制算法不適用於老年代,因為沒有多余的空間用於復制)

   現在應該能理解為什么新生代大小非常重要了(譯者,有另外一種說法:新生代大小並不重要,影響GC的因素主要是幸存對象的數量),如果新生代過小,會導致新生對象很快就晉升到老年代中,在老年代中對象很難被回收。如果新生代過大,會發生過多的復制過程。我們需要找到一個合適大小,不幸的是,要想獲得一個合適的大小,只能通過不斷的測試調優。這就需要JVM參數了

 

-XX:NewSize and -XX:MaxNewSize

就像可以通過參數(-Xmsand -Xmx) 指定堆大小一樣,可以通過參數指定新生代大小。設置XX:MaxNewSize 參數時,應該考慮到新生代只是整個堆的一部分,新生代設置的越大,老年代區域就會減少。一般不允許新生代比老年代還大,因為要考慮GC時最壞情況,所有對象都晉升到老年代。(譯者:會發生OOM錯誤)-XX:MaxNewSize 最大可以設置為-Xmx/2.

 

考慮性能,一般會通過參數-XX:NewSize 設置新生代初始大小。如果知道新生代初始分配的對象大小(經過監控) ,這樣設置會有幫助,可以節省新生代自動擴展的消耗。

 

-XX:NewRatio

可以設置新生代和老年代的相對大小。這種方式的優點是新生代大小會隨着整個堆大小動態擴展。參數-XX:NewRatio 設置老年代與新生代的比例。例如-XX:NewRatio=3 指定老年代/新生代為3/1. 老年代占堆大小的3/4 ,新生代占1/4 .

Tenured(年老代)
年老代存放從年輕代存活的對象。一般來說年老代存放的都是生命期較長的對象。

Perm(持久代)----同時也叫方法區
用 於存放靜態文件,如今Java類、方法等。持久代對垃圾回收沒有顯著影響,但是有些應用可能動態生成或者調用一些class,例如Hibernate等, 在這種時候需要設置一個比較大的持久代空間來存放這些運行過程中新增的類。持久代大小通過-XX:MaxPermSize=進行設置。

持久代一般固定大小為64m

下圖是JVM在內存空間(堆空間)中申請新對象過程的活動圖:

wKiom1UaFL6ixT2gAAHSOQPntw0987.jpg

 

 

 摘自:http://blog.51cto.com/vekergu/1626733


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM