JVM method area

Oracle 官方文档
The Java Virtual Machine has a method area that is shared among all Java Virtual Machine threads. The method area is analogous相似的 to the storage存储 area for compiled编译的 code of a conventional传统的 language or analogous相似的 to the “text” segment片;段; in an operating system process. It stores per-class structures结构 such as the run-time constant常量 pool, field字段 and method data, and the code字节码 for methods and constructors构造器, including the special特殊的 methods (§2.9) used in class and instance实例 initialization初始化 and interface initialization.

The method area is created on virtual machine start-up. Although the method area is logically逻辑上的 part of the heap, simple implementations实现 may choose not to either garbage collect or compact压缩 it. This specification规范 does not mandate强制执行 the location of the method area or the policies策略 used to manage compiled code. The method area may be of a fixed固定的 size or may be expanded扩展 as required by the computation计算 and may be contracted收缩 if a larger method area becomes unnecessary. The memory for the method area does not need to be contiguous连续的.

A Java Virtual Machine implementation may provide the programmer or the user control over the initial size of the method area, as well as, in the case of a varying-size大小动态变化的 method area, control over the maximum and minimum method area size.

The following exceptional异常的 condition情况 is associated相关的 with the method area:

  • If memory in the method area cannot be made available to satisfy满足 an allocation分配 request, the Java Virtual Machine throws an OutOfMemoryError.

Method area 方法区
stack areaheap areamethod area交互关系
Person p=new Person();

  • Person 类信息 保存在 method area
  • new Person() 对象实例保存在 heap area
  • 如果此行代码在方法内部,则p是一个reference引用类型保存在stack arealocal variables

特点

  • Method area 有一个别名叫Non-Heap非堆
  • Method area 是一块独立于heap area之外的内存空间
  • Method area 保存类信息
  • Method area 线程共享
  • Method area 可以设置为固定大小,也可以动态扩展
  • Method area 大小决定系统可以保存多少个类,如果系统定义了太多的类,导致方法区溢出,JVM抛出java.lang.OutOfMemoryError: PermGen space/java.lang.OutOfMemoryError: Metaspace
    • 例子:
      • 加载大量的第三方jar包
      • Tomcat部署的工程过多(30-50个)
      • 大量动态的生成反射类
  • Method area生命周期与JVM一致,JVM启动时分配,关闭后释放。

设置method area大小OOM

JDK7及之前

  • -XX:PermSize=20.75M 设置PermanentGenerationSpace 初始值。默认20.75M
  • -XX:MaxPermSize=82M 设置PermanentGenerationSpace 最大可分配空间。32位机器默认是64M,64位机器默认是82M

JDK8及之后

  • -XX:MetaspaceSize=21M 设置Metaspace元空间初始值,平台不同默认值不同,64位 -server 模式windows下默认约为21M
  • -XX:MaxMetaspaceSize=-1 设置Metaspace最大可分配空间,-1表示没有限制

如果不指定大小,默认情况下,JVM会耗尽所有的可用系统内存,如果Metaspace发生溢出,jvm会抛出java.lang.OutOfMemoryError: Metaspace

设置初始MetaspaceSize大小,对于一个64bits -serverJVM来说,其默认的-XX:MetaspaceSize值为21MB,初始的高水位线,一旦触及这个水位线FullGC将会被触发并卸载没用的类这些类对应的类加载器不再存活
FullGC后这个高水位线将会重置,新的高水位线的值取决于GC后释放了多少Metaspace

  • 如果释放的空间不足,在不超过MaxMetaspaceSize时,适当提高MetaspaceSize
  • 如果释放的空间过多,则适当降低MetaspaceSize

如果初始化的高水位线设置过低,上述高水位线调整情况会发生很多次。通过GC的日志可以观察到FullGC多次调用。
为了避免频繁GC,建议将-XX:MetaspaceSize设置为一个相对较高的值

method area内部结构

method area标准的存储内容。后续会变化

  • 类型信息
  • field字段
  • 方法信息
  • non-final static变量静态变量
  • static final全局常量
  • run-time constant pool运行时常量池
  • StringTable在运行时常量池内随着JDK变化,StringTable存储位置也会变化
  • JIT即时编译器编译后的代码缓存

类型信息:classinterface接口enum枚举annotation注解

  • 这个类型的完整有效名称包名.类名
  • 这个类型直接父类的完整有效名对于interfacejava.lang.Object都没有父类
  • 这个类型的修饰符publicabstractfinal的某个子集
  • 这个类型直接接口的一个有序列表

field字段: JVM必须在method area中保存类型信息的所有field相关信息以及field声明顺序
field的相关信息包括

  • field名称
  • field类型
  • field修饰符public、private、protected、static、final、volatile、transient的某个子集

方法信息

  • 方法名称
  • 方法的返回类型void也是一种类型
  • 方法参数的数量和类型有序
  • 方法的修饰符public、private、protected、static、final、synchronized、native、abstract的一个子集
  • 方法的byte codes字节码Operand StackLocal Variables大小abstractnative方法除外
  • 异常表abstractnative除外: 每个异常处理的开始位置结束位置、代码处理在Program Counter Register中的偏移地址、被捕获的异常类的常量池索引

non-final static变量静态变量/类变量:随着JDK变化,存储位置会变化

  • 静态变量和类关联在一起,随着类的加载而加载,他们称为类数据在逻辑上的一部分
  • 类变量被类的所有实例共享,即使没有类实例也可以访问

static final全局常量

  • 被声明为final类变量的处理方法不同,每个全局常量在编译期分配

run-time constant pool运行时常量池

  • Constant Pool Tableclass文件的一部分,用于存放编译期生成的各种字面量符号引用
  • Constant Pool Table类加载后存放到Method AreaRuntime Constant Pool
  • run-time constant pool,在加载类和接口到jvm后,就会创建对应的Runtime Constant Pool
  • JVM为每个已加载的类型类或接口都维护一个Runtime Constant PoolRuntime Constant Pool中的数据项像数组项一样,是通过索引访问的
  • run-time constant pool中包含多种不同的常量,包括编译期就已经明确的数值字面量,也包括到运行期解析后才能够获得的方法或者字段引用,此时不再是常量池中的符号地址了,这里换为真实地址
    • Runtime Constant Pool,相对于class文件Constant Pool Table的另一重要特征是:具备动态性
    • 动态举例: String.intern()public native String intern();如果Runtime Constant Pool中没有该字符串,则在Runtime Constant Pool放一个字符串常量
  • run-time constant pool类似于传统编程语言中的symbol table符号表,但是它所包含的数据比symbol table要更加丰富一些
  • 当创建类或接口的run-time constant pool时,如果构造run-time constant pool所需的内存空间超过了method area所能提供的最大值,则JVM会抛java.lang.OutOfMemoryError异常
  • JIT即时编译器编译后的代码缓存

字节码文件

一个有效的字节码文件中除了包含类的版本信息field字段methodinterface等描述信息外,还包含Constant Pool Table常量池表,包括各种字面量和对类型fieldmethod符号引用

字节码文件为什么需要Constant Pool Table常量池表
一个Java源文件,编译后产生一个字节码文件。字节码需要数据支持,通常这种数据会很大,以至于不能直接存在字节码里,而是换另一种存储方式存在Constant Pool Table,字节码使用指向Constant Pool Table符号引用。这样可以大大减小字节码文件的大小
JVM stackDynamic Linking的时候会将符号引用转换为指向run-time constant pool直接引用
Constant Pool Table,可以看作是一张表,虚拟机指令根据这张表找到要执行的类名、方法名、参数类型、字面量等类型

运行时常量池vs常量池

  • methad area内部包含run-time constant pool
  • 字节码文件内部包含Constant Pool Table
  • ClassLoader SubSystem字节码文件加载到RuntimeDataArea,其中字节码文件中的Constant Pool Table被加载到methad area后,就是run-time constant pool.

methad area演进细节

jdk7及以前习惯上把methad area称为 Permanent Generation space永久代
jdk8开始使用MetaSpace元空间取代Permanent Generation space
本质上methad areaPermanent Generation space并不等价,仅对Hotspot JVM而言两者等价
JVM规范对如何实现methad area不做统一要求,BEAJRockitIBMJ9中不存在Permanent Generation space的概念
Permanent Generation space导致java程序更容易OOM超过-XX:MaxPermSize上限

JDK8完全废弃Permanent Generation space的概念,改用JRockitJ9一样在本地内存中实现MetaSpace来代替
MetaSpace的本质和Permanent Generation space类似,都是对JVM规范method area的实现
MeatSpace不在JVM设置的内存中,而是直接使用本地物理内存,可以设置的更大,更不容易OOM PermGenSpaceJVM设置的内存中,所以容易OOM
根据JVM规范,如果methad area无法满足新的内存分配需求时,将抛出OOM

HotspotMethod area变化

  • jdk1.6及之前有permanent generation永久代
    • 永久代保存信息JVM内存
      • 类型信息
      • field字段
      • 方法信息
      • non-final static变量静态变量
      • static final全局常量
      • run-time constant pool运行时常量池
      • StringTable在运行时常量池内
      • JIT代码缓存
  • jdk1.7:有permanent generation,逐步”去永久代“,将StringTablenon-final static变量静态变量permanent generation移到Heap Area
    • 永久代保存信息JVM内存
      • 类型信息
      • field字段
      • 方法信息
      • static final全局常量
      • run-time constant pool运行时常量池
      • JIT代码缓存
    • heap area保存JVM内存
      • non-final static变量静态变量
      • StringTable
  • jdk1.8及之后:无permanent generation
    • Metaspace直接使用的是物理机内存
      • 类型信息
      • field
      • 方法信息
      • static final全局常量
      • run-time constant pool
      • JIT代码缓存
    • heap area保存JVM内存
      • non-final static变量静态变量
      • StringTable

为什么使用Metaspace替换PermanentGenerationSpace
Motivation动机
This is part of the JRockit and Hotspot convergence融合 effort试图.
JRockit customers do not need to configure the permanent generation永久代 (since JRockit does not have a permanent generation) and are accustomed 习惯于to not configuring the permanent generation.

设置PermanentGenerationSpace的大小很难确定
某些场景下,动态加载类过多,容易产生Perm区的OOM。比如某个实际Web工程中,因为功能点比较多,在运行过程中要不断动态加载很多类,经常出现致命错误OOM
使用MetaspacePermGenSpace最大区别在于:Metaspace并不在JVM中,而是使用本地物理机内存,因此默认情况下,Metaspace的大小仅受本地内存限制

PermanentGenerationSpace调优很困难FullGC

StringTable为什么要调整

JDK7中将StringTable放到heap area中,因为PermGenSpace回收效率很低,在FullGC的时候才会触发. 而FullGCOld区空间不足、PermGenSpace不足时才会触发,导致StringTable回收效率不高,而开发中会有大量的字符串被创建,回收效率低会更容易导致PermGenSpace空间不足。放在heap area能即时回收内存.

non-final static变量放在哪里

new出的实例对象都在heap area

对象引用存放位置

  • 非静态属性heap area
  • 方法内局部变量,在stack framelocal variables
  • 静态属性,在java.lang.Class对象内Class 对象

jvm规范定义的概念模型,所有Class相关的信息都应该存放在methad area中,但methad area如何实现jvm规范并未做出规定. 这就成了一件允许不同jvm自己灵活把握的事情
JDK7及其以后版本的Hotspot jvm选择把non-final static变量静态变量引用与类型大Class对象存放在一起,存储于heap area

methad area GC

主要回收两部分

  • run-time constant pool中废弃的常量
    • 字面量:如文本字符串、被声明为final的常量值等
    • 符号引用
      • 类和接口的全限定名
      • field的名称和描述符
      • method的名称和描述符
  • 不再使用的类型
    • 回收的条件
      • 该类所有的实例都已经被回收,也就是heap area中不存在该类及任何派生子类的实例
      • 加载该类的类加载器已经被回收,这个条件除非是经过精心设计的可替换类加载器的场景,如OSGiJSP的重加载等,否则通常很难达成
      • 该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法
    • JVM被允许对满足上述三个条件的无用类进行回收,这里说的仅仅是被允许,而并不是和对象一样,没有引用就必然会回收,关于是否要对类型进行回收
    • Hotspot提供了-Xnoclassgc参数进行控制
    • 可以使用如下参数查看类加载和卸载信息
      • -verbose:class
      • -XX:+TraceClass-Loading
      • -XX:+TraceClassUnLoading
    • 在大量使用反射动态代理CGLib字节码框架,动态生成JSP以及OSGi这类频繁自定义类加载器的场景中,通常都需要JVM具备类型卸载的能力,以保证不会对method area造成过大的内存压力

Hotspotrun-time constant pool的回收策略很明确,只要run-time constant pool中的常量没有被任何地方引用,就可以被回收. 回收废弃常量与回收heap area中java对象非常类似

面试题

  • 百度
    • 三面:说一下JVM内存模型,有哪些区?分别是干什么的?
      • 分代
        • 新生代:分配对象空间的位置
          • Eden区:第二分配位置
            • TLAB: 小块线程私有,第一分配位置
          • 2个Survivor区: 两块大小相同的区域自适应内存分配可能大小不同
        • 老年代:生命周期较长或者超大对象分配
        • 元空间:
          • 类型信息
          • field
          • 方法信息
          • static final全局常量
          • run-time constant pool
          • JIT代码缓存+
  • 蚂蚁金服
    • Java8的内存分代改进:元空间直接使用物理内存,取消permGen永久代
    • JVM内存分哪几个区,每个区的作用是什么?
    • 一面:JVM内存分布/内存结构,栈和堆的区别,堆的结构,为什么两个survivor
      • stack area 线程私有, heap area 线程共享, 两个survivor区,碎片整理,复制算法,优化GC
    • 二面:Eden区和survivor区的比例分配
      • -XX:SurvivorRatio=8 8:1:1
      • Eden过大,survivor小,MinorGC作用会被削弱,可能生命周期较小的对象更大概率晋升到Old
      • Eden过小,MinorGC次数会增加,影响应用效率
  • 小米
    • JVM内存分区,为什么要有新生代和老年代?
      • 优化GC性能
      • 不分代,所有对象放一起,每次GC都要全部检查一遍STW
      • 分代,新对象都在一起,优先MinorGC新对象,不需要经常FullGC
  • 字节跳动
    • 二面:java的内存分区
    • 二面:讲一讲JVM运行时数据库区
    • 什么时候对象会进入老年代
      • 达到阈值,从Survivor from区晋升到Old
      • Survivor区中某一年龄的对象总大小超过Survivor空间的一半,不需要等达到阈值,大于等于年龄的对象一起晋升到Old
      • 大对象,Eden区放不下,直接放在Old
  • 京东
    • JVM内存结构,Eden和Survivor比例
    • JVM内存为什么要分成新生代,老年代,持久代,新生代中为什么要分为Eden区和Survivor
  • 天猫
    • 一面:JVM内存模型以及分区,需要详细到每个区放什么
    • 一面:JVM的内存模型,java8做了什么修改
  • 拼多多
    • JVM内存分哪几个区,每个区的作用是什么
  • 美团
    • java内存分配
    • jvm的永久代中会发生垃圾回收?
    • 一面:jvm内存分区,为什么要有新生代和老年代