前言:
昨天写了篇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回收,而字符串字面量创建的字符串对象则不会,因为常量池中还持有对该字面量字符串的引用。