zoukankan      html  css  js  c++  java
  • java8中字符串常量以及GC相应处理机制

    1,常量池

    1.1, class文件常量池

    ​ class文件常量池位于class文件中

    ​ class文件头4个字节称为魔数,魔数后面的4个字节为文件版本号,而版本号之后的就是常量池的入口。该常量池用于存放编译器生成的各种字面量和符号引用,字面量就是所谓的常量,如字符串,final修饰的常量值等。而符号引用则是用来描述引用目标的,如类和接口的全限定名,方法和字段的名称和描述符。此时符号引用并不会存储最终内存布局信息。

    ​ class文件中方法和字段均需要引用CONSTANT_Utf8型常量描述名称,该类型的最大长度也就是java中方法和字段名的长度,而该类型的长度用2个字节表示,最大值为65535,所以java中如果定义了超过64kb(2^16*1byte,utf8中一个字符占一个字节)的英文字符的变量或方法名,将无法编译。

    1.2, 运行时常量池

    ​ 当类或接口创建时,它的class中的常量池会被用来构造运行时常量池,常量池中的符号引用会被解析成具体的内存地址。运行时常量池是jvm方法区的一部分,它可以在运行时将符号引用解析为直接引用。

    ​ 运行时常量池位于jvm的元空间中(java8)

    1.3,字符串常量池

    ​ 字符串常量池底层实现是一个哈希表,可以通过-XX:StringTableSize参数调整大小。字符串常量池中存储的是字符串对象的引用,而字符串本身是在堆上分配的(java中的对象基本都在堆上分配)。运行时常量池初始化的时候,字面量的符号引用的初始化会用到字符串常量池。String中的intern方法可以在运行时将字符串实例加入字符串常量池。

    ​ 在java1.7以前,字符串常量池是在堆的永久代里面,大小固定,而从java1.7以后,字符串常量池则移动到java堆中了。

    验证代码如下:

    public class test {
        private static String str = "";
        public static void main(String[] args) {
            //-Xms32m -Xmx32m
            char element = 'a';
            StringBuffer sb = new StringBuffer();
            for (int i = 0; i < 1024*1024*64; i++) {
                sb.append(element);
            }
            str = sb.toString();
            System.out.println();
        }
    }
    

    JVM启动参数:-Xms32m -Xmx32m

    运行结果如下:

    Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
    	......
    	at test.test.main(test.java:17)
    

    可以发现堆发生溢出(oom),说明常量池在堆中分配。

    String.intern()

    String类的intern()方法的描述是:String类维护着一个初始化为空的字符串池,当某String实例调用intern()方法的时候,如果字符串池中已包含与此字符串相同的字符串(用equal(obj)方法判断相等),则返回池中的字符串对象。否则,将此字符串对象加入到常量池中,并返回此字符串对象的引用。对于任意两个字符串s和t,当切仅当s.equals(t)为true,s.intern() ==t.intern()才为true。所有字面值字符串和字符串赋值常量表达式都使用intern方法进行处理。

    示例:

    public class InternTest {
        public static void main(String[] args) {
            String s1 = new String("a");
            s1.intern();
            String s2 = "a";
            System.out.println(s1 == s2);
    
            String s3 = new String("b") + new String("b");
            s3.intern();
            String s4 = "bb";
            System.out.println(s3 == s4);
        }
    }
    

    运行结果如下:

    false
    true
    

    -XX:StringTableSize

    可以通过jvm参数-XX:StringTableSize来配置String常量池的大小,在java6和java7u40之前的版本,其默认大小为1009,java7u40以及之后的java8中其默认值为60013。其值要求得是素数。

    2,使用new关键字和使用字符串字面量的区别

    示例:

    String str1="abc";
    
    String str2=new String("abc");
    
    • 当使用String str="abc"这种形式的时候,JVM会在字符串常量池中创建该对象并且str1会指向这个对象;

    • 当使用String str=new String("abc")这种形式的时候,JVM会先检查在字符串常量池中是否已存在"abc",如果不存在则在常量池中创建该对象,如果已存在则不创建。除此之外,还会在堆外创建一个额外的字符串对象,并且str2会指向这个对象。

    验证代码:

    public class test {
        public static void main(String[] args) {
            String str1 = "abc";
            String str2 = new String("abc");
            System.out.println(str1);
            System.out.println(str2);
            System.out.println(str1 == str2);
        }
    }
    

    运行结果如下:

    abc
    abc
    false
    

    查看该代码的字节码,会发现两种创建字符串对象的方式的不同:

       L0
        LINENUMBER 9 L0
        LDC "abc" //从常量池加载String
        ASTORE 1
    
       L1
        LINENUMBER 10 L1
        NEW java/lang/String  //创建一个String对象
        DUP  
        LDC "abc"   //从常量池加载String
        INVOKESPECIAL java/lang/String.<init> (Ljava/lang/String;)V //调用构造方法,实例初始化
        ASTORE 2
    

    通过对比发现,使用new关键字与直接使用字符串字面量相比,多了创建对象的过程。

    3,字符串常量池与GC

    先看一个示例:

    public class test {
        public static void main(String[] args) {
            System.out.println("---------String with new--------");
            collectWeakReference(new String("dasfsafsafsafsa"));
            System.out.println("---------String with literal--------");
            collectWeakReference("dsafdsafxcdfeghg");
        }
        private static void  collectWeakReference(String obj ) {
            ReferenceQueue<String> rq = new ReferenceQueue<>();
            Reference<String> r = new WeakReference<>(obj, rq);
            obj = null;
            Reference rf;
            int gccount = 10;
            //一次System.gc()并不一定会回收A,所以要多试几次
            while((rf=rq.poll()) == null && gccount >=0) {
                System.gc();
                gccount--;
            }
            System.out.println(rf);
            if (rf != null) {//如果对象被回收则弱引用会加入引用队列
                //引用指向的对象已经被回收,存入引入队列的是弱引用本身,所以这里最终返回null
                System.out.println(rf.get());
            }
        }
    }
    

    运行结果如下:

    ---------String with new--------
    java.lang.ref.WeakReference@5a07e868
    null
    ---------String with literal--------
    null
    

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

    参考链接:

    https://netsurfingzone.com/core-java/string-constant-pool-in-java

    https://tangxman.github.io/2015/07/27/the-difference-of-java-string-pool/

    https://blog.csdn.net/zm13007310400/article/details/77534349

    https://stackoverflow.com/questions/23252767/string-pool-vs-constant-pool

    http://java-performance.info/string-intern-in-java-6-7-8/

    https://netsurfingzone.com/core-java/string-constant-pool-in-java

    https://www.cnblogs.com/gxyandwmm/p/9495923.html

    https://tech.meituan.com/2014/03/06/in-depth-understanding-string-intern.html

    https://blog.csdn.net/qq_26929957/article/details/79090406

    https://jimmy2angel.github.io/2018/10/26/ConstantPool/

    https://droidyue.com/blog/2014/12/21/string-literal-pool-in-java/

    https://blog.csdn.net/qq_31615049/article/details/81611918

    https://blog.omitol.com/2017/09/30/JVM-CP/

  • 相关阅读:
    Vue-router 路由
    第一个Vue-cli
    webpack学习
    kail拦截自己局域网
    kaii在普通用户进入root的时候,使用''su '',出现鉴定故障
    Kali Linux缺少ifconfig命令
    SpringIOC
    JavaScript图形实例:太极图
    JavaScript图形实例:玩转正方形
    JavaScript图形实例:模仿海龟作图
  • 原文地址:https://www.cnblogs.com/cord/p/11440303.html
Copyright © 2011-2022 走看看