JVM之方法区
方法区放什么
对于每一个加载的类型,会在方法区中保存以下信息:
- 类及其父类的全限定名(类的全路径名)(java.lang.Object没有父类)
- 类的类型(Class or Interface)
- 访问修饰符(public, abstract, final)
- 实现的接口的全限定名的列表
- 常量池
- 字段信息
- 方法信息
- 静态变量
- ClassLoader引用
- Class引用
对于每一个字段,会在方法区中保存以下信息(字段声明顺序也会保存):
- 字段名
- 字段的类型
- 字段的修饰符(public, private , protected, static, final, volatile, transient)
对于每一个方法,会在方法区中保存以下信息(方法声明顺序也会保存):
- 方法名
- 方法返回类型(或void)
- 参数信息
- 方法修饰符(public, private, protected , static, final, synchronized, native, abstract)
如果方法不是抽象方法并不是本地方法(Native Method),还会保存以下信息:
- 方法的字节码
- 本地变量表及操作数栈的大小
- 异常表
方法区注意的地方
- 默认64M
- 当OutOfMemoryError时,可以修改MaxPermSize
- 需要多大的永久代空间,取决于类的数量、方法的大小、常量池的大小
方法区的缺点
它的大小是在启动时固定好的,很难进行优化,-XX:MaxPermSize应该设置多大
方法区特点
- 方法区是线程安全的。由于所有的线程都共享方法区,所以,方法区里的数据访问必须被设计成线程安全的。例如,假如同时有两个线程都企图访问方法区中的同一个类,而这个类还没有被装入JVM,那么只允许一个线程去装载它,而其它线程必须等待
- 方法区的大小不必是固定的,JVM可根据应用需要动态调整。同时,方法区也不一定是连续的,方法区可以在一个堆(甚至是JVM自己的堆)中自由分配。
- 方法区也可被垃圾收集,当某个类不在被使用(不可触及)时,JVM将卸载这个类,进行垃圾收集
对方法区的另一种理解
方法区也是所有线程共享。主要用于存储类的信息、常量池、方法数据、方法代码等。方法区逻辑上属于堆的一部分,但是为了与堆进行区分,通常又叫“非堆”。 关于方法区内存溢出的问题会在下文中详细探讨。
永久代
JDK 1.6 还在使用永久代,当类或方法过多,就会导致永久代内存溢出。最典型的场景就是,在 jsp 页面比较多的情况,容易出现永久代内存溢出。
永久代的满了或者超过临界值,也会触发Full GC,而永久代也会被回收的。这就是为什么正确的永久代大小对避免Full GC是非常重要的原因。
代码
package com.paddx.test.memory;
import java.io.File;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.List;
public class PermGenOomMock{
public static void main(String[] args) {
URL url = null;
List<ClassLoader> classLoaderList = new ArrayList<ClassLoader>();
try {
url = new File("/tmp").toURI().toURL();
URL[] urls = {url};
while (true){
ClassLoader loader = new URLClassLoader(urls);
classLoaderList.add(loader);
loader.loadClass("com.paddx.test.memory.Test");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
结果
永久代配置
PermSize
MaxPermSize
元空间
JDK1.8放弃了方法区,将其改成元空间,最大的区别是:元空间并不在虚拟机中,而是使用本地内存。因此,默认情况下,元空间的大小仅受本地内存限制。
内存溢出结果
元空间配置
-XX:MetaspaceSize
-XX:MaxMetaspaceSize
-XX:MinMetaspaceFreeRatio