Java面試進階:jvm內存區域的劃分

1)程序計數器(PC,Program Counter Register)。在 JVM 規範中,每個線程都有它自己的程序計數器,並且任何時間一個線程都只有一個方法在執行,也就是所謂的當前方法。程序計數器會存儲當前線程正在執行的 Java 方法的 JVM 指令地址;或者,如果是在執行本地方法,則是未指定值(undefined)。

2)Java 虛擬機棧(Java Virtual Machine Stack),早期也叫 Java 棧。每個線程在創建時都會創建一個虛擬機棧,其內部保存一個個的棧幀(Stack Frame),對應着一次次的 Java 方法調用。如果在方法中調用了其他方法,對應的新的棧幀會被創建出來,成爲新的當前幀,一直到它返回結果或者執行結束。JVM 直接對 Java 棧的操作只有兩個,就是對棧幀的壓棧和出棧。棧幀中存儲着局部變量表、操作數(operand)棧、動態鏈接、方法正常退出或者異常退出的定義等。

3)堆(Heap),它是 Java 內存管理的核心區域,用來放置 Java 對象實例,幾乎所有創建的 Java 對象實例都是被直接分配在堆上。堆被所有的線程共享,在虛擬機啓動時,我們指定的“Xmx”之類參數就是用來指定最大堆空間等指標。堆內空間被不同的垃圾收集器進行進一步的細分,最有名的就是新生代、老年代的劃分。

4)方法區(Method Area)。這也是所有線程共享的一塊內存區域,用於存儲所謂的元(Meta)數據,例如類結構信息,以及對應的運行時常量池、字段、方法代碼等。很多人習慣於將方法區稱爲永久代(Permanent Generation)。Oracle JDK 8 中將永久代移除,同時增加了元數據區(Metaspace)。

5)本地方法棧(Native Method Stack)。它和 Java 虛擬機棧是非常相似的,支持對本地方法的調用,也是每個線程都會創建一個。

6)直接內存(Direct Memory)區域,它就是我在專欄第 12 講中談到的 Direct Buffer 所直接分配的內存

7)Code Cache:JVM 本身是個本地程序,還需要其他的內存去完成各種基本任務,如JIT Compiler 在運行時對熱點方法進行編譯,就會將編譯後的方法儲存在 Code Cache 裏面;

 

Java 對象是不是都創建在堆上的呢?

通過逃逸分析,JVM 會在棧上分配那些不會逃逸的對象,這在理論上是可行的,實際上所有的對象實例都是創建在堆上。Intern 字符串的緩存和靜態變量曾經都被分配在永久代上,而永久代已經被元數據區取代。但是,Intern 字符串緩存和靜態變量並不是被轉移到元數據區,而是直接在堆上分配,所以這一點同樣符合前面一點的結論:對象實例都是分配在堆上。

 

幾種情況的OOM:

1)堆內存不足是最常見的 OOM 原因之一,拋出的錯誤信息是“java.lang.OutOfMemoryError:Java heap space”,原因可能千奇百怪,例如,可能存在內存泄漏問題;也很有可能就是堆的大小不合理,

2)而對於 Java 虛擬機棧和本地方法棧,這裏要稍微複雜一點。如果我們寫一段程序不斷的進行遞歸調用,而且沒有退出條件,就會導致不斷地進行壓棧。類似這種情況,JVM 實際會拋出 StackOverFlowError;當然,如果 JVM 試圖去擴展棧空間的的時候失敗,則會拋出 OutOfMemoryError。

3)老版本的 Oracle JDK,因爲永久代的大小是有限的,JVM 對永久代垃圾回收非常不積極,所以當我們不斷添加新類型的時候java.lang.OutOfMemoryError: PermGen space”。

4)隨着元數據區的引入,方法區內存已經不再那麼窘迫,所以相應的 OOM 有所改觀,出現 OOM,異常信息則變成了:“java.lang.OutOfMemoryError: Metaspace”。

5)直接內存不足,也會導致 OOM

 

我在試圖分配一個 100M bytes 大數組的時候發生了 OOME,但是 GC 日誌顯示,明明堆上還有遠不止 100M 的空間,你覺得可能問題的原因是什麼?

要看下新生代和老年代的垃圾回收機制是什麼。如果新生代是serial,會默認使用copying算法,利用兩塊eden和survivor來進行處理。但是默認當遇到超大對象時,會直接將超大對象放置到老年代中,而不用走正常對象的存活次數記錄。因爲要放置的是一個byte數組,那麼必然需要申請連續的空間,當空間不足時,會進行gc操作。這裏又需要看老年代的gc機制是哪一種。如果是serial old,那麼會採用mark compat,會進行整理,從而整理出連續空間,如果還不夠,說明是老年代的空間不夠,所謂的堆內存大於100m是新+老共同的結果。如果採用的是cms(concurrent mark sweep),那麼只會標記清理,並不會壓縮,所以內存會碎片化,同時可能出現浮游垃圾。如果是cms的話,即使老年代的空間大於100m,也會出現沒有連續的空間供該對象使用。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章