栈、堆、方法区的交互关系
方法区的基本理解
- 方法区(Method Area)与Java堆一样,是各个线程共享的内存区域
- 方法区在JVM启动的时候被创建,并且它的实际物理内存空间中和Java堆一样可以是不连续的
- 方法区的大小,跟堆空间一样,可以选择固定大小或者可扩展
- 方法区的大小决定了系统可以保存多少个类,如果系统定义了太多的类,导致方法区溢出,虚拟机同样会抛出内存溢出的错误:java.lang.OutOfmemoryError:PermGen space或者java.lang.OutOfMemoryError:Metaspace
- 关闭JVM就会释放这个区域的内存
设置方法区大小与OOM
- JDK7及以前
- 通过-XX:PermSize来设置永久代初始分配空间。默认值是20.75M
- -XX:MaxPermSize来设定永久代最大可分配空间。32位机器默认是64M,64位机器模式是82M
- JDK8及以后
- 元数据区大小可以使用参数:-XX:MetaspaceSize和-XX:MaxMetaspaceSize指定,替代上述原有的两个参数
- 默认值依赖于平台。Windows下,-XX:MetaspaceSize是21M,-XX:MaxMetaspaceSize的值是-1,即没有限制
- 与永久代不同,如果不指定大小,默认情况下,虚拟机会耗尽所有的可用系统内存。
- 如果元数据去发生溢出,虚拟机一样会抛出异常OutOfMemoryError:Metaspace
- -XX:MetaspaceSize:设置初始的元空间大小。对于一个64位的服务器端JVM来说,其默认的-XX:MetaspaceSize是21MB。这就是初始的高水位线,一旦触及这个水位线,Full GC将会触发并卸载没用的类,(即这些类对于的类加载器不再存活),然后这个高水位线将会被重置。新的高水位线的值取决于GC后释放了多少元空间。如果释放空间不足,那么在不超过MaxMetaspaceSize时,适当提高该值。如果释放空间过多,则适当降低该值
- 如果初始化的高水位线设置过低,上述高水位线调整情况会发生很多次。通过垃圾回收器的日志可以观察到Full GC多次调用,为了避免频繁地GC,建议将-XX:MetaspaceSize设置为一个相对较高的值。
方法区中的内部结构
它用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器后的代码缓存。
运行时常量池
- 运行时常量池(Runtime Constant Pool)是方法区的一部分
- 常量池表(Constant Pool Table)是class文件的一部分,用于存放编译器生成的各种字面量与符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中。
- 运行时常量池,在加载类和接口道虚拟机后,就会创建对应的运行时常量池
- JVM为每个已加载的类型(类或接口)都维护一个常量池。池中的数据项像数组一样,是通过索引访问的。
- 当创建类或接口的运行时常量池时,如果构造运行时常量池所需的内存空间超过了方法区所能提供的最大值,则JVM会抛OutOfMemoryError异常。
方法区的演进细节
- JDK6及以前:有永久代(Permanent generation),静态变量存放在永久代上
- JDK7:有永久代,但已经逐步“去永久代”,字符串常量池、静态变量移除,保存在堆中。
- JDK8及以后:无永久代,类型信息、字段、方法、常量保存在本地内存的元空间,但字符串常量池、静态变量仍在堆。
永久代为什么要被元空间替换
- 永久代设置空间大小是很难确定
- 对永久代调优困难
StringTbale为什么要调整
jdk7中将StringTable放到堆空间中。因为永久代的回收效率很低,在full gc的时候才会触发。而full gc是老年代空间不足,永久代不足时才会触发。
这就导致StringTable回收效率不高。而我们开发中会有大量字符串被创建,回收效率低,导致永久代内存不足。放到堆里,能及时回收内存。