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 area
、heap area
、method area
交互关系Person p=new Person();
Person
类信息 保存在method area
new Person()
对象实例保存在heap area
- 如果此行代码在方法内部,则
p
是一个reference
引用类型保存在stack area
的local 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 -server
的JVM
来说,其默认的-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
即时编译器编译后的代码缓存
类型信息:class
类、interface
接口、enum
枚举、annotation
注解等
- 这个类型的完整有效名称包名.类名
- 这个类型
直接父类
的完整有效名对于interface
或java.lang.Object
都没有父类 - 这个类型的修饰符
public
、abstract
、final
的某个子集 - 这个类型
直接接口
的一个有序列表
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 Stack
、Local Variables
及大小abstract
和native
方法除外 - 异常表
abstract
和native
除外: 每个异常处理的开始位置
、结束位置
、代码处理在Program Counter Register中
的偏移地址、被捕获的异常类的常量池索引
non-final static
变量静态变量/类变量:随着JDK
变化,存储位置会变化
- 静态变量和类关联在一起,随着类的加载而加载,他们称为类数据在逻辑上的一部分
- 类变量被类的所有实例共享,即使没有类实例也可以访问
static final
全局常量
- 被声明为
final
的类变量的处理方法不同,每个全局常量在编译期分配
run-time constant pool
运行时常量池
Constant Pool Table
是class
文件的一部分,用于存放编译期生成的各种字面量与符号引用Constant Pool Table
在类加载后存放到Method Area
的Runtime Constant Pool
run-time constant pool
,在加载类和接口到jvm
后,就会创建对应的Runtime Constant Pool
JVM
为每个已加载的类型类或接口都维护一个Runtime Constant Pool
,Runtime 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
字段、method
、interface
等描述信息外,还包含Constant Pool Table
常量池表,包括各种字面量和对类型、field
、method
的符号引用
字节码文件为什么需要Constant Pool Table
常量池表
一个Java源文件,编译后产生一个字节码文件。字节码需要数据支持,通常这种数据会很大,以至于不能直接存在字节码里,而是换另一种存储方式存在Constant Pool Table
,字节码使用指向Constant Pool Table
的符号引用。这样可以大大减小字节码文件的大小
在JVM stack
的Dynamic 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 area
和Permanent Generation space
并不等价,仅对Hotspot JVM
而言两者等价JVM规范
对如何实现methad area
不做统一要求,BEA
的JRockit
和IBM
的J9
中不存在Permanent Generation space
的概念Permanent Generation space
导致java程序更容易OOM
超过-XX:MaxPermSize
上限
JDK8
完全废弃Permanent Generation space
的概念,改用JRockit
、J9
一样在本地内存中实现MetaSpace
来代替MetaSpace
的本质和Permanent Generation space
类似,都是对JVM规范
中method area
的实现MeatSpace
不在JVM
设置的内存中,而是直接使用本地物理内存,可以设置的更大,更不容易OOM
PermGenSpace
在JVM
设置的内存中,所以容易OOM
根据JVM规范
,如果methad area
无法满足新的内存分配需求时,将抛出OOM
Hotspot
中Method area
变化
jdk1.6
及之前有permanent generation
永久代- 永久代保存信息JVM内存
- 类型信息
field
字段- 方法信息
non-final static
变量静态变量static final
全局常量run-time constant pool
运行时常量池StringTable
在运行时常量池内JIT
代码缓存
- 永久代保存信息JVM内存
jdk1.7
:有permanent generation
,逐步”去永久代“,将StringTable
、non-final static
变量静态变量从permanent generation
移到Heap Area
- 永久代保存信息JVM内存
- 类型信息
field
字段- 方法信息
static final
全局常量run-time constant pool
运行时常量池- JIT代码缓存
heap area
保存JVM内存non-final static
变量静态变量StringTable
- 永久代保存信息JVM内存
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
使用Metaspace
和PermGenSpace
最大区别在于:Metaspace
并不在JVM中,而是使用本地物理机内存,因此默认情况下,Metaspace
的大小仅受本地内存限制
对PermanentGenerationSpace
调优很困难FullGC
StringTable
为什么要调整
JDK7中将StringTable
放到heap area
中,因为PermGenSpace
的回收效率很低,在FullGC
的时候才会触发. 而FullGC
是Old区
空间不足、PermGenSpace
不足时才会触发,导致StringTable
回收效率不高,而开发中会有大量的字符串被创建,回收效率低会更容易导致PermGenSpace
空间不足。放在heap area
能即时回收内存.
non-final static
变量放在哪里
new
出的实例对象都在heap area
对象引用存放位置
- 非静态属性放
heap area
- 方法内的局部变量,在
stack frame
的local 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
中不存在该类及任何派生子类的实例 - 加载该类的类加载器已经被回收,这个条件除非是经过精心设计的可替换类加载器的场景,如
OSGi
、JSP
的重加载等,否则通常很难达成 - 该类对应的
java.lang.Class
对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法
- 该类所有的实例都已经被回收,也就是
JVM
被允许对满足上述三个条件的无用类进行回收,这里说的仅仅是被允许,而并不是和对象一样,没有引用就必然会回收,关于是否要对类型进行回收- Hotspot提供了
-Xnoclassgc
参数进行控制 - 可以使用如下参数查看类加载和卸载信息
-verbose:class
-XX:+TraceClass-Loading
-XX:+TraceClassUnLoading
- 在大量使用
反射
、动态代理
、CGLib
等字节码框架,动态生成JSP
以及OSGi
这类频繁自定义类加载器的场景中,通常都需要JVM
具备类型卸载的能力,以保证不会对method area
造成过大的内存压力
- 回收的条件
Hotspot
对run-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:1Eden
过大,survivor
小,MinorGC
作用会被削弱,可能生命周期较小的对象更大概率晋升到Old
Eden
过小,MinorGC
次数会增加,影响应用效率
- 小米
- JVM内存分区,为什么要有新生代和老年代?
- 优化
GC
性能 - 不分代,所有对象放一起,每次GC都要全部检查一遍
STW
- 分代,新对象都在一起,优先
MinorGC
新对象,不需要经常FullGC
- 优化
- JVM内存分区,为什么要有新生代和老年代?
- 字节跳动
- 二面:java的内存分区
- 二面:讲一讲JVM运行时数据库区
- 什么时候对象会进入老年代
- 达到阈值,从
Survivor from
区晋升到Old
区 Survivor
区中某一年龄的对象总大小超过Survivor
空间的一半,不需要等达到阈值,大于等于年龄的对象一起晋升到Old
区- 大对象,
Eden
区放不下,直接放在Old
- 达到阈值,从
- 京东
- JVM内存结构,Eden和Survivor比例
- JVM内存为什么要分成新生代,老年代,持久代,新生代中为什么要分为
Eden
区和Survivor
- 天猫
- 一面:JVM内存模型以及分区,需要详细到每个区放什么
- 一面:JVM的内存模型,java8做了什么修改
- 拼多多
- JVM内存分哪几个区,每个区的作用是什么
- 美团
- java内存分配
- jvm的永久代中会发生垃圾回收?
- 一面:jvm内存分区,为什么要有新生代和老年代