昨天写了篇jvm内存模型的文章,说常量池在方法区中,结果被大佬啪啪打脸,本日我猖獗百度,把结果汇总下。
先说结论:
Jdk1.6及之前: 有永久代, 常量池1.6在方法区Jdk1.7: 有永久代,但已经逐步"去永久代",常量池1.7在堆Jdk1.8及之后: 无永久代,常量池1.8在元空间
1.7和1.8的差别比较大,移除了方法区,也便是永久区,改用元空间来代替,那么元空间是什么?
元空间:
名字由来是由于里面存储的是类的元数据信息而元数据又是什么?
元数据(Meta Date)便是关于数据的数据,或者叫做用来描述数据的数据或者叫做信息的信息。
听起来很绕口,确实这些定义都很是抽象,我们可以把元数据大略的理解成,最小的数据单位。元数据可以为数据解释其元素或属性(名称、大小、数据类型、等),或其构造(长度、字段、数据列),或其干系数据(位于何处、如何联系、拥有者)。
为什么要加这么一个元空间?
在方法区还未移除时,常常会发生一个非常:"Out Of Memory",顾名思义内存溢出,为什么?
1.7以前,方法区位于jvm中,而方法区中含有永久代,当涌现于大量Class或者jsp页面,或者采取cglib等反射机制的情形,由于上述情形会产生大量的Class信息存储于方法区。
此时方法区中的数据:
(1)类加载器引用(ClassLoader)
(2)运行时常量池:包含所有常量、字段引用、方法引用、属性
(3)字段数据:每个字段的名字、类型(如类的全路径名、类型或接口) 、润色符(如public、abstract、final)、属性等
(4)方法数据:每个方法的名字、返回类型、参数类型(按顺序)、润色符、属性
(5)方法代码:每个方法的字节码、操作数栈大小、局部变量大小、局部变量表、非常表和每个非常处理的开始位置、结束位置、代码处理在程序计数器中的偏移地址、被捕获的非常类的常量池索引
此时的常量池大小固定,不能根据运行的须要扩大,也不能被gc,以是当过多的常量尤其是字符串也会导致方法区溢出,
为理解决此问题,1.7时,将常量池放入了堆中,下面我们上代码验证下
结果如下:
如何降落oom的风险?
1.7中,常量池被移入堆中,大略的说便是让常量池可以被垃圾回收器回收。而1.8则更近一步,直接取消永久代,改用元空间。
元空间与永久代之间最大的差异在于:元空间并不在虚拟机中,而是利用本地内存(也便是说jvm可以利用外边的内存)。因此,默认情形下,元空间的大小仅受本地内存限定,但可以通过以下参数来指定元空间的大小
当然,移除永久代而利用元空间的缘故原由并不但有这一个,另一个主要缘故原由是要合并HotSpot和JRockit的代码,JRockit从来没有一个叫永久代的东西, 但是运行良好, 也不须要开拓运维职员设置这么一个永久代的大小.
内存差异见下图
1.7中
1.8中
总结:
1、字符串存在永久代中,随意马虎涌现性能问题和内存溢出。
2、类及方法等信息比较难确定大小,因此很难指定永久代的大小指定,太小会涌现永久代溢出,太大则会导致老年代溢出,而关于内存区域分类我们下期再说。
3、永久代会给 GC 带来不须要的繁芜度,且回收率较低。
4. HotSpot 与 JRockit 可能会合二为一
末了我们来大略看下常量池和gc关系,以常见的String口试题举例:
运行结果如下:
由此可见,new创建的字符串的弱引用会被gc回收掉,而只想字符串的弱引用则不会被gc回收掉,这解释new关键字创建的字符串工具如果不可达了会被gc回收,而字符串字面量创建的字符串工具则不会,由于常量池中还持有对该字面量字符串的引用。