您的位置 首页 java

jvm中常量池在方法区?原来我错了这么久

前言:

昨天写了篇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时,将常量池放入了堆中,下面我们上代码验证下

jvm中常量池在方法区?原来我错了这么久

结果如下:

jvm中常量池在方法区?原来我错了这么久

如何降低oom的风险?

1.7中,常量池被移入堆中,简单的说就是让常量池可以被垃圾回收器回收。而1.8则更近一步,直接取消永久代,改用元空间。

元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存(也就是说jvm可以使用外边的内存)。因此,默认情况下,元空间的大小仅受本地内存限制,但可以通过以下参数来指定元空间的大小

jvm中常量池在方法区?原来我错了这么久

当然,移除永久代而使用元空间的原因并不只有这一个,另一个重要原因是要合并HotSpot和JRockit的代码,JRockit从来没有一个叫永久代的东西, 但是运行良好, 也不需要开发运维人员设置这么一个永久代的大小.

内存区别见下图

1.7中

jvm中常量池在方法区?原来我错了这么久

1.8中

jvm中常量池在方法区?原来我错了这么久

总结

1、字符串存在永久代中,容易出现性能问题和内存溢出。

2、类及方法等信息比较难确定大小,因此很难指定永久代的大小指定,太小会出现永久代溢出,太大则会导致老年代溢出,而关于内存区域分类我们下期再说。

3、永久代会给 GC 带来不需要的复杂度,且回收率较低。

4. HotSpot 与 JRockit 可能会合二为一

最后我们来简单看下常量池和gc关系,以常见的String面试题举例:

运行结果如下:

由此可见,new创建的字符串的弱引用会被gc回收掉,而只想字符串的弱引用则不会被gc回收掉,这说明new关键字创建的字符串对象如果不可达了会被gc回收,而字符串字面量创建的字符串对象则不会,因为常量池中还持有对该字面量字符串的引用。

文章来源:智云一二三科技

文章标题:jvm中常量池在方法区?原来我错了这么久

文章地址:https://www.zhihuclub.com/194998.shtml

关于作者: 智云科技

热门文章

网站地图