方法區究竟是個什麼鬼

1、方法區與永久代

這兩個是很是容易混淆的概念,永久代的對象放在方法區中,就會想固然地認爲,方法區就等同於持久代的內存區域。事實上二者是這樣的關係:html

《Java虛擬機規範》只是規定了有方法區這麼個概念和它的做用,並無規定如何去實現它。那麼,在不一樣的 JVM 上方法區的實現確定是不一樣的了。 同時大多數用的JVM都是Sun公司的HotSpot。在HotSpot上把GC分代收集擴展至方法區,或者說使用永久代來實現方法區。換句話說:方法區是一種規範,永久代是Hotspot針對這一規範的一種實現。而永久代自己也在迭代中:java

在Java 6中,方法區中包含的數據,除了JIT編譯生成的代碼存放在native memory的CodeCache區域,其餘都存放在永久代;
在Java 7中,Symbol的存儲從PermGen移動到了native memory,而且把靜態變量從instanceKlass末尾(位於PermGen內)移動到了java.lang.Class對象的末尾(位於普通Java heap內);
在Java 8中,永久代被完全移除,取而代之的是另外一塊與堆不相連的本地內存——元空間(Metaspace),‑XX:MaxPermSize 參數失去了意義,取而代之的是-XX:MaxMetaspaceSize。

對於Java8, HotSpots取消了永久代,那麼是否是也就沒有方法區了呢?固然不是,方法區是一個規範,規範沒變,它就一直在。那麼取代永久代的就是元空間。它與永久代有什麼不一樣的?數組

存儲位置不一樣,永久代是堆的一部分,和新生代,老年代地址是連續的,而元空間屬於本地內存;框架

存儲內容不一樣,元空間存儲類的元信息,靜態變量和常量池等併入堆中。至關於永久代的數據被分到了堆和元空間中。jvm

2、方法區裏存着什麼?

既然永久代是方法區的一種實現,那麼在Hotspot下,方法區就等於永久代,也被稱爲非堆。那方法區裏都存着什麼呢?先拋結論:性能

靜態變量 + 常量 + 類信息(構造方法/接口定義) + 運行時常量池存在方法區中 。spa

類信息與類常量池

方法區裏的class文件信息包括:魔數,版本號,常量池,類,父類和接口數組,字段,方法等信息,其實類裏面又包括字段和方法的信息。
在Class文件結構中,最頭的4個字節用於存儲魔數Magic Number,用於肯定一個文件是否能被JVM接受,再接着4個字節用於存儲版本號,前2個字節存儲次版本號,後2個存儲主版本號,再接着是用於存放常量的常量池,因爲常量的數量是不固定的,因此常量池的入口放置一個U2類型的數據(constant_pool_count)存儲常量池容量計數值,參見下圖。.net

clipboard.png

所以class文件信息和class文件常量池的關係以下圖:code

clipboard.png

圖中還包含一個運行時常量池,這個稍後會介紹。htm

class文件常量池中存儲了哪些內部呢?

咱們寫的每個Java類被編譯後,就會造成一份class文件;class文件中除了包含類的版本、字段、方法、接口等描述信息外,還有一項信息就是常量池(constant pool table),用於存放編譯器生成的各類字面量(Literal)和符號引用(Symbolic References);

每一個class文件都有一個class常量池。

動態常量池

運行時常量池是方法區的一部分,是一塊內存區域。Class 文件常量池將在類加載後進入方法區的運行時常量池中存放。一個類加載到 JVM 中後對應一個運行時常量池,運行時常量池相對於 Class 文件常量池來講具有動態性,Class 文件常量只是一個靜態存儲結構,裏面的引用都是符號引用。而運行時常量池能夠在運行期間將符號引用解析爲直接引用。能夠說運行時常量池就是用來索引和查找字段和方法名稱和描述符的。給定任意一個方法或字段的索引,經過這個索引最終可獲得該方法或字段所屬的類型信息和名稱及描述符信息,這涉及到方法的調用和字段獲取。

靜態常量池和動態常量池的關係以及區別

靜態常量池存儲的是當class文件被java虛擬機加載進來後存放在方法區的一些字面量和符號引用,字面量包括字符串,基本類型的常量,符號引用其實引用的就是常量池裏面的字符串,但符號引用不是直接存儲字符串,而是存儲字符串在常量池裏的索引。

動態常量池是當class文件被加載完成後,java虛擬機會將靜態常量池裏的內容轉移到動態常量池裏,在靜態常量池的符號引用有一部分是會被轉變爲直接引用的,好比說類的靜態方法或私有方法,實例構造方法,父類方法,這是由於這些方法不能被重寫其餘版本,因此能在加載的時候就能夠將符號引用轉變爲直接引用,而其餘的一些方法是在這個方法被第一次調用的時候纔會將符號引用轉變爲直接引用的。

本節總結

方法區裏存儲着class文件信息和動態常量池,class文件的信息包括類信息和靜態常量池。能夠將類的信息是對class文件內容的一個框架,裏面具體的內容經過常量池來存儲。

動態常量池裏的內容除了是靜態常量池裏的內容外,還將靜態常量池裏的符號引用轉變爲直接引用,並且動態常量池裏的內容是能動態添加的。例如調用String的intern方法就能將string的值添加到String常量池中,這裏String常量池是包含在動態常量池裏的,但在jdk1.8後,將String常量池放到了堆中。

3、jvm中的常量池

在Java的內存分配中,總共3種常量池:

字符串常量池(String Constant Pool):

字符串常量池在Java內存區域的哪一個位置?
在JDK6.0及以前版本,字符串常量池是放在Perm Gen區(也就是方法區)中;

在JDK7.0版本,字符串常量池被移到了堆中了。至於爲何移到堆內,大概是因爲方法區的內存空間過小了。

字符串常量池是什麼?

在HotSpot VM裏實現的string pool功能的是一個StringTable類,它是一個Hash表,默認值大小長度是1009;這個StringTable在每一個HotSpot VM的實例只有一份,被全部的類共享。字符串常量由一個一個字符組成,放在了StringTable上。

在JDK6.0中,StringTable的長度是固定的,長度就是1009,所以若是放入String Pool中的String很是多,就會形成hash衝突,致使鏈表過長,當調用String#intern()時會須要到鏈表上一個一個找,從而致使性能大幅度降低;

在JDK7.0中,StringTable的長度能夠經過參數指定:

-XX:StringTableSize=66666

字符串常量池裏放的是什麼?

在JDK6.0及以前版本中,String Pool裏放的都是字符串常量;

在JDK7.0中,因爲String#intern()發生了改變,所以String Pool中也能夠存放放於堆內的字符串對象的引用。關於String在內存中的存儲和String#intern()方法的說明,能夠參考個人另一篇博客:

須要說明的是:字符串常量池中的字符串只存在一份!

如:

String s1 = "hello,world!";
String s2 = "hello,world!";

即執行完第一行代碼後,常量池中已存在 「hello,world!」,那麼 s2不會在常量池中申請新的空間,而是直接把已存在的字符串內存地址返回給s2.

class常量池(Class Constant Pool):

咱們寫的每個Java類被編譯後,就會造成一份class文件;class文件中除了包含類的版本、字段、方法、接口等描述信息外,還有一項信息就是常量池(constant pool table),用於存放編譯器生成的各類字面量(Literal)和符號引用(Symbolic References);

每一個class文件都有一個class常量池。

運行時常量池(Runtime Constant Pool):

運行時常量池存在於內存中,也就是class常量池被加載到內存以後的版本,不一樣之處是:它的字面量能夠動態的添加(String#intern()),符號引用能夠被解析爲直接引用

JVM在執行某個類的時候,必須通過加載、鏈接、初始化,而鏈接又包括驗證、準備、解析三個階段。而當類加載到內存中後,jvm就會將class常量池中的內容存放到運行時常量池中,由此可知,運行時常量池也是每一個類都有一個。在解析階段,會把符號引用替換爲直接引用,解析的過程會去查詢字符串常量池,也就是咱們上面所說的StringTable,以保證運行時常量池所引用的字符串與字符串常量池中是一致的。

常量池的好處

常量池是爲了不頻繁的建立和銷燬對象而影響系統性能,其實現了對象的共享。

例如字符串常量池,在編譯階段就把全部的字符串文字放到一個常量池中。

(1)節省內存空間:常量池中全部相同的字符串常量被合併,只佔用一個空間。

(2)節省運行時間:比較字符串時,==比equals()快。對於兩個引用變量,只用==判斷引用是否相等,也就能夠判斷實際值是否相等。

參考文檔

https://www.zhihu.com/questio...

http://blog.csdn.net/vegetabl...

https://www.cnblogs.com/holos...

https://blog.csdn.net/vegetab...

https://blog.csdn.net/u011635...

相關文章
相關標籤/搜索