主页 > imtoken官方苹果下载 > 【JVM】JVM内存结构——方法区(元空间)

【JVM】JVM内存结构——方法区(元空间)

imtoken官方苹果下载 2023-01-17 05:38:11

目录

方法区和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

在这里插入图片描述