## 相關特征
### 方法區特征
* 同 Java 堆一樣,方法區也是全局共享的一塊內存區域
* 方法區的作用是存儲 Java 類的結構信息,當我們創建對象實例后,對象的類型信息存儲在方法區之中,實例數據存放在堆中;實例數據指的是在 Java 中創建的各種實例對象以及它們的值,類型信息指的是定義在 Java 代碼中的常量、靜態變量、以及在類中聲明的各種方法、方法字段等等;同事可能包括即時編譯器編譯后產生的代碼數據。
* JVMS 不要求該區域實現自動的內存管理,但是商用 JVM 一般都已實現該區域的自動內存管理。
* 方法區分配內存可以不連續,可以動態擴展。
* 該區域并非像 JMM 規范描述的那樣數據一旦放進去就屬于 “永久代”;在該區域進行內存回收的主要目的是對常量池的回收和對內存數據的卸載;一般來說這個區域的內存回收效率比起 Java 堆要低得多。
* 當方法區無法滿足內存需求時,將拋出 OutOfMemoryError 異常。
### 運行時常量池的特征
* 運行時常量池是方法區的一部分,所以也是全局共享的。
* 其作用是存儲 Java 類文件常量池中的符號信息。
* class 文件中存在常量池(非運行時常量池),其在編譯階段就已經確定;JVM 規范對 class 文件結構有著嚴格的規范,必須符合此規范的 class 文件才會被 JVM 認可和裝載。
* 運行時常量池 中保存著一些 class 文件中描述的符號引用,同時還會將這些符號引用所翻譯出來的直接引用存儲在 運行時常量池 中。
* 運行時常量池相對于 class 常量池一大特征就是其具有動態性,Java 規范并不要求常量只能在運行時才產生,也就是說運行時常量池中的內容并不全部來自 class 常量池,class 常量池并非運行時常量池的唯一數據輸入口;在運行時可以通過代碼生成常量并將其放入運行時常量池中。
* 同方法區一樣,當運行時常量池無法申請到新的內存時,將拋出 OutOfMemoryError 異常。
## HotSpot 方法區變遷
### JDK1.2 ~ JDK6
在 JDK1.2 ~ JDK6 的實現中,HotSpot 使用永久代實現方法區;HotSpot 使用 GC 分代實現方法區帶來了很大便利;
### JDK7
由于 GC 分代技術的影響,使之許多優秀的內存調試工具無法在 Oracle HotSpot之上運行,必須單獨處理;并且 Oracle 同時收購了 BEA 和 Sun 公司,同時擁有 JRockit 和 HotSpot,在將 JRockit 許多優秀特性移植到 HotSpot 時由于 GC 分代技術遇到了種種困難,所以從 JDK7 開始 Oracle HotSpot 開始移除永久代。
JDK7中符號表被移動到 Native Heap中,字符串常量和類引用被移動到 Java Heap中。
### JDK8
在 JDK8 中,永久代已完全被元空間(Meatspace)所取代。
## 永久代變遷產生的影響
### 測試代碼1
```
public class Test1 {
public static void main(String[] args) {
String s1 = new StringBuilder("漠").append("然").toString();
System.out.println(s1.intern() == s1);
String s2 = new StringBuilder("漠").append("然").toString();
System.out.println(s2.intern() == s2);
}
}
```
以上代碼,在 JDK6 下執行結果為 false、false,在 JDK7 以上執行結果為 true、false。
首先明確兩點: 1、在 Java 中直接使用雙引號展示的字符串將會在常量池中直接創建。 2、String 的 intern 方法首先將嘗試在常量池中查找該對象,如果找到則直接返回該對象在常量池中的地址;找不到則將該對象放入常量池后再返回其地址。JDK6 常量池在方法區,頻繁調用該方法可能造成 OutOfMemoryError。
產生兩種結果的原因:
在 JDK6 下 s1、s2 指向的是新創建的對象,該對象將在 Java Heap 中創建,所以 s1、s2 指向的是 Java Heap 中的內存地址;調用 intern 方法后將嘗試在常量池中查找該對象,沒找到后將其放入常量池并返回,所以此時 s1/s2.intern() 指向的是常量池中的地址,JDK6常量池在方法區,與堆隔離,;所以 s1.intern()==s1 返回false。
### 測試代碼2
```
public class Test2 {
public static void main(String[] args) {
/**
* 首先設置 持久代最大和最小內存占用(限定為10M)
* VM args: -XX:PermSize=10M -XX:MaxPremSize=10M
*/
List<String> list = new ArrayList<String>();
// 無限循環 使用 list 對其引用保證 不被GC intern 方法保證其加入到常量池中
int i = 0;
while (true) {
// 此處永久執行,最多就是將整個 int 范圍轉化成字符串并放入常量池
list.add(String.valueOf(i++).intern());
}
}
}
```
以上代碼在 JDK6 下會出現 Perm 內存溢出,JDK7 or high 則沒問題。
原因分析:
JDK6 常量池存在方法區,設置了持久代大小后,不斷while循環必將撐滿 Perm 導致內存溢出;JDK7 常量池被移動到 Native Heap(Java Heap),所以即使設置了持久代大小,也不會對常量池產生影響;不斷while循環在當前的代碼中,所有int的字符串相加還不至于撐滿 Heap 區,所以不會出現異常。
- java
- 設計模式
- 設計模式總覽
- 設計原則
- 工廠方法模式
- 抽象工廠模式
- 單例模式
- 建造者模式
- 原型模式
- 適配器模式
- 裝飾者模式
- 代理模式
- 外觀模式
- 橋接模式
- 組合模式
- 享元模式
- 策略模式
- 模板方法模式
- 觀察者模式
- 迭代子模式
- 責任鏈模式
- 命令模式
- 備忘錄模式
- 狀態模式
- 訪問者模式
- 中介者模式
- 解釋器模式
- 附錄
- JVM相關
- JVM內存結構
- Java虛擬機的內存組成以及堆內存介紹
- Java堆和棧
- 附錄-數據結構的堆棧和內存分配的堆區棧區的區別
- Java內存之Java 堆
- Java內存之虛擬機和內存區域概述
- Java 內存之方法區和運行時常量池
- Java 內存之直接內存(堆外內存)
- JAVA內存模型
- Java內存模型介紹
- 內存模型如何解決緩存一致性問題
- 深入理解Java內存模型——基礎
- 深入理解Java內存模型——重排序
- 深入理解Java內存模型——順序一致性
- 深入理解Java內存模型——volatile
- 深入理解Java內存模型——鎖
- 深入理解Java內存模型——final
- 深入理解Java內存模型——總結
- 內存可見性
- JAVA對象模型
- JVM內存結構 VS Java內存模型 VS Java對象模型
- Java的對象模型
- Java的對象頭
- HotSpot虛擬機
- HotSpot虛擬機對象探秘
- 深入分析Java的編譯原理
- Java虛擬機的鎖優化技術
- 對象和數組并不是都在堆上分配內存的
- 垃圾回收
- JVM內存管理及垃圾回收
- JVM 垃圾回收器工作原理及使用實例介紹
- JVM內存回收理論與實現(對象存活的判定)
- JVM參數及調優
- CMS GC日志分析
- JVM實用參數(一)JVM類型以及編譯器模式
- JVM實用參數(二)參數分類和即時(JIT)編譯器診斷
- JVM實用參數(三)打印所有XX參數及值
- JVM實用參數(四)內存調優
- JVM實用參數(五)新生代垃圾回收
- JVM實用參數(六) 吞吐量收集器
- JVM實用參數(七)CMS收集器
- JVM實用參數(八)GC日志
- Java性能調優原則
- JVM 優化經驗總結
- 面試題整理
- 面試題1
- java日志規約
- Spring安全
- OAtuth2.0簡介
- Spring Session 簡介(一)
- Spring Session 簡介(二)
- Spring Session 簡介(三)
- Spring Security 簡介(一)
- Spring Security 簡介(二)
- Spring Security 簡介(三)
- Spring Security 簡介(四)
- Spring Security 簡介(五)
- Spring Security Oauth2 (一)
- Spring Security Oauth2 (二)
- Spring Security Oauth2 (三)
- SpringBoot
- Shiro
- Shiro和Spring Security對比
- Shiro簡介
- Session、Cookie和Cache
- Web Socket
- Spring WebFlux