JVM_03 运行时数据区 [ 方法区]
一、方法區的概述
方法區在JVM啟動的時候被創建,并且它的實際的物理內存空間和Java堆區一樣都可以是不連續的 | 關閉Jvm就會釋放這個區域的內存
方法區時邏輯上是堆的一個組成部分,但是在不同虛擬機里頭實現是不一樣的,最典型的就是永久代(PermGen space)和元空間(Metaspace)
(注意:方法區時一種規范,而永久代和元空間是它的一種實現方式)
方法區的大小決定了系統可以保存多少個類,如果系統定義了太多的類,導致方法區溢出,虛擬機同樣會拋出內存溢出錯誤:(java.lang.OutOfMemoryError:PermGen space、java.lang.OutOfMemoryError:Metaspace) 掌握
二、方法區的內部結構
深入理解Java虛擬機》書中對方法區存儲內容描述如下:它用于存儲已被虛擬機加載的類型信息、常量、靜態變量、即時編譯器編譯后的代碼緩存等 掌握
類型信息(對每個加載的類型( 類class、接口interface、枚舉enum、注解annotation),JVM必 .須在方法區中存儲以下類型信息:)
(Order.class字節碼文件,右鍵Open in Teminal打開控制臺,使用javap -v -p Order.class > tst.txt 將字節碼文件反編譯并輸出為txt文件,可以看到被聲明為static final的常量number在編譯的時候就被賦值了,這不同于沒有被final修飾的static變量count是在類加載的準備階段被賦值為默認的初始化值,在初始化的時候賦予正確的初始化值
以下代碼不會報空指針異常
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 final int number = 2;public static void hello() {System.out.println("hello!");} }三、方法區的演進細節 面試常問
Jdk 1.6 及之前:有永久代,靜態變量、字符串常量池1.6在方法區
Jdk 1.7 :有永久代,但已經逐步 " 去永久代 ",字符串常量池、靜態變量移除,保存在堆中
jdk 1.8 及之后: 無永久代,常量池1.8在元空間。但靜態變量、字符串常量池仍在堆中
為什么要用元空間取代永久代 掌握
- 為永久代設置空間大小是很難確定的
-①.永久代參數設置過小,在某些場景下,如果動態加載的類過多,容易產生Perm區的OOM,比如某個實際Web工程中,因為功能點比較多,在運行過程中,要不斷動態加載很多類,經常出現致命錯誤
-②. 永久代參數設置過大,導致空間浪費
-③. 默認情況下,元空間的大小受本地內存限制) - 對永久代進行調優是很困難的
(方法區的垃圾收集主要回收兩部分:常量池中廢棄的常量和不再使用的類型,而不再使用的類或類的加載器回收比較復雜,full gc 的時間長)
- jdk7中將StringTable放到了堆空間中。因為永久代的回收效率很低,在full gc的時候才能觸發。而full gc是老年代的空間不足、永久代不足才會觸發
- 這就導致StringTable回收效率不高,而我們開發中會有大量的字符串被創建,回收效率低,導致永久代內存不足,放到堆里,能及時回收內存
四、設置方法區大小
- -XX:PermSize=100m(默認值是20.75M)
- -XX:MaxPermSize=100m(32位機器默認是64M,64位機器模式是82M)
圖解:
- -XX:MetaspaceSize=100m(windows下,默認約等于21M)
- -XX:MaxMetaspaceSize=100m(默認是-1,即沒有限制)
五、 常量池的理解
一個有效的字節碼文件中除了包含類的版本信息、字段、方法以及接口等描述信息外,還包含一項信息那就是常量池表(Constant Poo1 Table),包括各種字面量和對類型域和方法的符號引用。
個 java 源文件中的類、接口,編譯后產生一個字節碼文件。而 Java 中的字節碼需要數據支持,通常這種數據會很大以至于不能直接存到字節碼里,換另一種方式,可以存到常量池這個字節碼包含了指向常量池的引用。在動態鏈接的時候會用到運行時常量池
比如如下代碼,雖然只有 194 字節,但是里面卻使用了 string、System、Printstream 及 Object 等結構。這里代碼量其實已經很小了。如果代碼多,引用到的結構會更多!
六、運行時常量池 掌握
運行時常量池,常量池是 *.class 文件中的,當該類被加載,它的常量池信息就會放入運行時常量池,并把里面的符號地址變為真實地址
運行時常量池( Runtime Constant Pool)是方法區的一部分。
常量池表(Constant Pool Table)是Class文件的一部分,用于存放編譯期生成的各種字面量與符號引用,這部分內容將在類加載后存放到方法區的運行時常量池中。
運行時常量池中包含多種不同的常量,包括編譯期就已經明確的數值字面量,也包括到運行期解析后才能夠獲得的方法或者字段引用。此時不再是常量池中的符號地址了,這里換為真實地址。
(方法區內常量池之中主要存放的兩大類常量:字面量和符號引用。
字面量比較接近Java語言層次的常量概念,如文本字符串、被聲明為final的常量值等。
而符號引用則屬于編譯原理方面的概念,包括下面三類常量:
1、類和接口的全限定名
2、字段的名稱和描述符
3、方法的名稱和描述符) 掌握
七、如何證明靜態變量存在哪
/*** 《深入理解Java虛擬機》中的案例:* staticObj、instanceObj、localObj存放在哪里?*/ public class StaticObjTest {static class Test {static ObjectHolder staticObj = new ObjectHolder();ObjectHolder instanceObj = new ObjectHolder();void foo() {ObjectHolder localObj = new ObjectHolder();System.out.println("done");}}private static class ObjectHolder {}public static void main(String[] args) {Test test = new StaticObjTest.Test();test.foo();} }- staticObj隨著Test的類型信息存放在方法區,instance0bj 隨著Test的對象實例存放在Java堆,localobject則是存放在foo()方法棧幀的局部變量表中
-
測試發現:三個對象的數據在內存中的地址都落在Eden區范圍內,所以結論:只要是對象實例必然會在Java堆中分配
-
接著,找到了一個引用該staticObj對象的地方,是在一個java. lang . Class的實例里,并且給出了這個實例的地址,通過Inspector查看該對象實例,可以清楚看到這確實是一個
java.lang.Class類型的對象實例,里面有一個名為staticObj的實例字段:
-
從《Java 虛擬機規范》所定義的概念模型來看,所有 C1ass 相關的信息都應該存放在方法區之中,但方法區該如何實現,《Java 虛擬機規范》并未做出規定,這就成了一件允許不同虛擬機自己靈活把握的事情。JDK7 及其以后版本的 Hotspot 虛擬機選擇把靜態變量與類型在 Java 語言一端的映射 C1ass 對象存放在一起,存儲于】ava 堆之中,從我們的實驗中也明確驗證了這一點.
八、 方法區的垃圾回收
前言:
- (1).有些人認為方法區(如Hotspot,虛擬機中的元空間或者永久代)是沒有垃圾收集行為的,其實不然。《Java虛擬機規范》對方法區的約束是非常寬松的,提到過可以不要求虛擬機在方法區中實現垃圾收集。事實上也確實有未實現或未能完整實現方法區類型卸載的收集器存在(如 JDK11 時期的 ZGC 收集器就不支持類卸載)
- (2).一般來說這個區域的回收效果比較難令人滿意,尤其是類型的卸載,條件相當苛刻。但是這部分區域的回收有時又確實是必要的。以前 Sun 公司的 Bug 列表中,曾出現過的若干個嚴重的 Bug 就是由于低版本的 Hotspot 虛擬機對此區域未完全回收而導致內存泄漏。
方法區的垃圾收集主要回收兩部分內容:常量池中廢奔的常量和不再使用的類型
先來說說方法區內常量池之中主要存放的兩大類常量:字面量和符號引用。 字面量比較接近Java語言層次的常量概念,如文本字符串、被聲明為final的常量值等。而符號引用則屬于編譯原理方面的概念,包括下面三類常量:
- 類和接口的全限定名
- 字段的名稱和描述符
- 方法的名稱和描述符
HotSpot虛擬機對常量池的回收策略是很明確的,只要常量池中的常量沒有被任何地方引用,就可以被回收。回收廢棄常量與回收Java堆中的對象非常類似。
判定一個常量是否“廢棄”還是相對簡單,而要判定一個類型是否屬于“不再被使用的類”的條件就比較苛刻了。需要同時滿足下面三個條件:
- 該類所有的實例都已經被回收,也就是Java堆中不存在該類及其任何派生子類的實例。
- 加載該類的類加載器已經被回收,這個條件除非是經過精心設計的可替換類加載器的場景,如OSGi、JSP的重加載等,否則通常是很難達成的
- 該類對應的java.lang.Class對象沒有在任何地方被引用,無法在任何地方通過反射訪問該類的方法
Java虛擬機被允許對滿足上述三個條件的無用類進行回收,這里說的僅僅是“被允許”,而并不是和對象一樣,沒有引用了就必然會回收。關于是否要對類型進行回收,HotSpot虛擬機提供了一Xnoclassgc 參數進行控制,還可以使用一verbose:class以及一XX: +TraceClass一Loading、一XX:+TraceClassUnLoading查 看類加載和卸載信息
在大量使用反射、動態代理、CGLib等字節碼框架,動態生成JSP以及oSGi這類頻繁自定義類加載器的場景中,通常都需要Java虛擬機具備類型卸載的能力,以保證不會對方法區造成過大的內存壓力
總結
以上是生活随笔為你收集整理的JVM_03 运行时数据区 [ 方法区]的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: JVM_03 运行时数据区[ 堆 ]
- 下一篇: JVM_04 对象的实例化+内存布局+访