JDK8方法区及常量池的变化

Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域,所管理的内存将会包括以下几个运行时数据区域:

(图片来源于《深入理解Java虚拟机》)

方法区的定义:

jdk8虚拟机规范中对方法区的定义:
在这里插入图片描述
方法区是各个线程共享的内存区域,它存储每个类的结构信息,例如运行时常量池,字段(通过引用常量池中的常量来描述)和方法等数据,以及方法和构造函数的代码,包括用于类和实例初始化以及接口初始化的特殊方法。

其中灰色部分:方法区域是在虚拟机启动时创建的。尽管方法区域在逻辑上是堆的一部分,但是简单的实现可以选择不进行垃圾回收或压缩。该规范没有规定方法区域的位置或用于管理已编译代码的策略

永久代

HotSpot虚拟机在jdk8以前是用永久代(Permanent Generation)实现方法区,永久代和方法区这两者不是等价的,对于其他虚拟机实现,譬如BEA JRockit、IBM J9等来说,是不存在永久代的概念的。因为仅仅是当时的HotSpot虚拟机设计团队选择把收集器的分代设计扩展至方法区,或者说使用永久代来实现方法区而已,这样使得HotSpot的垃圾收集器能够像管理Java堆一样管理这部分内存,省去专门为方法区编写内存管理代码的工作。但是这样的设计现在来看是存在问题的。

永久代存在的问题:
首先,这区域的内存回收目标主要是针对常量池的回收和对类型的卸载,一般来说这个区域的回收效果比较难令人满意。

另外,随着动态类加载的情况越来越多,这块内存我们变得不太可控,为永久代设置空间大小也是很难确定的,因为这其中有很多影响因素,比如类的总数,常量池的大小和方法数量等。

在JVM启动之前可以通过设置-XX:MaxPermSize的值来控制永久代的大小;永久代的垃圾回收和老年代的垃圾回收是绑定的,一旦其中一个区域被占满,这两个区都要进行垃圾回收;由于我们可以通过‑XX:MaxPermSize 设置永久代的大小,一旦类的元数据超过了设定的大小,程序就会耗尽内存,并出现内存溢出;

在JDK7之前的HotSpot虚拟机中,纳入字符串常量池的字符串被存储在永久代中,因此导致了一系列的性能问题和内存溢出错误;

元空间

元空间(metaspace)是jdk8中方法区的实现,专门用来存元数据。和永久代不同的是元空间并不在虚拟机中,而是使用本地内存

jdk7的HotSpot,已经把原本放在永久代的字符串常量池、静态变量等移出(字符串常量和静态变量移至堆内存中),符号引用(移入到native heap中)。而到了JDK 8,终于完全废弃了永久代的概念,改用与JRockit、J9一样在本地内存中实现的元空间来代替,把JDK 7中永久代还剩余的内容(主要是类型信息)全部移到元空间中。
jdk8中的内存模型如下:
在这里插入图片描述

元空间存储的内容:

在这里插入图片描述

  1. Klass Metaspace
    Klass Metaspace就是用来存klass的,就是class文件在jvm里的运行时数据结构(而xxx.class对象其实是存在heap里)。java对象模型中的头部信息Klass ,即指向它的类型元数据的指针,对应的就是Metaspace中的klass。但是这块内存也可以没有,假如没有开启压缩指针就不会有这块内存,这种情况下klass都会存在NoKlass Metaspace里。

  2. NoKlass Metaspace
    NoKlass Metaspace专门来存klass相关的其他的内容,比如method,constantPool等,这块内存是由多块内存组合起来的,所以可以认为是不连续的内存块组成的。这块内存是必须的,虽然叫做NoKlass Metaspace,当Klass Metaspace不存在时也可以存klass的内容。

常量池

静态常量池

即*.class文件中的常量池,Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池表(Constant Pool Table),用于存放编译期生成的各种字面量与符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中。
静态常量池中主要存放两大类常量:字面量(Literal)和符号引用(Symbolic References)。字面量比较接近于Java语言层面的常量概念,如文本字符串、被声明为final的常量值等。而符号引用则属于编译原理方面的概念,主要包括下面几类常量:
·被模块导出或者开放的包
·类和接口的全限定名
·字段的名称和描述符
·方法的名称和描述符
·方法句柄和方法类型
·动态调用点和动态常量

运行时常量池

运行时常量池(Runtime Constant Pool)是方法区的一部分。jdk1.8以前存在于永久代,jdk1.8之后存在于元空间。静态常量池中的内容,在类加载后会被存放到方法区的运行时常量池中。运行时常量池相对于Class文件常量池的另外一个重要特征是具备动态性,Java语言并不要求常量一定只有编译期才能产生,也就是说,并非预置入Class文件中静态常量池的内容才能进入方法区运行时常量池,运行期间也可以将新的常量放入池中,这种特性被开发人员利用得比较多的便是String类的intern()方法。但并不是把静态常量池中所有的内容都被放入了运行时常量池,其中字符串常量池移至堆内存中、静态变量移至堆内存( Class 对象中),符号引用(移入到native heap中)。

字符串常量池

字符串常量池可以理解为是分担了部分运行时常量池的工作。和其他的对象分配一样,字符串耗费高昂的时间与空间代价,作为最基础的数据类型,大量频繁的创建字符串,极大程度地影响程序的性能,为了提高性能和减少内存开销,在实例化字符串常量的时候进行了一些优化,为字符串开辟一个字符串常量池,类似于缓存区。实现该优化的基础是因为字符串是不可变的,可以不用担心数据冲突进行共享。运行时实例创建的全局字符串常量池中有一个表,总是为池中每个唯一的字符串对象维护一个引用。

加载时,对于class文件的静态常量池,字符串字面量会进入到当前类的运行时常量池,但不会立即进入全局的字符串常量池(即在字符串常量池中并没有相应的引用,在堆中也没有对应的对象产生),字符串常量池是lazy resolve的,在第一次引用该项的ldc指令被第一次执行到的时候才会resolve,但这个过程我们可以不关注。可以认为当类加载完成后,运行代码String str1="abc"或String str2=new String(“abc”)时,字面量“abc”已经存在于字符串常量池。jdk7字符串常量池以后存在于堆中。

java中字符串的使用主要有两种情况:

  • 直接使用双引号声明:String str1=“abc”;首先jvm会直接返回该字符串的引用;
  • 使用String str2=new String(“abc”)方式,采用new关键字新建一个字符串对象时,会在堆中创建一个String(“abc”)对象实例,将字符串常量池中这个"abc"字符串的引用地址返回赋给String(“abc”)。
    在这里插入图片描述

参考:
https://www.cnblogs.com/duanxz/p/3520829.html
https://www.jianshu.com/p/92a5fbb33764
https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-2.html#jvms-2.5.4

  • 7
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值