天天看点

JVM学习笔记——方法区

  方法区在逻辑上属于堆的一部分,但可以看做是一块独立于 Java 堆的内存空间。所有的字段和方法字节码,以及一些特殊的方法,如构造函数,接口代码在此定义。所有定义方法的信息都保存在方法区。在 JDK 8 之后,方法区移动至本地内存中。

方法区具有以下特点:

方法区与 Java 堆一样,是各个线程共享的内存区域

方法区在 JVM 启动时创建完成,并且实际内存区域和 Java 堆区一样都可以是不连续的

方法区的大小,跟堆空间大小一样,可以选择固定大小或者可扩展

方法区的大小决定了系统可以保存多少类,在 JDK8 之后方法区溢出报错改为 OutOfMemoryError:Metaspace

关闭 JVM 后方法区就被释放

  静态变量,常量,类信息,运行时常量池存在方法区中。实例变量存在堆中,与方法区无关。

类型信息

  对每个加载的类型(类class,接口interface,枚举enum,注解annotation),在方法区存储以下信息:

类型的完整有效名称,全限定名

类型直接父类的全限定名

类型的修饰符(public,abstract,final 的子集)

类型直接接口的一个有序表

域(Field)信息(属性、字段)

所有域的相关信息以及域的声明顺序

域名称,域类型,域修饰符(public,private,protected,static,final,volatile,transient 的子集)

方法信息

声明顺序

方法名称

方法的返回值类型

方法参数的属性,类型,顺序

方法修饰符(public,private,protected,static,final,synchronized,native,abstract的子集)

  Java 中的常量池分为静态常量池和运行时常量池。

静态常量池

  即.class 文件中的常量池,class 文件中的常量池包含字符串(数字)字面量,类、方法的信息,占用class文件绝大部分空间。

运行时常量池

  JVM 虚拟机在完成类装载后,将 class 文件中的常量池载入到内存中,并保存在方法区中,这就是运行时常量池

常量池表(Constant Pool Table)是 Class 文件的一部分,用于存放编译期间生成的各种字面量与符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中

JDK 7 之前习惯吧方法区称为永久代,JDK 8 后废除永久代的概念,把方法区改为和 JRocket、J9 一样的元空间(MetaSpace),并调整了方法区的内部结构,如将字符串常量由永久代转移到堆中

元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。因此,默认情况下,元空间的大小仅受本地内存限制

  字符串常量由永久代转移到堆中,是由于字符串存在永久代中,容易出现性能问题和内存溢出。

  类的静态变量(class statics)转移到了堆。类及方法的信息等比较难确定其大小,因此对于永久代的大小指定比较困难,太小容易出现永久代溢出,太大则容易导致老年代溢出。

永久代会为 GC 带来不必要的复杂度,并且回收效率偏低。

JDK 8 之后,元空间可以使用

<code>-XX:MetaspaceSize</code>:设置初始空间大小,到达该值就会触发垃圾收集,同时 GC 会对该值进行调整,windows 下,该值初始默认为 21M,无上限

<code>-XX:MetaspaceSize</code>:用于设置最大空间,无限制

<code>-XX:MinMetaspaceFreeRatio</code>:GC之后,最小的 Metaspace 剩余空间容量的百分比,减少为分配空间所导致的垃圾收集

<code>-XX:MaxMetaspaceFreeRatio</code>:GC之后,最大的 Metaspace 剩余空间容量的百分比,减少为释放空间所导致的垃圾收集