## 相关特征
### 方法区特征
* 同 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