方法区
1、方法区存储什么数据?
和Class文件式一 一对应的
类型信息,?如Class(com.wjx.User类) ?法信息,?如Method(?法名称、?法参数列表、?法返回值信息) 字段信息(域信息),?如Field(字段类型,字段名称需要特殊设置才能保存的住)
Code区(字节码指令区),存储的是?法执?对应的字节码指令 ?法表(?法调?的时候) 在A类的main?法中去调?B类的method1?法, 是根据B类的?法表去查找合适的?法,进?调?的。 在方法表缓存方法的key–value值,查找更快
静态变量(类变量)—JDK1.7之后,转移到堆中存储
运?时常量池(字符串常量池)—从class中的常量池加载?来—JDK1.7之后,转移到堆中存储
- 字?量类型
- 双引号引起来的字符串值,?如"wjx" ----- 会进?字符串常量池(StringPool)
- final修饰的变量
- ?final修饰的变量,?如long、double、float
- 引?类型–>内存地址
JIT编译器编译之后的代码缓存
如果需要访问?法区中类的其他信息,都必须先获得Class对象, 才能取访问该Class对象关联的?法信息或者字段信息
存储示意图如下,下面的图片显示的是JVM加载类的时候,方法区存储的信息:
1.1、类型信息(重点)
●类型的全限定名 ●超类的全限定名 ●直接超接口的全限定名 ●类型标志(该类是类类型还是接口类型) ●类的访问描述符(public、 private、 default. abstract. final、 static)
1.2、类型的常量池
存放该类型所用到的常量的有序集合,包括直接常量(如字符串、整数、 浮点数的常量)和对其他类型、字段、方法的符号引用。 常量池中每-一个保 存的常量都有一个索引,就像数组中的字段一样。因为 常量池中保存着所有类型使用到的类型、字段、方法的字符引用,所以它 也是动态连接的主要对象(在动态链接中起到核心作用)。
1.3、字段信息(重点)
- 字段修饰符(public、 protect. private. default)
- 字段的类型
- 字段名称
1.4、方法信息(重点)
方法信息中包含类的所有方法,每个方法包含以下信息:
- 方法修饰符
- 方法返回类型
- 方法名
- 方法参数个数、类型、顺序等
- 方法字节码
- 操作数栈和该方法在栈帧中的局部变量区大小
- 异常表
1.5、类变量(重点)
指该类所有对象共享的变量,即使没有任何实例对象时,也可以访问的类变量。它们与类进行绑定。
1.6、指向类加载器的引用
每一个被JVM加载的类型,都保存这个类加载器的引用,类加载器动态链接时会用到。
1.7、指向Class实例的引用
类加载的过程中,虚拟机会创建该类型的Class实例,方法区中必须保存对该对象的引用。 通过Class.forName(tring className)来查找获得该实例的引用,然后创建该类的对象。
1.8、 方法表(重点)
为了提高访问效率,JVM可能会对每个装载的非抽象类,都创建一个数组,数组的每个元素是实例可能调用的方法的直接引用, 包括父类中继承过来的方法。这个表在抽象类或者接口中是没有的。
1.9、运行时常量池
(Runtime Constant Pool) class文件中除了有类的版本、字段、方法、接口等描述信息外, 还有一项信息是常量池,用于存放编译器生成的各种字面常量和符号引用,这部分内容被类加载后进入方法区的运行时常量池中存放。 运行时常量池相对于class文件常量池的另外一个特征具有动态性,可以在运行期间将新的常量放入池中(典型的如String类的intrn0方法)。
2、永久代和元空间的区别是什么?
???1. JDK1.8之前使用的方法区实现是永久代,JDK1.8及以后使用的方法区实现是元空间。
???2.存储位置不同,永久代所使用的内存区域是IVM进程所使用的区域,它的大小受整个JVM的大小所限制。如果内存不够会触发fullGC(一定要避免) 元空间所使用的内存区域是物理内存区域。那么元空间的使用大小只会受物理内存大小的限制。
???3.存储内容不同,永久代存储的信息基本上就是上面方法区存储内容中的数据。 元空间只存储类的元信息,而静态变量和运行时常量池都挪到堆中。
3、为什么要使元空间来替换永久代?
1.字符串存在永久代中,容易出现性能问题和永久代内存溢出。
2.类及方法的信息等比较难确定其大小,因此对于永久代的大小指定比较困难,太小容易出现永久代溢出,太大则容易导致老年代溢出。
3.永久代会为GC带来不必要的复杂度,并且回收效率偏低。
?4. Oracle 计划将HotSpot与JRockit合二为一。
结论.
其实,移除永久代的工作从DK1.7就开始了。 JDK1.7中,存储在永久代的部分数据就已经转移到了Java Heap。 但永久代仍存在于JDK1.7中,并没完全移除,譬如字面量(interned strings). 转移到了java heap;类的静态变量(class statics)转移到了java heap。
4、方法区异常演示
4.1、类加载导致OOM(内存溢出)异常
类加载太多和运行时常量池太大
1)案例代码 我们现在通过动态生成类来模拟方法区的内存溢出: 重复加载一个类
package com.wjx.test.memory;
public class Test {}
package com.wjx.test.memory;
import java.io.File;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.List;
public class PermGenOomMock{
public static void main(String[] args) {
URL url = null;
List<ClassLoader> classLoaderList = new ArrayList<ClassLoader>();
try {
url = new File("/tmp").toURI().toURL();
URL[] urls = {url};
while (true){
ClassLoader loader = new URLClassLoader(urls);
classLoaderList.add(loader);
loader.loadClass("com.wjx.test.memory.Test");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
2) JDK1.7分析 指定的PermGen区的大小为8M:
最典型的场景就是,在jsp页面比较多的情况,容易出现永久代内存溢出。
3) JDK1.8+分析 现在我们在JDK 8下重新运行一下案例代码,不过这次不再指定Permsize和MaxPermsize。 而是指定MetaSpaceSize和MaxMetaSpaceSize的大小。输出结果如下: 从输出结果,我们可以看出,这次不再出现永久代溢出,而是出现了元空间的溢出。.
4.2、字符串OOM异常
1)案例代码 以下这段程序以2的指数级不断的生成新的字符串,这样可以比较快速的消耗内存:
package com.wjx.test.memory;
import java.util.ArrayList;
import java.util.List;
public class StringOomMock {
static String base = "string";
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
for (int i=0;i< Integer.MAX_VALUE;i++){
String str = base + base;
base = str;
list.add(str.intern());
}
}
}
2) JDK1.6 JDK 1.6的运行结果: 在JDK 1.6下,会出现永久代的内存溢出。
3) JDK1.7 JDK 1.7的运行结果: JDK 1.7中,会出现堆内存溢出。结论是: JK 1.7已经将字符串常量由永久代转移到堆中。
4) JDK1.8+ JDK 1.8的运行结果: 在JDK 1.8中,也会出现 堆内存溢出,并且显示JDK 1.8中PermSize和MaxPermGen已经无效。 因此,可以验证JDK 1.8中已经不存在永久代的结论
|