回顧java程序執行流程java
如上圖所示,首先Java源代碼文件(.java後綴)會被Java編譯器編譯爲字節碼文件(.class後綴),而後由JVM中的類加載器加載各個類的字節碼文件,加載完畢以後,交由JVM執行引擎執行。在整個程序執行過程當中,JVM會用一段空間來存儲程序執行期間須要用到的數據和相關信息,這段空間通常被稱做爲Runtime Data Area(運行時數據區),也就是咱們常說的JVM內存。所以,在Java中咱們經常說到的內存管理就是針對這段空間進行管理(如何分配和回收內存空間)。程序員
根據 JVM 規範,JVM 內存共分爲虛擬機棧、堆、方法區、程序計數器、本地方法棧五個部分。數組
名稱 | 特徵 | 做用 | 配置參數 | 異常 |
---|---|---|---|---|
程序計數器 | 佔用內存小,線程私有,生命週期與線程相同 | 大體爲字節碼行號指示器 | 無 | 無 |
虛擬機棧 | 線程私有,生命週期與線程相同,使用連續的內存空間 | Java 方法執行的內存模型,存儲局部變量表、操做棧、動態連接、方法出口等信息 | -Xss | StackOverflowError/OutOfMemoryError |
堆 | 線程共享,生命週期與虛擬機相同,能夠不使用連續的內存地址 | 保存對象實例,全部對象實例(包括數組)都要在堆上分配 | -Xms -Xsx -Xmn | OutOfMemoryError |
方法區 | 線程共享,生命週期與虛擬機相同,能夠不使用連續的內存地址 | 存儲已被虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯後的代碼等數據 | -XX:PermSize:16M架構 -XX:MaxPermSize64M性能 /spa -XX:MetaspaceSize=16M線程 - XX:MaxMetaspaceSize=64M代理 |
OutOfMemoryError |
本地方法棧 | 線程私有 | 爲虛擬機使用到的 Native 方法服務 | 無 | StackOverflowError/OutOfMemoryError |
Hotspot Java7和Java8內存結構差別code
Java7的內存結構jwt
Java8的內存結構
Java7和Java8內存結構的不一樣主要體如今方法區的實現
方法區是java虛擬機規範中定義的一種概念上的區域,不一樣的廠商能夠對虛擬機進行不一樣的實現。
咱們一般使用的Java SE都是由Sun JDK和OpenJDK所提供,這也是應用最普遍的版本。而該版本使用的VM就是HotSpot VM。一般狀況下,咱們所講的java虛擬機指的就是HotSpot的版本。
PermGen(永久代)
Java7
及之前版本的Hotspot中方法區位於永久代中。同時,永久代和堆是相互隔離的,但它們使用的物理內存是連續的,永久代的垃圾收集是和老年代捆綁在一塊兒的,所以不管誰滿了,都會觸發永久代和老年代的垃圾收集。
Java7
中永久代中存儲的部分數據已經開始轉移到Java Heap
或Native Memory
中了。好比,符號引用(Symbols)
轉移到了Native Memory
;字符串常量池(interned strings)
轉移到了Java Heap
;類的靜態變量(class statics)
轉移到了Java Heap
。
絕大部分Java程序員應該都見過java.lang.OutOfMemoryError: PremGen space
異常。這裏的PermGen space
其實指的就是方法區。不過方法區和PermGen space
又有着本質的區別。前者是JVM的規範,然後者則是JVM規範的一種實現,而且只有HotSpot纔有PermGen space
,而對於其餘類型的虛擬機,如JRockit(Oracle)
、J9(IBM)
並無PermGen space。因爲方法區主要存儲類的相關信息,因此對於動態生成類的狀況比較容易出現永久代的內存溢出。而且JDK 1.8
中永久代的參數PermSize
和MaxPermSize
已經失效。
Metaspace(元空間)
對於Java8,HotSpot取消了永久代,那麼是否是就沒有方法區了呢?固然不是,方法區只是一個規範,只不過它的實現變了。
在Java8中,元空間(Metaspace)登上舞臺,方法區存在於元空間(Metaspace)。同時,元空間再也不與堆連續,並且是存在於本地內存(Native memory)。
JDK1.7後對JVM架構進行了改造,將類元數據放到本地內存中,另外,將字符串常量池和靜態變量放到Java堆裏。HotSpot VM將會爲類的元數據明確分配和釋放本地內存。在這種架構下,類元信息就突破了原來 -XX:MaxPermSize
的限制,如今可使用更多的本地內存。這樣就從必定程度上解決了原來在運行時生成大量類形成常常Full GC問題,如運行時使用反射、代理等。因此升級之後Java堆空間可能會增長。
元空間的本質和永久代相似,都是對JVM規範中方法區的實現。不過元空間與永久代之間的最大區別在於:元空間並不在虛擬機中,而是使用本地內存。本地內存(Native memory),也稱爲C-Heap,是供JVM自身進程使用的。當Java Heap空間不足時會觸發GC,但Native memory空間不夠卻不會觸發GC。默認狀況下元空間是能夠無限使用本地內存的,但爲了避免讓它如此膨脹,JVM一樣提供了參數來限制它使用的使用。
-XX:MetaspaceSize,class metadata的初始空間配額,以bytes爲單位,達到該值就會觸發垃圾收集進行類型卸載,同時GC會對該值進行調整:若是釋放了大量的空間,就適當的下降該值;若是釋放了不多的空間,那麼在不超過MaxMetaspaceSize(若是設置了的話),適當的提升該值。
-XX:MaxMetaspaceSize,能夠爲class metadata分配的最大空間。默認是沒有限制的。
-XX:MinMetaspaceFreeRatio,在GC以後,最小的Metaspace剩餘空間容量的百分比,減小爲class metadata分配空間致使的垃圾收集。
-XX:MaxMetaspaceFreeRatio,在GC以後,最大的Metaspace剩餘空間容量的百分比,減小爲class metadata釋放空間致使的垃圾收集。
因此對於方法區,Java8以後的變化
移除了永久代(PermGen),替換爲元空間(Metaspace)
永久代中的class metadata(類元信息)轉移到了native memory(本地內存,而不是虛擬機)
永久代中的interned Strings(字符串常量池) 和 class static variables(類靜態變量)轉移到了Java heap
永久代參數(PermSize MaxPermSize)-> 元空間參數(MetaspaceSize MaxMetaspaceSize)
Java8爲何要將永久代替換成Metaspace?
字符串存在永久代中,容易出現性能問題和內存溢出。
類及方法的信息等比較難肯定其大小,所以對於永久代的大小指定比較困 難,過小容易出現永久代溢出,太大則容易致使老年代溢出。
永久代會爲 GC 帶來沒必要要的複雜度,而且回收效率偏低。
Oracle 可能會將HotSpot 與 JRockit 合二爲一,JRockit沒有所謂的永久代。
歡迎關注公衆號,後續文章更新通知,一塊兒討論技術問題 。