美文网首页
JVM扩展(2):常量池引发的一连串思考(上)

JVM扩展(2):常量池引发的一连串思考(上)

作者: _River_ | 来源:发表于2021-05-10 09:10 被阅读0次
      百度百科:
      https://baike.baidu.com/item/%E5%B8%B8%E9%87%8F%E6%B1%A0/3855836?fr=aladdin
    
      先暂时认为:JDK1.8 的所有类型常量池都存储在元空间(方法区) (后续会有解释)
    
    目录
      1:什么叫常量池
      2:常量结构实例说明
      3:字符串常量特殊讲解
      4:拘留字的实际使用 与  常量池的作用
      5:有什么类型常量池
      6:JVM在JDK 1.6 1.7 1.8到达经历了什么 (不纠结)
      7:为什么永久代要替换成元空间 
      8:验证移除是否已经永久代:
    
    1:什么叫常量池
      常量池在java用于保存在编译期已确定的,已编译的class文件中的一份数据。
      它包括了关于类,方法,接口等中的常量,也包括字符串常量,如String s = "java"这种申明方式;
      当然也可扩充,执行器产生的常量也会放入常量池,故认为常量池是JVM的一块特殊的内存空间。
    
      Java是一种动态链接的语言,常量池的作用非常重要,常量池中
      1:字面值:包含代码中所定义的各种基本类型(如int、long等等)和对象型(如String及数组)的常量值
      2:符号引用 ,以文本形式出现的比如:
            类和接口的全限定名;
            字段的名称和描述符;
            方法的名称和描述符。
    
     所以,与Java语言中的所谓“常量”不同,class文件中的“常量”内容很丰富,这些常量集中在class中的一个区域存放,一个紧接着一个,这里就称为“常量池”。
    
    2:常量结构实例说明
    在Java程序中,有很多的东西是永恒的,不会在运行过程中变化。
    
    字面值 与 符号应用  举例(不需要纠结是哪一个):
    
        比如一个类的名字:ClassTest
        比如一个类字段的名字/所属类型:String name ="HeSuiJIn"
        比如一个常量,int age =18
        比如一个类方法的名字/返回类型/参数名与所属类型:setName (String name 
    
     public class ClassTest {
            private String name ="HeSuiJIn";
            private final int age =18 ;
            public void setName (String name ){...}
        }
    
    而这些在JVM解释执行程序的时候是非常重要的。
    那么编译器将源程序编译成class文件后,会用一部分字节分类存储这些代码。而这些字节我们就称为常量池。
    事实上,只有JVM加载class后,在方法区中为它们开辟了空间才更像一个“池”
    
    3:字符串常量特殊讲解
    在Java源代码中的每一个字面值字符串,都会在编译成class文件阶段,形成标志号为8(CONSTANT_String_info)的常量表 。 
    
    当JVM加载 class文件的时候,会为对应的常量池建立一个内存数据结构,并存放在方法区中。
    同时JVM会自动为CONSTANT_String_info常量表中的字符串常量的字面值 在堆中创建新的String对象(intern字符串对象 ,又叫拘留字符串对象)。
    
    然后把CONSTANT_String_info常量表的入口地址转变成这个堆中String对象的直接地址(常量池解析)。
    
    4:拘留字的实际使用 与 常量池的作用
    拘留字符串对象
    源代码中所有相同字面值的字符串常量只可能建立唯一 一个拘留字符串对象。 
    实际上JVM是通过一个记录了拘留字符串引用的内部数据结构来维持这一特性的。
    
    在Java程序中,可以调用String的intern()方法来使得一个常规字符串对象成为拘留字符串对象。
    
    (1)String s=new String("Hello world"); 
        事实上,在加载完成之后,在运行这段指令之前,JVM就已经为"Hello world"在堆中创建了一个拘留字符串
    
         值得注意的是:如果源程序中还有一个"Hello world"字符串常量,那么他们都对应了同一个堆中的拘留字符串。
                        1:局部变量s实际上存储的是new出来的堆对象地址,
                        2:然后用这个拘留字符串的值来初始化堆中用new指令创建出来的新的String对象,
    
    (2)String s="Hello world";:
        这跟(1)中创建指令有很大的不同,此时局部变量s存储的是早已创建好的拘留字符串的堆地址。
    

    java常量池技术  java中的常量池技术,是为了方便快捷地创建某些对象而出现的,
    当需要一个对象时,就可以从池中取一个出来(如果池中没有则创建一个),
    则在需要重复创建相等变量时节省了很多时间。
    常量池其实也就是一个内存空间,常量池存在于方法区中
    
    5:有什么类型常量池
      Java中的常量池,实际上分为两种形态:
      静态常量池(类常量池)(类文件常量池)(class constant pool) 
      运行时常量池(runtime constant pool)
    
     Java语言并不要求常量一定只能在编译期产生:
     1:运行期间也可能产生新的常量,这些常量被放在运行时常量池中。  
     2:类加载后,同时静态常量池中的数据也会在运行时常量池中存放。
     
     这里所说的运行期间也可能产生新的常量包括:
     1:基本类型的包装类
     2:String(也可以通过String.intern()方法可以强制将String放入运行时常量池中)
    

    字符串常量池(String pool)(这里先了解)(不纠结) 下节会详细说明
    又称为全局常量池  存放字符串的指针
    

      由于随着JDK版本的变迁  存储常量池的地方不断的改变 导致各种概念鱼龙混杂
    
      根据网上的各种说常量池所存储的位置可能为 永久代 方法区  元空间 堆
      所以需要先弄清楚这些的概念
    
    6:JVM在JDK 1.6 1.7 1.8到底经历了什么 (不纠结)
    这里仅针对JDK 1.8 做详细讲解
    千万不要纠结:既然你不是使用 JDK1.6  JDK1.7 
                      那么现在不要把所有概念都混在一起,这样更难理解。
    
    注意:
        我们只关注  常量池 永久代 方法区  元空间 堆 这几个关键词
    

    JDK1.6 
    永久代又称为方法区
    1:存放Class加载相关信息 
    2:静态常量池:存放 符号引用 与 各种字面量
    3:运行时常量池
    4:字符串常量池
    

    JDK1.7
    永久代又称为方法区
    存放Class加载相关信息 
    
    其他信息逐步移除 同时被转移到堆中
    1:静态常量池:存放 符号引用 与 各种字面量
    2:运行时常量池
    3:字符串常量池
    

    JDK1.8
    永久代被完全移除   
    元空间出现 同时元空间又称为方法区
    
    元空间
    存放Class加载相关信息 
    

    从JVM的内存结构来看:
    JDK1.6到JDK1.8的过程 
    实际上就是   永久代的功能逐步被削弱 最终被完成移除的过程
    
    7:为什么永久代要替换成元空间
    关于为什么移除永久代?
        字符串存在永久代中,容易出现性能问题和内存溢出。        
        类及方法的信息等比较难确定其大小,因此对于永久代的大小指定比较困难,
        太小容易出现永久代溢出,太大则容易导致老年代溢出。        
        永久代会为 GC 带来不必要的复杂度,并且回收效率偏低。
    
     元空间的本质和永久代类似,都是对JVM规范中方法区的实现。  
     不过元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。
     因此,默认情况下,元空间的大小仅受本地内存限制,但可以通过以下参数来指定元空间的大小:
     -XX:MetaspaceSize,初始空间大小
     -XX:MaxMetaspaceSize 最大空间
     
     也因此有时候我们把元空间 也叫做 方法区
    
    8:验证移除是否已经永久代
        我们可以通过一段程序来比较 JDK 1.6 与 JDK 1.7及 JDK 1.8 的区别,以字符串常量为例:
    
    public class StringOomMock {
        static String  base = "string";
        public static void main(String[] args) {
            List<String> list = new ArrayList<String>();
            for (int i=0;i< Integer.MAX_VALUE;i++){
                String str = base + base;
                base = str;
                list.add(str.intern());
            }
        }
    }
    
    从上述结果可以看出,
    JDK 1.6下,会出现“PermGen Space”的内存溢出,
    而在 JDK 1.7和 JDK 1.8 中,会出现堆内存溢出,
    并且 JDK 1.8中 PermSize 和 MaxPermGen 已经无效。
    因此,可以大致验证 JDK 1.7 和 1.8 将字符串常量由永久代转移到堆中
    

    相关文章

      网友评论

          本文标题:JVM扩展(2):常量池引发的一连串思考(上)

          本文链接:https://www.haomeiwen.com/subject/gmyydltx.html