主页 > imtoken官方苹果下载 > 【JVM】JVM内存结构——方法区(元空间)
【JVM】JVM内存结构——方法区(元空间)
目录
方法区和Java堆一样,是每个线程共享的内存区域,用来存放类信息、常量、静态变量、虚拟机已经加载的即时编译器等。后代码和其他数据。方法区也称为永久代。
1.栈、堆和方法区的交互
UserEntity.class 信息存储在常量池中
常量池存放在JDK中的方法区1.6;
常量池存储在JDK中的元空间中1.8;新的 UserEntity 存储在堆内存中;返回一个内存地址给我们存储在栈中的userEntity2.方法区的理解
1.官方文档:#jvms-2.5.4
2.《Java 虚拟机规范》明确指出,虽然所有方法区在逻辑上都是堆的一部分,但一些简单的实现可能不会选择这样做垃圾收集或压缩。但是对于HotSpotJVM来说,方法区还有一个别名叫做Non-Heap(非堆),目的是为了和堆分开。因此,方法区可以看成是一个独立于Java堆的内存空间。
3.方法区主要存放Class信息,堆主要存放实例化的对象
4.方法区和Java堆一样,是线程共享的内存区域。当多个线程同时加载同一个类时,只有一个线程可以加载该类,其他线程只能等待该线程加载,然后直接使用该类,即该类只能加载一次。
5.方法区是在JVM启动时创建的,它的实际物理内存空间可以像Java堆区一样是不连续的。
6.方法区的大小,和堆空间一样,可以是固定的,也可以是可扩展的。
7.方法区的大小决定了系统可以保存多少个类。如果系统定义的类过多,方法区溢出,虚拟机也会抛出内存溢出错误:java .lang.OutofMemoryError: PermGen space or java.lang.OutOfMemoryError: Metaspace
8.方法区溢出的原因:
A.加载大量第三方jar包
B. Tomcat 部署的项目太多
C.大量动态生成的反射类
D. JDK1.6 定义了大量的字符串
关闭 JVM 以释放该区域的内存。
3.设置方法区的大小和OOM
方法区的大小不必固定,JVM可以根据应用的需要动态调整。
3.1 JDK7 及更早版本(永久代)
1.使用-XX:Permsize设置永久代的初始分配空间。默认值为 20.75M
2.-XX:MaxPermsize 设置永久代最大可分配空间。
3.当JVM加载的类信息容量超过这个值时,会报异常OutofMemoryError: PermGen space。
3.2 JDK8 及更高版本(元空间)
JDK8版本设置元数据区大小
1.元数据区域的大小可以使用参数-XX:MetaspaceSize和-XX:MaxMetaspaceSize来指定
2.默认值取决于平台。 Windows 下 -XX:MetaspaceSize 约为 21M,-XX:MaxMetaspaceSize 的值为 -1 ,即无限制。
3.与永久代不同,如果没有指定大小,默认情况下虚拟机将耗尽所有可用的系统内存。如果元数据区溢出,虚拟机也会抛出异常OutOfMemoryError:Metaspace
4.-XX:MetaspaceSize:设置初始元空间大小。对于 64 位服务器端 JVM,默认 -XX:MetaspaceSize 值为 21MB。这是初始的高水位线,一旦命中这个水位线,会触发Full GC,卸载无用的类(即这些类对应的类加载器不再存活),然后重置高水位线新高水位线的值取决于 GC 后释放了多少元空间。如果释放空间不足,在不超过MaxMetaspaceSize的情况下适当增加。如果释放的空间过多,适当降低该值。
5.如果初始高水位线设置得太低,上述高水位线调整可能会发生多次。通过垃圾收集器的日志可以观察到Full GC被多次调用。为避免频繁的GC,建议将-XX:MetaspaceSize设置为较高的值。
4.方法区OOM异常如何解决
DemoOOMClassLoader类继承ClassLoader类,获取defineClass()方法,可以自行加载类
4.1 JDK7 及更早版本(永久代)
public class DemoOOMClassLoader extends ClassLoader {
/**
* jdk6/7中:
* -XX:PermSize=10m -XX:MaxPermSize=10m
*
* jdk8中:
* -XX:-UseCompressedClassPointers -XX:MetaspaceSize=10m -XX:MaxMetaspaceSize=10m
*
* @param args
*/
public static void main(String[] args) {
int j = 0;
try {
DemoOOMClassLoader test = new DemoOOMClassLoader();
for (int i = 0; i < 10000; i++) {
//创建ClassWriter对象,用于生成类的二进制字节码
ClassWriter classWriter = new ClassWriter(0);
//指明版本号,修饰符,类名,包名,父类,接口
classWriter.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, "Class" + i, null, "java/lang/Object", null);
//返回byte[]
byte[] code = classWriter.toByteArray();
//类的加载
test.defineClass("Class" + i, code, 0, code.length);//Class对象
j++;
}
} finally {
System.out.println(j);
}
}
}
4.2 JDK8 及更高版本(元空间)
public class DemoOOMClassLoader extends ClassLoader {
/**
* jdk6/7中:
* -XX:PermSize=10m -XX:MaxPermSize=10m
*
* jdk8中:
* -XX:-UseCompressedClassPointers -XX:MetaspaceSize=10m -XX:MaxMetaspaceSize=10m
*
* @param args
*/
public static void main(String[] args) {
int j = 0;
try {
DemoOOMClassLoader test = new DemoOOMClassLoader();
for (int i = 0; i < 10000; i++) {
//创建ClassWriter对象,用于生成类的二进制字节码
ClassWriter classWriter = new ClassWriter(0);
//指明版本号,修饰符,类名,包名,父类,接口
classWriter.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, "Class" + i, null, "java/lang/Object", null);
//返回byte[]
byte[] code = classWriter.toByteArray();
//类的加载
test.defineClass("Class" + i, code, 0, code.length);//Class对象
j++;
}
} finally {
System.out.println(j);
}
}
}
5.方法区内部结构
p>
MethodArea的存储内容描述如下:用于存储类型信息、常量、静态变量、虚拟机已经加载的即时编译器Post代码缓存等。
5.1 类型信息
对于每个加载的类型(类、接口、枚举、注解注解),JVM必须在方法区存储以下类型信息:
该类型的有效全名(全名=包名.类名)
该类型是类的完整有效名称的直接父类(对于接口或java.lang.Object,没有父类)
此类型的修饰符(public、abstract、final 的子集)
类型的直接接口的有序列表
5.2 字段信息
JVM必须在方法区存储该类型的所有字段的信息以及字段的声明顺序。
域的相关信息包括:域名、域类型、域修饰符(public、private、protected、static、final、volatile、transient的子集)
5.3 方法信息
JVM 必须为所有方法保存以下信息虚拟币池子大小,包括域信息等声明顺序:
方法名
Method 返回类型(包括void返回类型),Java中的void对应void.class
方法参数的数量和类型(按顺序)
方法的修饰符(public、private、protected、static、final、synchronized、native、abstract的子集)
方法的字节码(bytecodes)、操作数栈、局部变量表和大小(抽象和本机方法除外))
异常表(抽象方法和native方法除外),异常表记录了每次异常处理的开始位置、结束位置、程序计数器中代码处理的偏移地址、捕获到的异常类Pool的常量索引
6.永久代进化过程
JDK1.7有永久代,字符串常量池,静态变量去除,存放在堆中其他常量池存放在永久代
JDK1.8 没有永久代虚拟币池子大小,类型信息,字段,方法,常量都存放在本地内存的元空间中,但是字符串常量池,静态变量还在堆中。
6.1 为什么永久代被元空间代替了?
只有热点有永久代。对于BEA JRockit、IBMJ9等,没有永久代的概念。
1.JDK8没有永久代改为元空间,在本地内存中分配,最大可以分配的元空间空间为系统可用内存空间;
2.永久代的空间大小很难确定。在某些场景下,如果动态加载的类过多,很容易在方法区产生OOM。比如在一个实际的Web项目中,由于功能点很多,需要加载很多类。在运行过程中,需要动态加载很多类,经常会出现致命错误。 java.lang.OutOfMemoryError:PermGen 空间
3.元空间和永久代最大的区别是元空间不在虚拟机中,而是使用本地内存。因此,默认情况下,元空间的大小仅受本地内存的限制。
相关证明代码:
6.2 为什么要调整字符串常量池的位置?
1.JDK7 将字符串常量池放入堆空间。因为永久代的回收效率很低,所以只有在进行Full GC时才会进行永久代的垃圾回收,当老年代空间不足和永久代不足时才触发Full GC .
2.这导致字符串常量池回收效率低下,我们开发中会创建大量字符串,导致回收效率低,导致永久代内存不足。放到堆里,内存可以及时回收。
6.3 如何证明静态变量存放在堆中?
静态引用对应的对象实体(即新字节[1024 * 1024 * 10])始终存在于堆空间中
栈内存中的变量:bytes 在JDK6、JDK7、JDK8中存储位置发生了变化
JDK6
JDK8