一:JVM中內存
JVM中內存通常划分為兩個部分,分別為堆內存與棧內存,棧內存主要用運行線程方法
存放本地暫時變量與線程中方法運行時候須要的引用對象地址。
JVM全部的對象信息都
存放在堆內存中。相比棧內存,堆內存能夠所大的多,所以JVM一直通過對堆內存划分
不同的功能區塊實現對堆內存中對象管理。
堆內存不夠最常見的錯誤就是OOM(OutOfMemoryError)
棧內存溢出最常見的錯誤就是StackOverflowError。程序有遞歸調用時候最easy發生
二:堆內存划分
在JDK7以及其前期的JDK版本號中。堆內存通常被分為三塊區域Nursery內存(young
generation)、長時內存(old generation)、永久內存(Permanent Generation for
VM Matedata),顯演示樣例如以下圖:
當中最上一層是Nursery內存,一個對象被創建以后首先被放到Nursery中的Eden內
存中,假設存活期超兩個Survivor之后就會被轉移到長時內存中(Old Generation)中
永久內存中存放着對象的方法、變量等元數據信息。通過假設永久內存不夠。我們
就會得到例如以下錯誤:
java.lang.OutOfMemoryError: PermGen
而在JDK8中情況發生了明顯的變化,就是普通情況下你都不會得到這個錯誤,原因
在於JDK8中把存放元數據中的永久內存從堆內存中移到了本地內存(native memory)
中,JDK8中JVM堆內存結構就變成了例如以下:
這樣永久內存就不再占用堆內存。它能夠通過自己主動增長來避免JDK7以及前期版本號中
常見的永久內存錯誤(java.lang.OutOfMemoryError: PermGen),或許這個就是你的
JDK升級到JDK8的理由之中的一個吧。
當然JDK8也提供了一個新的設置Matespace內存
大小的參數。通過這個參數能夠設置Matespace內存大小,這樣我們能夠依據自己
項目的實際情況,避免過度浪費本地內存,達到有效利用。
-XX:MaxMetaspaceSize=128m 設置最大的元內存空間128兆
注意:假設不設置JVM將會依據一定的策略自己主動添加本地元內存空間。
假設你設置的元內存空間過小,你的應用程序可能得到下面錯誤:
java.lang.OutOfMemoryError: Metadata space
java1.8之前內存區域分為方法區、堆內存、虛擬機棧、本地方法棧、程序計數器。 下圖所示:
方法區(Method Area)與Java堆一樣,是各個線程共享的內存區域,它用於存儲已被虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯后的代碼等數據。雖然Java虛擬機規范把方法區描述為堆的一個邏輯部分,但是它卻有一個別名叫做Non-Heap(非堆),目的應該是與Java堆區分開來。很多人都更願意把方法區稱為“永久代”(Permanent Generation)。從jdk1.7已經開始准備“去永久代”的規划,jdk1.7的HotSpot中,已經把原本放在方法區中的靜態變量、字符串常量池等移到堆內存中。
在jdk1.8中,永久代已經不存在,存儲的類信息、編譯后的代碼數據等已經移動到了元空間(MetaSpace)中,元空間並沒有處於堆內存上,而是直接占用的本地內存(NativeMemory)。
元空間的本質和永久代類似,都是對JVM規范中方法區的實現。不過元空間與永久代之間最大的區別在於:元空間並不在虛擬機中,而是使用本地內存。因此,默認情況下,元空間的大小僅受本地內存限制,但可以通過以下參數來指定元空間的大小:
-XX:MetaspaceSize,初始空間大小,達到該值就會觸發垃圾收集進行類型卸載,同時GC會對該值進行調整:如果釋放了大量的空間,就適當降低該值;如果釋放了很少的空間,那么在不超過MaxMetaspaceSize時,適當提高該值。
-XX:MaxMetaspaceSize,最大空間,默認是沒有限制的。
除了上面兩個指定大小的選項以外,還有兩個與 GC 相關的屬性:
-XX:MinMetaspaceFreeRatio,在GC之后,最小的Metaspace剩余空間容量的百分比,減少為分配空間所導致的垃圾收集
-XX:MaxMetaspaceFreeRatio,在GC之后,最大的Metaspace剩余空間容量的百分比,減少為釋放空間所導致的垃圾收集
注意:如果不設置JVM將會根據一定的策略自動增加本地元內存空間。
如果你設置的元內存空間過小,你的應用程序可能得到以下錯誤:
java.lang.OutOfMemoryError: Metadata space
在Java7之前,HotSpot虛擬機中將GC分代收集擴展到了方法區,使用永久代來實現了方法區。這個區域的內存回收目標主要是針對常量池的回收和對類型的卸載。而在Java8中,已經徹底沒有了永久代,將方法區直接放在一個與堆不相連的本地內存區域,這個區域被叫做元空間。
常量池里存儲着字面量和符號引用。
符號引用包括:1.類的全限定名,2.字段名和屬性,3.方法名和屬性。
字符串池里的內容是在類加載完成,經過驗證,准備階段之后在堆中生成字符串對象實例,然后將該字符串對象實例的引用值存到string pool中(記住:string pool中存的是引用值而不是具體的實例對象,具體的實例對象是在堆中開辟的一塊空間存放的。)。 在HotSpot VM里實現的string pool功能的是一個StringTable類,它是一個哈希表,里面存的是駐留字符串(也就是我們常說的用雙引號括起來的)的引用(而不是駐留字符串實例本身),也就是說在堆中的某些字符串實例被這個StringTable引用之后就等同被賦予了”駐留字符串”的身份。這個StringTable在每個HotSpot VM的實例只有一份,被所有的類共享。
- 1.字符串池常量池在每個VM中只有一份,存放的是字符串常量的引用值,存放在堆中.
- 2.class常量池是在編譯的時候每個class都有的,在編譯階段,存放的是常量的符號引用。
- 3.運行時常量池是在類加載完成之后,將每個class常量池中的符號引用值轉存到運行時常量池中,也就是說,每個class都有一個運行時常量池,類在解析之后,將符號引用替換成直接引用,與全局常量池中的引用值保持一致。