天天看點

JVM - 運作時資料區 - 方法區

1. 棧,堆,方法區的互動關系

JVM - 運作時資料區 - 方法區

2. 方法區的基本了解

方法區是線程共享的記憶體區域,在Java規範中把方法區作為堆的一個邏輯部分,方法區的大小決定了系統可以儲存多少類,如果加載了大量類可能會導緻

OOM

方法區用來存儲已經被虛拟機加載的類資訊,常量,靜态變量,編譯器編譯後的代碼緩存等資料

2.1 方法區會存在記憶體溢出的問題

方法區的大小決定了系統可以儲存多少個類,如果系統定義了太多的類,導緻方法區溢出,虛拟機同樣會抛出記憶體溢出錯誤:

java.lang.OutOfMemoryError:PermGen space

或者

java.lang,OutOfMemoryError:Metaspace

,比如:

  • 加載大量的第三方jar包;
  • Tomcat部署的工程過多;
  • 大量動态生成反射類;

2.2 記憶體結構詳解

方法區用來存儲已經被虛拟機加載的類資訊,常量,靜态變量,編譯器編譯後的代碼緩存等資料

類型資訊

對每個加載的類型( 類class、接口interface、枚舉enum、注解annotation),JVM必須在方法區中存儲以下類型資訊:

① 這個類型的完整有效名稱(全名=包名.類名)

② 這個類型直接父類的完整有效名(對于interface或是java. lang.Object,都沒有父類)

③ 這個類型的修飾符(public, abstract, final的某個子集)

④ 這個類型直接接口的一個有序清單

域資訊(成員變量)

  • JVM必須在方法區中儲存類型的所有域的相關資訊以及域的聲明順序。
  • 域的相關資訊包括:域名稱、 域類型、域修飾符(public, private, protected, static, final, volatile, transient的某個子集)

方法資訊

JVM必須儲存所有方法的以下資訊,同域資訊一樣包括聲明順序:

  • 方法名稱。
  • 方法的傳回類型(或void)。
  • 方法參數的數量和類型(按順序)。
  • 方法的修飾符(public, private, protected, static, final, synchronized, native , abstract的一個子集)。
  • 方法的位元組碼(bytecodes)、操作數棧、局部變量表及大小( abstract和native 方法除外)。
  • 異常表( abstract和native方法除外),每個異常處理的開始位置、結束位置、代碼處理在程式計數器中的偏移位址、被捕獲的異常類的常量池索引

關于靜态變量

none - final

的類變量

靜态變量和類關聯在一起,随着類的加載而加載,他們成為類資料在邏輯上的一部分;類變量被類的所有執行個體共享,即使沒有類執行個體時也可以通路

public class MethodAreaTest{
	public static void main(String[] args){
		Order order = null;
		order.hello();//并不會有空指針的異常 可以通路到
		System.out.println(order.count);
	}	
}
class Order{
	public static int count = 1;
	public static void hello(){
		System.out.println("hello");
	}
}
           

final

全局常量

被聲明為

fianl

的類變量的處理方法則不同,每個全局常量在編譯的時候就被配置設定了,看下面的代碼

public class TestClass3 {
    public static int count = 1;
    public static final int number = 2;
}

           

反編譯的部分結果如下

JVM - 運作時資料區 - 方法區

可以看到常量在類被編譯成位元組碼的時候就被指派了,而非

final

的類變量沒有,是在類加載的過程才進行初始化

運作時常量池

Runtime Constant Pool

運作時常量池是方法區的一部分,

Class

檔案中除了包括版本,字段,方法,接口等字段以外,還有一項資訊就是常量池(Constant Pool Table)用于存放編譯期生成的各種字面量和符号引用,這部分内容在類加載進入方法區的運作時常量池中存放(注意區分常量池和運作時常量池)

JVM為每個已加載的類型(類或接口)都維護一個常量池

  • 常量池:放在二進制位元組碼檔案,就是一張表,之後類的方法定義中的虛拟機指令根據這張表找到要執行的類名,方法名,參數類型,字面量等資訊
  • 運作時常量池:當類被加載(加載到記憶體),他的常量池資訊就會被放入運作是常量池,并把裡面的符号位址變為真實位址

一個 java 源檔案中的類、接口,編譯後産生一個位元組碼檔案。而 Java 中的位元組碼需要資料支援,通常這種資料會很大以至于不能直接存到位元組碼裡,換另一種方式,可以存到常量池;而這個位元組碼包含了指向常量池的引用。在動态連結的時候會用到運作時常量池.

相對應

Class

檔案的常量池來說,運作時常量池的一個重要特性就是動态性,Java語言并不要求常量一定是在編譯期間産生的,也就是并非預先置入

Class

檔案中的常量池的内容才能進入方法區運作時常量池,運作期間也能将新的常量放入池中,比如

String

intern

方法.

串池

StringTable

StringTable

是運作時常量池的一部分,是

String

的享元模式的實作方式

常量池中的資訊,都會被加載到運作時常量池中,但還沒有變為java對象,當使用到時,會生成對象(延遲執行個體化);對于字元串來說,我們要在一個java程式中要使用要大量的字元串對象,而很多字元串對象的值都一樣我們也把他們當作常量來使用,如果每次都分别建立字元串對象那麼完全沒有必要,是以我們要利用之前已經建立的字元串對象

以字面量直接指派的

String

對象會被放到串池,下次如果有相同的字元串直接去串池中拿就行了,而不用生成新的對象

2.2 記憶體結構演進

jdk1.6

的記憶體結構中,使用永久代來實作方法區(對于Hotspots),靜态變量放在永久代上

本質上,方法區和永久代并不等價。僅是對hotSpot而言的。《java虛拟機規範》對如何實作方法區,不做統一要求。例如:BEA JRockit/IBM J9中不存在永久代的概念

在jdk1.7時還有永久代,但已經逐漸“去永久代”,字元串常量池、靜态變量移除,儲存在堆中;

在1.8方法區不再由

JVM

管理而是放在了元空間中,元空間放到本地記憶體中,類型資訊、字段、方法、常量儲存在本地記憶體的元空間,但字元串常量池、靜态變量仍留在堆空間

版本 改進
jdk1.6及以前 有永久代,靜态變量存放在永久代上
jdk1.7 有永久代,但已經逐漸去永久代,字元串常量池,靜态變量被移除,儲存在堆中
jdk1.8及以後 無永久代,類型資訊,字段,方法資訊,常量儲存在本地記憶體的元空間,但字元串常量池,靜态變量仍在堆中
JVM - 運作時資料區 - 方法區
JVM - 運作時資料區 - 方法區
JVM - 運作時資料區 - 方法區

做這樣元空間替換永久代的動機有兩點:

元空間和永久代的差別

① 為永久代設定空間大小是很難确定的;

永久代是一片連續的堆空間,在JVM啟動之前通過在指令行設定參數-XX:MaxPermSize來設定永久代最大可配置設定的記憶體空間,預設大小是64M。永久代的垃圾收集是和老年代(old generation)捆綁在一起的,是以無論誰滿了,都會觸發永久代和老年代的垃圾收集。不過,一個明顯的問題是,當JVM加載的類資訊容量超過了參數-XX:MaxPermSize設定的值時,應用将會報OOM的錯誤

在某些場景下,如果動态加載類過多,會導緻

Perm

區的

OOM

,而元空間和永久代的差別就在于:元空間并不限制在虛拟機,而是使用本地記憶體,是以元空間的大小僅受本地記憶體的限制

② 對永久代的調優是很困難的:垃圾回收的判斷麻煩

類的中繼資料資訊轉移到Metaspace的原因是PermGen很難調整。PermGen中類的中繼資料資訊在每次FullGC的時候可能會被收集,但成績很難令人滿意。而且應該為PermGen配置設定多大的空間很難确定,因為PermSize的大小依賴于很多因素,比如JVM加載的class的總數,常量池的大小,方法的大小等。

此外,在HotSpot中的每個垃圾收集器需要專門的代碼來處理存儲在PermGen中的類的中繼資料資訊。從PermGen分離類的中繼資料資訊到Metaspace,由于Metaspace的配置設定具有和Java Heap相同的位址空間,是以Metaspace和Java Heap可以無縫的管理,而且簡化了FullGC的過程,以至将來可以并行的對中繼資料資訊進行垃圾收集,而沒有GC暫停

上面說到在jdk 1.6,串池

StringTable

被放到了永久代中,而在jdk1.8則是放到了堆中,為什麼要做這個改變呢?

這個和

JVM

的垃圾回收機制有關,對于永久代來說,他的垃圾回收是發生在

full GC

,垃圾回收的幾率很小,而堆的垃圾回收幾率更多,為了盡快回收大量不用的

String

對象,才做出這個改變

3. 方法區的大小設定

jdk7及以前: 永久代
-XX:PermSize來設定永久代初始配置設定空間 預設值是20.75M
-XX:MaxPermSize來設定永久代的最大可配置設定空間 32位虛拟機預設是64M 64位虛拟機是82M

jdk8及以後: 元空間
-XX:MetaSpaceSize來設定永久代初始配置設定空間 預設值是20.75M
-XX:MaxMetaSpaceSize來設定永久代的最大可配置設定空間 32位虛拟機預設是64M 64位虛拟機是82M
           

繼續閱讀