方法區
方法區,Method Area, 對於習慣在HotSpot虛擬機上開發和部署程序的開發者來說,很多人願意把方法區稱為“永久代”(Permanent Generation),本質上兩者並不等價,僅僅是因為HotSpot虛擬機的設計團隊選擇把GC分代收集擴展至方法區,或者說使用永久代來實現方法區而已。對於其他虛擬機(如BEA JRockit、IBM J9等)來說是不存在永久代的概念的。
主要存放已被虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯后的代碼等數據(比如spring 使用IOC或者AOP創建bean時,或者使用cglib,反射的形式動態生成class信息等)。
細分
方法區被分為兩個主要的子區域:
-
持久代 這個區域會存儲包括類定義、結構、字段、方法(數據及代碼)以及常量在內的類相關數據。它可以通過-XX:PermSize及-XX:MaxPermSize來進行調節。如果它的空間用完了,會導致java.lang.OutOfMemoryError: PermGenspace的異常。而JDK8開始,持久代已經被徹底刪除了,取代它的是另一個內存區域也被稱為元空間。
-
方法區存儲的是每個class的信息:1.類加載器引用(classLoader)2.運行時常量池所有常量、字段引用、方法引用、屬性3.字段數據每個方法的名字、類型(如類的全路徑名、類型或接口) 、修飾符(如public、abstract、final)、屬性4.方法數據每個方法的名字、返回類型、參數類型(按順序)、修飾符、屬性5.方法代碼每個方法編譯后代碼、操作數棧大小、局部變量大小、局部變量表、異常表和每個異常處理的開始位置、結 束位置、代碼處理在程序計數器中的偏移地址、被捕獲的異常類的常量池索引
1.2 運行時常量池
CLass文件中除了有類的版本、字段、方法、接口等描述信息外,還有一項信息是常量池,用於存放編譯期生成的各種字面量和符號引用,這部分內容將在類加載后進入方法區的運行時常量池中存放。
運行時常量池相對於CLass文件常量池的另外一個重要特征是具備動態性,Java語言並不要求常量一定只有編譯期才能產生,也就是並非預置入CLass文件中常量池的內容才能進入方法區運行時常量池,運行期間也可能將新的常量放入池中,這種特性被開發人員利用比較多的就是String類的intern()方法。
1.3 常量池的好處
常量池是為了避免頻繁的創建和銷毀對象而影響系統性能,其實現了對象的共享。
例如字符串常量池,在編譯階段就把所有的字符串文字放到一個常量池中。
- (1)節省內存空間:常量池中所有相同的字符串常量被合並,只占用一個空間。
- (2)節省運行時間:比較字符串時,==比equals()快。對於兩個引用變量,只用==判斷引用是否相等,也就可以判斷實際值是否相等。
雙等號==的含義
- 基本數據類型之間應用雙等號,比較的是他們的數值。
- 復合數據類型(類)之間應用雙等號,比較的是他們在內存中的存放地址。
1 常量池
1.1 Class文件中的常量池
在Class文件結構中,最頭的4個字節用於存儲Megic Number,用於確定一個文件是否能被JVM接受,再接着4個字節用於存儲版本號,前2個字節存儲次版本號,后2個存儲主版本號,再接着是用於存放常量的常量池,由於常量的數量是不固定的,所以常量池的入口放置一個U2類型的數據(constant_pool_count)存儲常量池容量計數值。
常量池主要用於存放兩大類常量:字面量(Literal)和符號引用量(Symbolic References),字面量相當於Java語言層面常量的概念,如文本字符串,聲明為final的常量值等,符號引用則屬於編譯原理方面的概念,包括了如下三種類型的常量:
- 類和接口的全限定名
- 字段名稱和描述符
- 方法名稱和描述符
1.2 運行時常量池
CLass文件中除了有類的版本、字段、方法、接口等描述信息外,還有一項信息是常量池,用於存放編譯期生成的各種字面量和符號引用,這部分內容將在類加載后進入方法區的運行時常量池中存放。
運行時常量池相對於CLass文件常量池的另外一個重要特征是具備動態性,Java語言並不要求常量一定只有編譯期才能產生,也就是並非預置入CLass文件中常量池的內容才能進入方法區運行時常量池,運行期間也可能將新的常量放入池中,這種特性被開發人員利用比較多的就是String類的intern()方法。
1.3 常量池的好處
常量池是為了避免頻繁的創建和銷毀對象而影響系統性能,其實現了對象的共享。
例如字符串常量池,在編譯階段就把所有的字符串文字放到一個常量池中。
- (1)節省內存空間:常量池中所有相同的字符串常量被合並,只占用一個空間。
- (2)節省運行時間:比較字符串時,==比equals()快。對於兩個引用變量,只用==判斷引用是否相等,也就可以判斷實際值是否相等。
雙等號==的含義
- 基本數據類型之間應用雙等號,比較的是他們的數值。
- 復合數據類型(類)之間應用雙等號,比較的是他們在內存中的存放地址。
1.4 基本類型的包裝類和常量池
java中基本類型的包裝類的大部分都實現了常量池技術,即Byte,Short,Integer,Long,Character,Boolean。
這5種包裝類默認創建了數值[-128,127]的相應類型的緩存數據,但是超出此范圍仍然會去創建新的對象。 兩種浮點數類型的包裝類Float,Double並沒有實現常量池技術。
Integer與常量池
Integer i1 = 40; Integer i2 = 40; Integer i3 = 0; Integer i4 = new Integer(40); Integer i5 = new Integer(40); Integer i6 = new Integer(0); System.out.println("i1=i2 " + (i1 == i2)); System.out.println("i1=i2+i3 " + (i1 == i2 + i3)); System.out.println("i1=i4 " + (i1 == i4)); System.out.println("i4=i5 " + (i4 == i5)); System.out.println("i4=i5+i6 " + (i4 == i5 + i6)); System.out.println("40=i5+i6 " + (40 == i5 + i6)); i1=i2 true i1=i2+i3 true i1=i4 false i4=i5 false i4=i5+i6 true 40=i5+i6 true
解釋:
- (1)Integer i1=40;Java在編譯的時候會直接將代碼封裝成Integer i1=Integer.valueOf(40);,從而使用常量池中的對象。
- (2)Integer i1 = new Integer(40);這種情況下會創建新的對象。
- (3)語句i4 == i5 + i6,因為+這個操作符不適用於Integer對象,首先i5和i6進行自動拆箱操作,進行數值相加,即i4 == 40。然后Integer對象無法與數值進行直接比較,所以i4自動拆箱轉為int值40,最終這條語句轉為40 == 40進行數值比較
一個Jvm實例只存在一個堆內存,堆內存的大小是可以調節的。類加器讀取了類文件后,需要把類、方法、常變量放到堆內存中,保存所有引用類型的真實信息以方便執行器執行。
堆內存邏輯上分為三部分:新生+養老+永久
新生區
新生區是類的誕生、成長、消亡的區域,一個類在這里產生,應用,最后被垃圾回收器收集,結束生命。新生區又分為兩部分:伊甸區(Eden Space)和幸存者區(Survivor space),所有的類都是在伊甸區被new出來的。幸村區有兩個:0區(Survivor 0 space)和1區(Survivor 1space)。當伊甸園的空間用完時,程序又需要創建對象,Jvm的垃圾回收器將對伊甸園區進行垃圾回收(Minor GC),將伊甸園區中的不再被其他對象所引用的對象進行銷毀。然后將伊甸園區中的剩余對象移動到幸存0區。若幸存0區也滿了,再對該區進行垃圾回收,然后移動到1區。那如果1區也滿了呢?再移動到養老區。若養老區也滿了,那么這個時候將產生MajorGC(FullGC),進行養老區的內存清理。若養老區執行了Full GC之后發現依然無法進行對象的保存,就會產生OOM異常“OutOfMemoryError“。
如果出現java.lang.OutOfMemoryError:Java heap space異常,說明Java虛擬機的對內存不夠。原因有二:
(1)Java虛擬機的堆內存設置不夠,可以通過參數-Xms、Xmx來調整。
(2)代碼中創建了大量大對象,並且長時間不能被垃圾收集器收集(存在被引用)
Java堆從GC的角度還可以細分為:新生代(Eden區、From Survivor區和To Survivor區)和老年代。
MinorGC的過程(復制->清空->互換),其中,Eden:From:To = 8:1:1
1:eden、SurvivorFrom復制到survivorTo,年齡+1
首先,把Eden和SurvivorFrom區域中存活的對象復制到SurvivorTo區域(如果有對象的年齡以及達到了老年的標准,則賦值到老年代區),同時把這些對象的年齡+1(如果SurvivorTo不夠位置了就放到老年區)
2:清空eden、SurvivorFrom
然后,清空Eden和SurvivorFrom中的對象
3:SurvivorTo和SurvivorFrom互換
最后,SurvivorTo和SurvivorFrom互換,原SurvivorTo成為下一次GC時SurvivorFrom區
實際而言,方法區(Method Area)和堆一樣,是各個線程共享的內容區域,它用於存儲虛擬機加載的:類信息+普通變量+靜態變量+編譯器編譯后的代碼等等,雖然JVM規范將方法區描述為堆的一個邏輯部分,但它卻還有一個別名叫做Non-Heap(非堆),目的就是要和堆分開。
對於HotSpot虛擬機,很多開發者習慣將方法區稱之為”永久代(Parmaneng Gen)”,但嚴格本質上說兩者不同,或者說使用永久代來實現方法區而已,永久代是方法區(相當於是一個接口interface)的一個實現,jdk1.7的版本中,已經將原來放在永久代的字符串常量池移走。
java7的heap如下圖所示:
Java1.8之后將最初的永久代取消了,由元空間取代。
在Java8中,永久代已經被移除了。被一個稱為元空間的區域所取代。元空間的本質和永久代類似。
元空間與永久代之間最大的區別在於:
元空間並不在虛擬機中而是使用本地內存。因此,默認情況下,元空間的大小僅受本地內存限制。類的元數據當如native memeory,字符串池和類的靜態變量放入java堆中,這樣可以加載多少類的元數據就不在由MaxPermSize控制,而由系統的實際可用空間來控制。
堆內存空間調整參數
參數名稱 | 描述 |
-Xms | 設置初始分配大小,默認為物理內存的1/64 |
-Xmx | 最大分配內存,默認為物理內存的1/4 |
-XX:+PrintGCDetails | 輸出詳細的GC處理日志 |
-XX:+PrintGCTimeStamps | 輸出GC的時間戳信息 |
-XX:+PrintGCDateStamps | 輸出GC的時間戳信息(以日期的形式,如2019-09-15T16:24:24.155+0800) |
-XX:+PrintHeapAtGC | 在GC進行處理的前后打印堆內存信息 |
-Xloggc:保存路徑 | 設置日志信息保存文件 |
1 public class Test { 2 public static void main(String[] args) { 3 long maxMemory = Runtime.getRuntime().maxMemory(); 4 long total = Runtime.getRuntime().totalMemory(); 5 System.out.println("maxmemeory :"+maxMemory+"字節"+(maxMemory/(double)1024/1024)+"MB"); 6 System.out.println("total :"+total+"字節"+(total/(double)1024/1024)+"MB"); 7 8 } 9 10 }
maxmemeory :1873805312字節1787.0MB
total :126877696字節121.0MB
我的電腦物理內存是8G,會發現默認初始內存是1/64,即大約121.0M;默認最大分配內存時1/4,即大約1787.0M
配置VM options:-Xms2G -Xmx2G -XX:+PrintGCDetails后,會輸出jvm運行的詳細信息,如下:
maxmemeory :2058354688字節1963.0MB
total :2058354688字節1963.0MB
Heap
PSYoungGen total 611840K, used 41984K [0x00000000d5580000, 0x0000000100000000, 0x0000000100000000)
eden space 524800K, 8% used [0x00000000d5580000,0x00000000d7e801b8,0x00000000f5600000)
from space 87040K, 0% used [0x00000000fab00000,0x00000000fab00000,0x0000000100000000)
to space 87040K, 0% used [0x00000000f5600000,0x00000000f5600000,0x00000000fab00000)
ParOldGen total 1398272K, used 0K [0x0000000080000000, 0x00000000d5580000, 0x00000000d5580000)
object space 1398272K, 0% used [0x0000000080000000,0x0000000080000000,0x00000000d5580000)
Metaspace used 3238K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 350K, capacity 388K, committed 512K, reserved 1048576K
配置GC -Xms2G -Xmx2G -XX:+PrintGCDetails 使其強制產生OOM
1 public class Test { 2 public static void main(String[] args) { 3 Random random = new Random(); 4 String string = "0000000000"; 5 while (true) 6 { 7 string += string + random.nextInt(8888888)+random.nextInt(8888888); 8 string.intern(); //強制產生垃圾 9 } 10 } 11 }
[GC (Allocation Failure) [PSYoungGen: 504215K->49540K(611840K)] 504215K->145290K(2010112K), 0.0508276 secs] [Times: user=0.27 sys=0.08, real=0.05 secs]
[GC (Allocation Failure) [PSYoungGen: 538420K->1235K(611840K)] 634170K->384221K(2010112K), 0.1519967 secs] [Times: user=0.42 sys=0.28, real=0.15 secs]
[GC (Allocation Failure) [PSYoungGen: 408791K->1171K(611840K)] 1174746K->958620K(2010112K), 0.1376924 secs] [Times: user=0.73 sys=0.05, real=0.14 secs]
[Full GC (Ergonomics) [PSYoungGen: 192656K->0K(611840K)] [ParOldGen: 1340418K->575542K(1398272K)] 1533074K->575542K(2010112K), [Metaspace: 3318K->3318K(1056768K)], 0.1610330 secs] [Times: user=0.76 sys=0.05, real=0.16 secs]
[Full GC (Ergonomics) [PSYoungGen: 392390K->0K(611840K)] [ParOldGen: 1341482K->384057K(1398272K)] 1733872K->384057K(2010112K), [Metaspace: 3318K->3318K(1056768K)], 0.1103824 secs] [Times: user=0.61 sys=0.00, real=0.11 secs]
[Full GC (Ergonomics) [PSYoungGen: 382969K->0K(611840K)] [ParOldGen: 1149997K->1149997K(1398272K)] 1532967K->1149997K(2010112K), [Metaspace: 3318K->3318K(1056768K)], 0.5124315 secs] [Times: user=1.94 sys=0.05, real=0.51 secs]
[GC (Allocation Failure) [PSYoungGen: 0K->0K(611840K)] 1149997K->1149997K(2010112K), 0.0051481 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
[Full GC (Allocation Failure) [PSYoungGen: 0K->0K(611840K)] [ParOldGen: 1149997K->1149973K(1398272K)] 1149997K->1149973K(2010112K), [Metaspace: 3318K->3318K(1056768K)], 0.5310038 secs] [Times: user=2.56 sys=0.08, real=0.53 secs]
Heap
PSYoungGen total 611840K, used 15045K [0x00000000d5580000, 0x0000000100000000, 0x0000000100000000)
eden space 524800K, 2% used [0x00000000d5580000,0x00000000d64315f0,0x00000000f5600000)
from space 87040K, 0% used [0x00000000fab00000,0x00000000fab00000,0x0000000100000000)
to space 87040K, 0% used [0x00000000f5600000,0x00000000f5600000,0x00000000fab00000)
ParOldGen total 1398272K, used 1149973K [0x0000000080000000, 0x00000000d5580000, 0x00000000d5580000)
object space 1398272K, 82% used [0x0000000080000000,0x00000000c6305700,0x00000000d5580000)
Metaspace used 3350K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 360K, capacity 388K, committed 512K, reserved 1048576K
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
另外,如果不會調整內存的話,可以將Xmx和Xms調成一樣的大小既可。