日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

java方法区超详细汇总,方法区到底是干什么用的?不懂方法区不能说了解jvm!

發(fā)布時間:2025/3/19 编程问答 25 豆豆
生活随笔 收集整理的這篇文章主要介紹了 java方法区超详细汇总,方法区到底是干什么用的?不懂方法区不能说了解jvm! 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

目錄

一、運行時數(shù)據(jù)區(qū)結構圖

二、棧、堆、方法區(qū)的交互關系

三、方法區(qū)的理解

官方文檔

方法區(qū)在哪里

方法區(qū)的基本理解

HotSpot中方法區(qū)的演進

四、設置方法區(qū)大小與OOM

設置方法區(qū)內存的大小

代碼舉例1

代碼舉例2

如何解決這種OOM?

五、方法區(qū)的內部結構

方法區(qū)(Method?Area)存儲什么?

方法區(qū)的內部結構

類型信息:

域(Field)(屬性)信息:

方法(Method)信息:

non-final的類變量

全局常量(static+final)

六、運行時常量池vs常量池

常量池是什么?

為什么需要常量池??

在常量池內存儲的數(shù)據(jù)類型包括

常量池小結

運行時常量池

七、方法區(qū)使用舉例

執(zhí)行順序:

八、方法區(qū)演進細節(jié)

永久代為什么要被元空間替換?

StringTable(字符串常量池)為什么要調整?

靜態(tài)變量放在哪里?

九、方法區(qū)的垃圾回收

十、總結

常見面試題


一、運行時數(shù)據(jù)區(qū)結構圖

二、棧、堆、方法區(qū)的交互關系

從線程共享與否的角度來看:

代碼角度:

從棧堆方法區(qū)內存結構來看:

三、方法區(qū)的理解

官方文檔

方法區(qū)在哪里

? ? 《Java虛擬機規(guī)范》中明確說明:“盡管所有的方法區(qū)在邏輯上是屬于堆的一部分,但一些簡單的實現(xiàn)可能不會選擇去進行垃圾收集或者進行壓縮。”但對于HotSpotJVM而言,方法區(qū)還有一個別名叫做Non-Heap(非堆),目的就是要和堆分開。

? ? 所以,方法區(qū)看做是一塊獨立于Java堆的內存空間

方法區(qū)的基本理解

1.方法區(qū)(Method?Area)與Java堆一樣,是各個線程共享的內存區(qū)域。

2.方法區(qū)在JVM啟動的時候被創(chuàng)建,并且它的實際的物理內存空間中和Java堆區(qū)一樣都可以是不連續(xù)的。

3.方法區(qū)的大小,跟堆空間一樣,可以選擇固定大小或者可擴展。

4.方法區(qū)的大小決定了系統(tǒng)可以保存多少個類,如果系統(tǒng)定義了太多的類(加載大量第三方jar包、timcat部署的工程過多、大量動態(tài)生成反射類),導致方法區(qū)溢出,虛擬機同樣會拋出內存溢出錯誤:java.lang.OutOfMemoryError: PermGen space(jdk7以前)或者java.lang.OutOfMemoryError: Metaspace(jdk8以后)

5.關閉JVM就會釋放這個區(qū)域的內存。

HotSpot中方法區(qū)的演進

1.在jdk7以前,習慣上把方法區(qū),成為永久代。jdk8開始,使用元空間取代了永久代。

2.本質上,方法區(qū)和永久代并不等價。僅是對HotSpot而言的。《Java虛擬機規(guī)范》對如何實現(xiàn)方法區(qū),不做統(tǒng)一要求。例如:BEA?JRockit/IBM J9中不存在永久代的概念。

? ? 現(xiàn)在來看,當年使用永久代,不是好的idea。導致java程序更容易OOM(超過-XX:MaxPermSize上限)

3.而到了JDK8,終于完全廢棄了永久代的概念,改用與JRockit、J9一樣在本地內存中實現(xiàn)的元空間(Metaspace)來代替。

4.元空間的本質和永久代類似,都是對JVM規(guī)范中方法區(qū)的實現(xiàn)。不過元空間與永久代最大的區(qū)別在于:元空間不再虛擬機設置的內存中,而是使用本地內存。

5.永久代、元空間二者并不只是名字變了,內部結構也調整了。

6.根據(jù)《Java虛擬機規(guī)范》的規(guī)定,如果方法區(qū)無法滿足新的內存分配需求時,將拋出OOM異常。

四、設置方法區(qū)大小與OOM

設置方法區(qū)內存的大小

? ? 方法區(qū)的大小不必是固定的,jvm可以根據(jù)應用的需要動態(tài)調整。

jdk7及以前:

? ? 通過-XX:PermSize來設置永久代初始分配空間。默認值是20.75M

? ? -XX:MaxPermSize來設定永久代最大可分配空間。32位機器默認是64M,64位機器默認是82M

? ? 當JVM加載的類信息容量超過了這個值,會報異常OutOfMemoryError:PermGen space。

jdk8及以后

? ? 元數(shù)據(jù)區(qū)大小可以使用參數(shù)-XX:MetaspaceSize和-XX:MaxMetaspaceSize指定,替代上述原有的兩個參數(shù)。

? ? 默認值依賴于平臺。windows下,-XX:MetaspaceSize是21M,-XX:MaxMetaspaceSize的值是-1,即沒有限制

? ? 與永久代不同,如果不指定大小,默認情況下,虛擬機會耗盡所有的可用系統(tǒng)內存。如果元數(shù)據(jù)區(qū)發(fā)生溢出,虛擬機一樣會拋出異常OutOfMemoryError:Metaspace。

?? ?-XX:MetaspaceSize:設置初始的元空間大小。對于一個64位的服務器端JVM來說,其默認的-XX:MetaspaceSize值為21MB。這就是初始的高水位線,一旦觸及這個水位線,Full?GC將會被觸發(fā)并卸載沒用的類(即這些類對應的類加載器不再存活),然后這個高水位線將會重置。新的高水位線的值取決于GC后釋放了多少元空間。如果釋放的空間不足,那么在不超過MaxMetaspaceSize時,適當提高該值。如果釋放空間過多,則適當降低該值。

? ? 如果初始化的高水位線設置過低,上述高水位線調整情況會發(fā)生很多次。通過垃圾回收器的日志可以觀察到Full?GC多次調用。為了避免頻繁地GC,建議將-XX:MetaspaceSize設置為一個相對較高的值。

代碼舉例1

代碼舉例2

如何解決這種OOM?

1.要解決OOM異常或heap?space的異常,一般的手段是首先通過內存映像分析工具(如Eclipse?Memory?Analyzer)對dump出來的堆轉儲快照進行分析,重點是確認內存中的對象是否是必要的,也就是要先分清楚到底是出現(xiàn)了內存泄漏(Memory?Leak)還是內存溢出(Memory?Overflow)。

2.如果是內存泄漏,可進一步通過工具查看泄漏對象到GC?Roots的引用鏈。于是就能找到泄漏對象是通過怎樣的路徑與GC?Roots相關聯(lián)并導致垃圾收集器無法自動回收它們的。掌握了泄漏對象的類型信息,以及GC?Roots引用鏈的信息,就可以比較準確地定位出泄漏代碼的位置。

3.如果不存在內存泄漏,換句話說就是內存中的對象卻是都還必須存活著,那就應當檢查虛擬機的堆參數(shù)(-Xmx與-Xms),與機器物理內存對比看是否還可以調大,從代碼上檢查是否存在某些對象生命周期過長、持有狀態(tài)時間過長的情況,嘗試減少程序運行期的內存消耗。

五、方法區(qū)的內部結構

方法區(qū)(Method?Area)存儲什么?

? ? 《深入理解Java虛擬機》書中對方法區(qū)(Method?Aera)存儲內容描述如下:它用于存儲已被虛擬機加載的類型信息、常量、靜態(tài)變量、即時編譯器編譯后的代碼緩存等。

方法區(qū)的內部結構

類型信息:

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

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

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

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

4.這個類型直接接口的一個有序列表

域(Field)(屬性)信息:

1.JVM必須在方法區(qū)中保存類型的所有域的相關信息以及域的聲明順序。

2.域的相關信息包括:域名稱、域類型、域修飾符(public,private,protected,static,final,volatile,transient的某個子集)

方法(Method)信息:

JVM必須保存所有方法的以下信息,同域信息一樣包括聲明順序:

1.方法名稱

2.方法的返回類型(或void)

3.方法參數(shù)的數(shù)量和類型(按順序)

4.方法的修飾符(public,private,protected,static,final,synchronized,native,abstract的某個子集)

5.方法的字節(jié)碼(bytecodes)、操作數(shù)棧、局部變量表及大小(?? ?abstract和native方法除外)

6.異常表(abstract和native方法除外)

? ? 每個異常處理的開始位置、結束位置、代碼處理在程序計數(shù)器中的偏移地址、被捕獲的異常類的常量池索引。

non-final的類變量

1.靜態(tài)變量和類關聯(lián)在一起,隨著類的加載而加載,它們成為類數(shù)據(jù)在邏輯上的一部分。

2.類變量被類的所有實例共享,即使沒有類實例時你也可以訪問它。

全局常量(static+final)

被聲明為final的類變量的處理方法則不同,每個全局常量在編譯的時候就會被分配了。

六、運行時常量池vs常量池

? ? 方法區(qū),內部包含了運行時常量池。

? ? 字節(jié)碼文件,內部包含了常量池。

? ? 要弄清楚方法區(qū),需要理解清楚ClassFile,因為加載類的信息都在方法區(qū)。

? ? 要弄清楚方法區(qū)的運行時常量池,需要理解清楚ClassFile中?的常量池。

官方文檔:

常量池是什么?

一個有效地字節(jié)碼文件中除了包含類的版本信息、字段、方法以及接口等描述信息外,還包含一項信息那就是常量池表(Constant?Pool?Table),包括各種字面量和對類型、域、和方法的符號引用

為什么需要常量池??

? ? 一個java源文件中的類、接口,編譯后產生一個字節(jié)碼文件。而java中的字節(jié)碼需要數(shù)據(jù)支持,通常這種數(shù)據(jù)會很大以至于不能直接存到字節(jié)碼里,換另一種方式,可以存到常量池,這個字節(jié)碼包含了指向常量池的引用。在動態(tài)鏈接的時候會用到運行時常量池。

? ? 比如,如下代碼:

? ? 雖然很小,但是里面卻使用了String、System、PrintStream及Object等結構。這里代碼量其實已經(jīng)很小了。如果代碼多,引用到的結構會更多!這里就需要常量池了!

在常量池內存儲的數(shù)據(jù)類型包括

1.數(shù)量值

2.字符串值

3.類引用

4.字段引用

5.方法引用

例如下面這段代碼:

將會被編譯成如下字節(jié)碼,其中后面的#2、#3,就指向了class文件常量池:

類似這種:

常量池小結

? ? 常量池,可以看做是一張表,虛擬機指令根據(jù)這張常量表找到要執(zhí)行的類名、方法名、參數(shù)類型、字面量等類型

運行時常量池

1.運行時常量池(Runtime?Constant?Pool)是方法區(qū)的一部分。

2.常量池表(Constant?Pool?Table)是Class文件的一部分,用于存放編譯器生成的各種字面量與符號引用,這部分內容將在類加載后存放到方法區(qū)的運行時常量池中

3.運行時常量池,在加載類和接口到虛擬機后,就會創(chuàng)建對應的運行時常量池。

4.JVM為每個已加載的類型(類或接口)都維護一個常量池。池中的數(shù)據(jù)項像數(shù)組項一樣,是通過索引訪問的(類似于用#xx來指向常量池中數(shù)據(jù))。

5.運行時常量池中包含多種不同的常量,包括編譯期就已經(jīng)明確的數(shù)值字面量,也包括到運行期解析后才能獲得的方法或者字段引用。此時不再是常量池中的符號地址了,這里換為真實地址。

? ? 運行時常量池,相對于Class文件常量池的另一重要特征是:具備動態(tài)性。

?? ??? ?關于字符串String中的intern方法,是當前的字符對象(通過new出來的對象)可以使用intern方法從常量池中獲取,

?? ??? ?如果常量池中不存在該字符串,那么就新建一個這樣的字符串放到常量池中。

   使用常量池的方法一個是通過雙引號定義字符串例如:String S = “1”;還有就是上面的intern方法。

6.運行時常量池類似于傳統(tǒng)編程語言中的符號表(symbol?table),但是它所包含的數(shù)據(jù)卻比符號表要更加豐富一些。

7.當創(chuàng)建類或接口的運行時常量池時,如果構造運行時常量池所需的內存空間超過了方法區(qū)所能提供的最大值,則JVM會拋OutOfMemoryError異常。

七、方法區(qū)使用舉例

javap -v -p?xxx.class,查看字節(jié)碼文件

執(zhí)行順序:

八、方法區(qū)演進細節(jié)

1.首先明確:只有HotSpot才有永久代。BEA?JRockit、IBM?J9等來說,是不存在永久代的概念的。原則上如何實現(xiàn)方法區(qū)屬于虛擬機實現(xiàn)細節(jié),不受《Java虛擬機規(guī)范》管束,并不要求統(tǒng)一。

2.HotSpot中方法區(qū)的變化:

jdk1.6及以前

有永久代(permanent?generation),靜態(tài)變量存放在永久代上

jdk1.7

有永久代,但已經(jīng)逐步“去永久代”,字符串常量池、靜態(tài)變量移除,保存在堆中

jdk1.8及以后

無永久代,類型信息、字段、方法、常量保存在本地內存的元空間,但字符串常量池、靜態(tài)變量仍在堆

永久代為什么要被元空間替換?

1.隨著Java8的到來,HotSpot?VM中再也見不到永久代了。但是這并不意味著類的元數(shù)據(jù)信息也消失了。這些數(shù)據(jù)被移到了一個與堆不相連的本地內存區(qū)域,這個區(qū)域叫做元空間(Metaspace)。

2.由于類的元數(shù)據(jù)分配在本地內存中,元空間的最大可分配空間就是系統(tǒng)可用內存空間。

3.這項改動是很有必要的,原因有:

?? ?(1)為永久代設置空間大小是很難確定的

? ? 在某些場景下,如果動態(tài)加載類過多,容易產生Perm區(qū)的OOM。比如某個實際web工程中,因為功能點比較多,在運行過程中,要不斷動態(tài)加載很多類,經(jīng)常出現(xiàn)致命錯誤。

? ? 而元空間和永久代之間最大的區(qū)別在于:元空間并不在虛擬機中,而是使用本地內存。因此,默認情況下,元空間的大小僅受本地內存限制。

? ? (2)對永久代進行調優(yōu)是很困難的。

StringTable(字符串常量池)為什么要調整

? ? jdk7中將StringTable放到了堆空間中。因為永久代的回收效率很低,在full?gc的時候才會觸發(fā)。而full?gc是老年代的空間不足、永久代不足時才會觸發(fā)。這就導致StringTable回收效率不高。而我們開發(fā)中會有大量的字符串被創(chuàng)建,回收效率低,導致永久代內存不足。放到堆里,能及時回收內存。

靜態(tài)變量放在哪里?

測試一:

結論:靜態(tài)引用,對應的對象實體(new的東西)始終都存在堆空間中(不管jdk幾)。

測試二:

staticObj靜態(tài)變量隨著Test的類型信息存放在方法區(qū),instanceObj成員變量隨著Test的對象實例存放在Java堆,localObject局部變量則是存放在foo()方法棧幀的局部變量表中。

測試發(fā)現(xiàn):三個對象的數(shù)據(jù)在內存中的地址都落在Eden區(qū)范圍內,所以結論:只要是對象實例必然會在Java堆中分配。

接著,找到了一個引用該staticObj對象的地方,是在一個java.lang.Class的實例里,并且給出了這個實例的地址,通過Inspector查看該對象實例,可以清楚看到這確實是一個java.lang.Class類型的對象實例,里面有一個名為staticObj的實例字段:

? ? 從《Java虛擬機規(guī)范》所定義的概念模型來看,所有Class相關的信息都應該存放在方法區(qū)之中,但方法區(qū)該如何實現(xiàn),《Java虛擬機規(guī)范》并未作出規(guī)定,這就成了一件允許不同虛擬機自己靈活把握的事情。JDK7及其以后版本的HotSpot虛擬機選擇把靜態(tài)變量與類型在Java語言一端的映射Class對象存放在一起,存儲于Java堆之中,從我們的實驗中也明確驗證了這一點

九、方法區(qū)的垃圾回收

? ? 有些人認為方法區(qū)(如HotSpot虛擬機中的元空間或者永久代)是沒有垃圾收集行為的,其實不然。《Java虛擬機規(guī)范》對方法區(qū)的約束是非常寬松的,提到過可以不要求虛擬機在方法區(qū)中實現(xiàn)垃圾收集。事實上也確實有未實現(xiàn)或未能完整實現(xiàn)方法區(qū)類型卸載的收集器存在(如JDK11時期的ZGC收集器就不支持類卸載)。

? ? 一般來說這個區(qū)域的回收效果比較難令人滿意,尤其是類型的卸載,條件相當苛刻。但是這部分區(qū)域的回收有時又確實是必要的。以前Sun公司的Bug列表中,曾出現(xiàn)過的若干個嚴重的Bug就是由于低版本的HotSpot虛擬機對此區(qū)域未完全回收而導致內存泄漏。

? ? 方法區(qū)的垃圾收集主要回收兩部分內容:常量池中廢棄的常量和不再使用的類型

1.先來說說方法區(qū)內常量池之中主要存放的兩大類常量:字面量和符號引用。字面量比較接近Java語言層次的常量概念,如文本字符串、被聲明為final的常量值等。而符號引用則屬于編譯原理方面的概念,包括下面三類常量: (1)類和接口的全限定名 (2)字段的名稱和描述符 (3)方法的名稱和描述符 2.HotSpot虛擬機對常量池的回收策略是很明確的,只要常量池中的常量沒有被任何地方引用,就可以被回收。 3.回收廢棄常量與回收Java堆中的對象非常類似。 4.判斷一個常量是否“廢棄”還是相對簡單,而要判定一個類型是否屬于“不再被使用的類”的條件就比較苛刻了。需要同時滿足下面三個條件: (1)該類所有的實例都已經(jīng)被回收,也就是Java堆中不存在該類及其任何派生子類的實例。 (2)加載該類的類加載器已經(jīng)被回收,這個條件除非是經(jīng)過精心設計的可替換類加載器的場景,如OSGi、JSP的重加載等,否則通常是很難達成的。 (3)該類對應的java.lang.Class對象沒有在任何地方被引用,無法在任何地方通過反射訪問該類的方法。 5.Java虛擬機被允許對滿足上述三個條件的無用類進行回收,這里說的僅僅是“被允許”,而并不是和對象一樣,沒有引用了就必然會回收。關于是否要對類型進行回收,HotSpot虛擬機提供了-Xnoclassgc參數(shù)進行控制,還可以使用-verbose:class以及-XX:+TraceClass-Loading、-XX:TraceClassUnLoading查看類加載和卸載信息。 6.在大量使用反射、動態(tài)代理、CGLib等字節(jié)碼框架,動態(tài)生成JSP以及OSGi這類頻繁自定義類加載器的場景中,通常都需要Java虛擬機具備類型卸載的能力,以保證不會對方法區(qū)造成過大的內存壓力。

十、總結

常見面試題

1.說一下JVM內存模型吧,有哪些區(qū)?分別干什么的?

2.Java8的內存分代改進

3.JVM內存分布/內存結構?棧和堆的區(qū)別?堆的結構?為什么兩個survivor區(qū)?

4.Eden和Survivor的比例分配

5.jvm內存分區(qū),為什么要有新生代和老年代

6.講講jvm運行時數(shù)據(jù)庫區(qū)

7.什么時候對象會進入老年代?

8.JVM內存為什么要分新生代,老年代,永久代。新生代為什么要分Eden和Survivor。

9.jvm的永久代中會發(fā)生垃圾回收嗎

總結

以上是生活随笔為你收集整理的java方法区超详细汇总,方法区到底是干什么用的?不懂方法区不能说了解jvm!的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內容還不錯,歡迎將生活随笔推薦給好友。