常量池的分类

一说到常量池,感觉都能讲几句,常量池位于方法区,存放类变量、字符串等……这种说法都是一知半解,如果详细介绍常量池,至少得先说出是哪个版本的JDK以及哪个虚拟机,因为每个版本的内存分配实现方式是不一样的。此处我以JDK8和hotspot虚拟机做个总结。

首先常量池的物理位置:1、方法区(运行时常量池);2、堆中也有一部分属于常量池

方法区常量池——运行时常量池

在这里插入图片描述
在这里插入图片描述

JDK8方法区在元数据内存,不占用堆内存,也就是说你用-Xms -Xmx指定的堆内存和方法区没关系,这个元数据空间直接使用的是你的操作系统的物理内存,这个大小也是可以指定的。
(附:设置MetaSpace
·-XX:M axM etaspaceSize:设置元空间最大值,默认是-1,即不限制,或者说只受限于本地物理内存
大小。
·-XX:M etaspaceSize:指定元空间的初始空间大小,以字节为单位,达到该值就会触发垃圾收集
进行类型卸载,同时收集器会对该值进行调整:如果释放了大量的空间,就适当降低该值;如果释放
了很少的空间,那么在不超过-XX:M axM etaspaceSize(如果设置了的话)的情况下,适当提高该
值。
默认情况下只要你的内存条的内存足够大,你可以无限制的加载Class文件,如果想测试Metaspace内存溢出,可以通过自定义类加载来无限制加载一个类)
.方法区存的是class字节码文件,而不是Class这个类所代表的的对象,Class代表的对象是在堆中!!!!对象都在堆中!
Class文件是一组以8个字节为基础单位的二进制流,,它里面有一部分也称为常量池,存放的是字面量(Literal)和符号引用(Symbolic References),下图特指的是方法区的常量池!!
在这里插入图片描述
下面做个试验验证一下上边的截图看看,可以看到Constant pool中有符号引用,包括字段的名字,全限定的包名,字符串常量,final 的常量等,

  1. 使用javap -verbose 可以查看常量池:
    在这里插入图片描述
  2. 直接查看class字节码文件,可以看到字节码文件中也是可以看到字段名、字符串常量等等
    在这里插入图片描述
    总结:
    静态常量池指的是每个class二进制文件中的那个静态存储结构,每个class文件都有一个!!!
    放到文件中静态常量池中的内容虚拟机用不了,所以需要加载,加载就是把静态常量池中的内容放到运行时常量池中,同时生成一个Class对象,这样我们才可以使用那个class文件所代表的的类!
    在这里插入图片描述

温馨提示: 方法中定义的局部变量字段名也会存在文件的静态常量池中!!!如果方法中定义一个字段,但是没有初始化,在常量池中是没有的。还有数字的字面量在常量池中比较特殊,参考2.4章节
在这里插入图片描述

堆中常量池

这个说法有些拗口,因为大家中的印象里常量池是在方法区的,这里注意理解以下《深入理解jvm虚拟机》书中作者说了,方法区只是一个逻辑上的称呼,实际上方法区不仅仅包含上边说的元数据内存,还包含一部分堆内存,至于为何要把一部分堆内存逻辑上划分到元数据空间,我也不懂。

类变量在类加载的“准备”阶段,在堆中分配内存(这块内存在逻辑上属于方法区的一部分):
在这里插入图片描述

证明类常量的物理存储位置是属于堆内存,如下,在static代码块写个死循环,不停的给产生新的 i 值,此处我特意不用字符串同时给堆设置个固定值,方便查看gc。

// 设置虚拟机参数 -Xms10m -Xmx10m -XX:+PrintGCDetails
public class Staff {
    static long i = 0;

    static {
        if (true) {
            while (true) {
                i++;
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public static void main(String[] args) {
        System.out.println("我不会被执行");
    }
}

使用JDK自带的虚拟机调试工具VisualVM,看下在Visual GC可以看到Eden区在不停的增长,然后触发GC,然后再增长,一直循环下去。
在这里插入图片描述
通过上面试验我们证明了类变量,在类加载的“准备”阶段确实是在堆内存给类变量分配的。
在这里插入图片描述

在控制台也可以看到新生代在不停的GC

补充说明

网上有很多把字符串常量池、静态常量池、运行时常量池放一起比较,看了上边的介绍答案呼之欲出。

静态常量池,指的是class二进制文件中保存的某一些内容。把它叫做“池”,这个说法个人认为不妥,一说到常量池,指的应该是内存中的一部分。《深入理解java虚拟机》作者周志明把它称作“静态存储结构”这个说法比较好
在这里插入图片描述
运行时常量池 个人理解就是方法区的一块内存,这个内存用来存储class文件解析后的内容的,这个是内存是整个虚拟机共用一个。
所以结论就是:静态常量池,与其说是“池”不如说是一个“静态存储结构”,是每个类都有一个。运行时常量池是整个JVM共享一个。

好文推荐

常量池介绍

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐