Java內存模型

一、JAVA內存模型

      運行時內存模型,分爲線程私有和共享數據區兩大類。


程序計數器
指向當前線程下一條需要執行的字節碼指令的地址

內存溢出:不會發生

VM Stack
方法執行的內存區,每個方法執行時會在虛擬機棧中創建棧幀;
內存溢出:StackOverflowError和OutOfMemoryError。
溢出原因:
StackOverflowError:如果請求的棧的深度大於虛擬機所允許的深度,將會拋出這個異常,如果使用虛擬機默認參數,一般達到1000到2000這樣的深度沒有問題。
OutOfMemoryError:因爲除掉堆內存和方法區容量,剩下的內存由虛擬機棧和本地方法棧瓜分,如果剩下的內存不足以滿足更多的工作線程的運行、或者不足以拓展虛擬機棧的時候,就會拋出OutOfMemoryError異常。
解決方法:
針對StackOverflowError:
1.首先棧溢出會輸出異常信息,根據信息查看對應的方法調用是否出現無限調用、或者棧幀過大等代碼邏輯上的問題,通過修改代碼邏輯解決;
2.如果確確實實需要更大的棧容量,可以檢查並調大棧容量:-Xss16m。


針對OutOfMemoryError:
1.首先檢查是否創建過多的線程,減少線程數
2.可以通過“減少最大堆容量”或“減少棧容量”來解決

本地方法棧
虛擬機的Native方法執行的內存區;
溢出原因:同虛擬機棧
解決方法:同虛擬機棧

Java堆:

作用:所有線程共享,存放對象實例

內存溢出:OutOfMemoryError:Java heap space

溢出原因:堆中沒有足夠內存完成實例分配,並且無法繼續拓展時

解決方法

1.內存泄露檢查:首先通過“內存溢出快照 + MAT等分析工具”,分析是否存在內存泄露現象,檢查時可以懷疑的點比如集合、第三方庫如數據庫連接的使用、new關鍵字相關等。

2.如果沒有內存泄露,那麼就是內存溢出,所有對象卻是都還需要存活,這個時候就只能調大堆內存了:-Xms和-Xmx。


方法區:
作用又叫永久區(Permanent)存放類信息、常量、靜態變量、編譯器編譯後的代碼等數據;參數設置示例:-XX:PermSize=5M -XX:MaxPermSize=7M

內存溢出:OutOfMemoryError:PermGen space

溢出原因:方法區沒有足夠內存完成內存分配存放運行時新加載的class信息

解決方法

1. 內存泄露檢查:檢查是否加載過多class文件(jar文件),或者重複加載相同的class文件(jar文件)多次

2. 通過-XX:PermSize=64M -XX:MaxPermSize=128M改大方法區大小

運行時常量池
 

作用:方法區的一部分,存放常量

內存溢出:OutOfMemoryError:PermGen space

溢出原因:方法區沒有足夠的內存完成內存分配,存放運行時新創建的常量,比如String類的intern()方法,其作用是如果常量池已經包含一個相同的字符串,則返回其引用,否則將此String對象包含的字符串添加到常量池中。

解決方法

1. 內存泄露檢查:檢查是否創建過多常量

2. 通過-XX:PermSize=64M -XX:MaxPermSize=128M改大方法區大

   

對於大多數的程序員來說,Java內存比較流行的說法便是堆和棧,這其實是非常粗略的一種劃分,這種劃分的”堆”對應內存模型的Java堆,”棧”是指虛擬機棧,然而Java內存模型遠比這更復雜,想深入瞭解Java的內存,還是有必要明白整個內存模型。

二、 詳細模型
       運行時內存分爲五大塊區域(常量池屬於方法區,算作一塊區域),前面簡要介紹了每個區域的功能,那接下來再詳細說明每個區域的內容,Java內存總體結構圖如下:



      Java虛擬機在運行時會爲每一個線程在內存中分配了一個虛擬機棧,來表示線程的運行狀態和信息,虛擬機棧中的元素稱之爲棧幀(JVM stack frame),每一個棧幀表示這對一個方法的調用信息。


堆內存

Java 中的堆是 JVM 所管理的最大的一塊內存空間,主要用於存放各種類的實例對象。
在 Java 中,堆被劃分成兩個不同的區域:新生代 ( Young )、老年代 ( Old )。新生代 ( Young ) 又被劃分爲三個區域:Eden、From Survivor、To Survivor。
這樣劃分的目的是爲了使 JVM 能夠更好的管理堆內存中的對象,包括內存的分配以及回收。
堆的內存模型大致爲:





從圖中可以看出: 堆大小 = 新生代 + 老年代。
(本人使用的是 JDK1.6,以下涉及的 JVM 默認值均以該版本爲準。)
默認的,新生代 ( Young ) 與老年代 ( Old ) 的比例的值爲 1:2 ( 該值可以通過參數 –XX:NewRatio 來指定 )。

新生代 ( Young ) 被細分爲 Eden 和 兩個 Survivor 區域,這兩個 Survivor 區域分別被命名爲 from 和 to,以示區分。
默認的,Edem : from : to = 8 : 1 : 1 ( 可以通過參數 –XX:SurvivorRatio 來設定 ),即: Eden = 8/10 的新生代空間大小,from = to = 1/10 的新生代空間大小。
JVM 每次只會使用 Eden 和其中的一塊 Survivor 區域來爲對象服務,所以無論什麼時候,總是有一塊 Survivor 區域是空閒着的。
因此,新生代實際可用的內存空間爲 9/10 ( 即90% )的新生代空間。



三、GC堆

Java 中的堆也是 GC 收集垃圾的主要區域。GC 分爲兩種:Minor GC、Full GC ( 或稱爲 Major GC )。
Minor GC 是發生在新生代中的垃圾收集動作,所採用的是複製算法。
新生代幾乎是所有 Java 對象出生的地方,即 Java 對象申請的內存以及存放都是在這個地方。Java 中的大部分對象通常不需長久存活,具有朝生夕滅的性質。
當一個對象被判定爲 "死亡" 的時候,GC 就有責任來回收掉這部分對象的內存空間。新生代是 GC 收集垃圾的頻繁區域。
當對象在 Eden ( 包括一個 Survivor 區域,這裏假設是 from 區域 ) 出生後,在經過一次 Minor GC 後,如果對象還存活,並且能夠被另外一塊 Survivor 區域所容納( 上面已經假設爲 from 區域,這裏應爲 to 區域,即 to 區域有足夠的內存空間來存儲 Eden 和 from 區域中存活的對象 ),則使用複製算法將這些仍然還存活的對象複製到另外一塊 Survivor 區域 ( 即 to 區域 ) 中,然後清理所使用過的 Eden 以及 Survivor 區域 ( 即 from 區域 ),並且將這些對象的年齡設置爲1,以後對象在 Survivor 區每熬過一次 Minor GC,就將對象的年齡 + 1,當對象的年齡達到某個值時 ( 默認是 15 歲,可以通過參數 -XX:MaxTenuringThreshold 來設定 ),這些對象就會成爲老年代。
但這也不是一定的,對於一些較大的對象 ( 即需要分配一塊較大的連續內存空間 ) 則是直接進入到老年代。
Full GC 是發生在老年代的垃圾收集動作,所採用的是標記-清除算法。
現實的生活中,老年代的人通常會比新生代的人 "早死"。堆內存中的老年代(Old)不同於這個,老年代裏面的對象幾乎個個都是在 Survivor 區域中熬過來的,它們是不會那麼容易就 "死掉" 了的。因此,Full GC 發生的次數不會有 Minor GC 那麼頻繁,並且做一次 Full GC 要比進行一次 Minor GC 的時間更長。
另外,標記-清除算法收集垃圾的時候會產生許多的內存碎片 ( 即不連續的內存空間 ),此後需要爲較大的對象分配內存空間時,若無法找到足夠的連續的內存空間,就會提前觸發一次 GC 的收集動作。



四、內存泄露
很多開發人員都碰到過java.lang.OutOfMemoryError的錯誤。這種錯誤又分兩種:java.lang.OutOfMemoryError: Java heap space和java.lang.OutOfMemoryError: PermGen space。引起這種錯誤的原因可能是程序問題,也可能是是JVM參數配置問題引起的。若是參數問題,前者可以同過配置-Xms和-Xmx參數來設置,而後者可以通過配置 -XX:PermSize和-XX:MaxPermSize來設置。





發佈了46 篇原創文章 · 獲贊 35 · 訪問量 34萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章