Java 8新特性 - (14)Hotspot删除永久代
文章目录
JDK 6、JDK 7、JDK 8 内存模型演变
这些版本的JVM内存模型主要有以下差异:
- JDK 6:有永久代,静态变量存放在永久代上。
- JDK 7:有永久代,但已经把字符串常量池、静态变量,存放在堆上。逐渐地减少永久代的使用。
- JDK 8:无永久代,运行时常量池、类常量池,都保存在元数据区,也就是常说的元空间。但字符串常量池仍然存放在堆上。
内存模型各区域概要
程序计数器
- 较小的内存空间、线程私有,记录当前线程所执行的字节码行号。
- 如果执行 Java 方法,计数器记录虚拟机字节码当前指令的地址,本地方法则为空。
- 这一块区域没有任何 OutOfMemoryError 定义。
Java虚拟机栈
- 每一个方法在执行的同时,都会创建出一个栈帧,用于存放局部变量表、操作数栈、动态链接、方法出口、线程等信息。
- 方法从调用到执行完成,都对应着栈帧从虚拟机中入栈和出栈的过程。
- 最终,栈帧会随着方法的创建到结束而销毁。
本地方法栈
- 本地方法栈与Java虚拟机栈作用类似,唯一不同的就是本地方法栈执行的是Native方法,而虚拟机栈是为JVM执行Java方法服务的。
- 与 Java 虚拟机栈一样,本地方法栈也会抛出 StackOverflowError 和 OutOfMemoryError 异常。
- Java 8 HotSpot虚拟机直接就把本地方法栈和虚拟机栈合二为一。
堆和元空间
Java 8 JVM 的内存结构主要由三大块组成:堆内存、元空间和栈,Java 堆是内存空间占据最大的一块区域。
- Java 堆,由年轻代和年老代组成,分别占据1/3和2/3。
- 而年轻代又分为三部分,Eden、From Survivor、To Survivor,占据比例为8:1:1,可调。
- 这里特意画出了元空间,也就是直接内存区域。在 Java 8 之后就不在堆上分配方法区了。
- 元空间从虚拟机Java堆中转移到本地内存,默认情况下,元空间的大小仅受本地内存的限制,说白了也就是以后不会因为永久代空间不够而抛出OOM异常出现了。Java 8以前版本的 class和JAR包数据存储在 PermGen下面 ,PermGen 大小是固定的,而且项目之间无法共用,公有的 class,所以比较容易出现OOM异常。
常量池
从 Java 7开始把常量池从永久代中剥离,直到 Java 8 去掉了永久代。而字符串常量池一直放在堆空间,用于存储字符串对象,或是字符串对象的引用。
为什么废弃永久代(PermGen)
参照官方说明 JEP122:http://openjdk.java.net/jeps/122:
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.
即:移除永久代是为融合HotSpot JVM与 JRockit VM而做出的努力,因为JRockit没有永久代,不需要配置永久代。
同时,在实际使用过程中,由于永久代内存经常不够用或发生内存泄露,容易爆出异常java.lang.OutOfMemoryError: PermGen。这里的 “PermGen space”其实指的就是方法区。不过方法区和“PermGen space”又有着本质的区别。前者是 JVM 的规范,而后者则是 JVM 规范的一种实现,并且只有 HotSpot 才有 “PermGen space”,而对于其他类型的虚拟机,如 JRockit(Oracle)、J9(IBM) 并没有“PermGen space”。由于方法区主要存储类的相关信息,所以对于动态生成类的情况比较容易出现永久代的内存溢出。
深入理解元空间(Metaspace)
元空间的内存大小
元空间是方法区的在HotSpot jvm 中的实现,方法区主要用于存储类的信息、常量池、方法数据、方法代码等。方法区逻辑上属于堆的一部分,但是为了与堆进行区分,通常又叫“非堆”。
元空间的本质和永久代类似,都是对JVM规范中方法区的实现。不过元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。,理论上取决于32位/64位系统可虚拟的内存大小。可见也不是无限制的,需要配置参数。
常用配置参数
- MetaspaceSize
初始化的Metaspace大小,控制元空间发生GC的阈值。GC后,动态增加或降低MetaspaceSize。在默认情况下,这个值大小根据不同的平台在12M到20M浮动。使用Java -XX:+PrintFlagsInitial
命令查看本机的初始化参数 - MaxMetaspaceSize
限制Metaspace增长的上限,防止因为某些情况导致Metaspace无限的使用本地内存,影响到其他程序。在本机上该参数的默认值为4294967295B(大约4096MB)。 - MinMetaspaceFreeRatio
当进行过Metaspace GC之后,会计算当前Metaspace的空闲空间比,如果空闲比小于这个参数(即实际非空闲占比过大,内存不够用),那么虚拟机将增长Metaspace的大小。默认值为40,也就是40%。设置该参数可以控制Metaspace的增长的速度,太小的值会导致Metaspace增长的缓慢,Metaspace的使用逐渐趋于饱和,可能会影响之后类的加载。而太大的值会导致Metaspace增长的过快,浪费内存。 - MaxMetasaceFreeRatio
当进行过Metaspace GC之后, 会计算当前Metaspace的空闲空间比,如果空闲比大于这个参数,那么虚拟机会释放Metaspace的部分空间。默认值为70,也就是70%。 - MaxMetaspaceExpansion
Metaspace增长时的最大幅度。在本机上该参数的默认值为5452592B(大约为5MB)。 - MinMetaspaceExpansion
Metaspace增长时的最小幅度。在本机上该参数的默认值为340784B(大约为330KB)。
参考: