方法区
方法区在逻辑上虽然属于堆的一部分,但是简单的实现上可能不会选择去进行垃圾回收或者进行压缩,在Hotspot中为了区分方法区和堆,方法区又被称为非堆,所以方法区可以看做是独立于堆的一块内存空间
方法区的生命周期与JVM实例也是一致的,在JVM启动的时候就会被创建,也是各个线程共享的区域,其存储的是已被虚拟机加载的类的信息、常量、静态变量、即时编译器编译后的代码等数据,所以方法区的大小决定了系统可以保存多少个类,如果系统定义的类过多,方法区就会溢出,从而出现OOM
jdk7及之前报的是java.lang.OutOfMemoryError:PermGen space,jdk8报的是java.lang.OutOfMemoryError:Metaspace
在jdk7中方法区被称为永久代(Permanent Space),在jdk8中方法区被称为元空间(MetaSpace)
永久代和元空间可以看做是方法区的实现
元空间
元空间是jdk8中对于方法区的实现,元空间使用的不是JVM的内存,而是使用的本地内存,也就是使用的直接内存,直接内存是在java堆外的、直接向系统申请的内存空间
在jdk7的时候将字符串常量池和静态变量从永久代移到了堆中,jdk8虽然永久代去除,改为元空间,但是字符串常量池和静态变量还是在堆中
方法区存储的内容
1 | // User 存储在方法区 |
方法区存储的主要是被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码缓存等
其实就是类加载器加载之后的字节码class文件中的内容以及加载器的信息
不同的jdk版本所存放的内容是不一样的
jdk6中静态变量存储在永久代中,此时永久代中包含了类型信息、字段、方法、JIT代码缓存、静态变量(对象实例数据存储在堆空间,变量引用存储在永久代)、运行时常量池以及字符串常量池
jdk7中将字符串常量池、静态变量(对象实例数据和变量引用存储在堆空间)保存在堆中,其他信息存储在永久代
jdk8由于将永久代移除,所有类型信息、字段、方法、JIT代码缓存、运行时常量池都保存在本地内存的元空间中,但是字符串常量池、静态变量(对象实例数据和变量引用存储在堆空间)仍然存放在堆中。元空间是可以动态扩展的,默认-XX:MetaspaceSize值为21M的高水位线。一旦触及则FullGC将被触发并卸载没有用的类,然后高水位线将会重置。新的高水位线的值取决于GC后释放的元空间。如果释放的空间少,这个高水位线则上升。如果释放的空间多,则高水位线下降。
类信息
对于每个加载的类型(类class、接口interface、枚举enum、注解annotation)方法区中存储的类型信息有哪些呢
- 这个类型的的完整有效名称
- 这个类型直接父类的完整有效名
- 这个类型的修饰符
- 这个类型直接接口的一个有序列表
域信息(Filed属性信息)
方法区中存储的域信息有哪些呢?
- 保存类型的所有域的相关信息以及域的声明顺序,域的相关信息包括:域名称、域类型、域修饰符(public、private、protected、static、final、volatile、transient)
方法信息
方法区中存储的方法信息有哪些呢?
- 方法名称
- 方法返回类型
- 方法参数的数量和类型,按顺序存储
- 方法的修饰符
- 方法的字节码、操作数栈、局部变量表以及其大小(abstract和native方法除外)
- 异常表(abstract和native方法除外),每个异常处理的开始位置、结束位置、代码处理在程序计数器中的偏移地址、被捕获的异常类的常量池索引
静态变量
静态变量是属于类的,不需要对象实例,属于类数据的一部分
来看一下静态变量和静态常量的区别
1 | public class Test { |
1 | private static int count; |
可以看到静态常量在编译成字节码的时候就将值存储下来了
jdk7静态变量移至堆中
运行时常量池
什么是运行时常量池呢?在字节码文件中有一个常量池,JVM为每个已加载的类型都维护一个常量池,用于存放编译期间生成的各种字面量和符号引用,通过索引访问。这部分内容是在类加载之后存放到运行时常量池中的。
1 | // 包含有数量值、字符串值、类引用、字段引用、方法引用 |
而将字节码中的常量池加载到方法区之后,就称为运行时常量池,运行时常量池中包含有多种不同的常量,包括编译期就已经明确的数值字面量,也包括到运行期解析后才能获得的方法和字段引用,将常量池中的符号地址换为真实地址,运行时常量池相对于Class文件常量池的重要特征是具备动态性
运行期间可以通过String.intern()方法将新的常量放入池中
jdk7运行时常量池移至堆中
方法区的垃圾回收
方法区出现垃圾回收的情况很少,主要包含有两部分内容:常量池中废弃的常量和不再使用的类型
方法区的常量池存储的主要是字面量和符号引用,只要常量池中的常量没有任何地方引用,就可以被回收了
方法区的参数设置
设置方法区内存大小
-XX:MetaspaceSize
元空间初始分配空间,默认21M
-XX:MaxMetaspaceSize
元空间最大分配空间,默认是-1,没有限制
jdk7及以前的配置与jdk8不同
-XX:PermSize
永久代初始分配空间,默认20.75M
-XX:MaxPermSize
永久代最大分配空间,默认是82M