0
点赞
收藏
分享

微信扫一扫

十二、JVM-String Table(基础篇)

一、String的基本特性

1、String的基本特性

  • String:字符串,使用一对 "" 引起来表示
String s1 = "lori" ; // 字面量的定义方式 
String s2 = new String("lori"); // new 对象的方式 
  • String声明为final的,不可被继承 String实现了Serializable接口:表示字符串是支持序列化的,可以跨进程传输字符串。实现了Comparable接口:表示String可以比较大小、排序 208.png
  • string在jdk8及以前内部定义了final char[] value用于存储字符串数据。JDK9时改为byte[] 209.png 210.png

2、为什么JDK9改变了结构

  • String类的当前实现将字符存储在char数组中,每个字符使用两个字节(16位)。从许多不同的应用程序收集的数据表明,字符串是堆使用的主要组成部分,而且,大多数字符串对象只包含拉丁字符。这些字符只需要一个字节的存储空间,因此这些字符串对象的内部char数组中有一半的空间将不会使用。
  • 我们建议改变字符串的内部表示clasš从utf - 16字符数组到字节数组+一个encoding-flag字段。新的String类将根据字符串的内容存储编码为ISO-8859-1/Latin-1(每个字符一个字节)或UTF-16(每个字符两个字节)的字符。编码标志将指示使用哪种编码。 结论:String再也不用char[] 来存储了,改成了byte [] 加上编码标记,节约了一些空间
// 之前
private final char value[];
// 之后
private final byte[] value

同时基于String的数据结构,例如StringBuffer和StringBuilder也同样做了修改

3、String的不可变性

String:代表不可变的字符序列。简称:不可变性。

  • 当对字符串变量重新赋值时,会直接新建一个字符串(或池中本来就有的),不会影响本来的字符串
  • 当对现有的字符串进行连接拼接操作时,也需要重新指定内存区域赋值,不能使用原有的value进行赋值。
  • 当调用String的replace()方法修改指定字符或字符串时,也需要重新指定内存区域赋值,不能使用原有的value进行赋值。 代码:
/**
 * String的不可变性
 *
 * @author: 陌溪
 * @create: 2020-07-11-8:57
 */
public class StringTest1 {

    public static void test1() {
        // 字面量定义的方式,“abc”存储在字符串常量池中
        String s1 = "abc";
        String s2 = "abc";
        System.out.println(s1 == s2);
        s1 = "hello";
        System.out.println(s1 == s2);
        System.out.println(s1);
        System.out.println(s2);
        System.out.println("----------------");
    }

    public static void test2() {
        String s1 = "abc";
        String s2 = "abc";
        // 只要进行了修改,就会重新创建一个对象,这就是不可变性
        s2 += "def";
        System.out.println(s1);
        System.out.println(s2);
        System.out.println("----------------");
    }

    public static void test3() {
        String s1 = "abc";
        String s2 = s1.replace('a', 'm');
        System.out.println(s1);
        System.out.println(s2);
    }

    public static void main(String[] args) {
        test1();
        test2();
        test3();
    }
}

运行结果:

true
false
hello
abc
----------------
abc
abcdef
----------------
abc
mbc

4、面试题

/**
 * 面试题
 *
 * @author: 陌溪
 * @create: 2020-07-11-9:05
 */
public class StringExer {
    String str = new String("good");
    char [] ch = {'t','e','s','t'};

    public void change(String str, char ch []) {
        str = "test ok";
        ch[0] = 'b';
    }

    public static void main(String[] args) {
        StringExer ex = new StringExer();
        ex.change(ex.str, ex.ch);
        System.out.println(ex.str);
        System.out.println(ex.ch);
    }
}

输出结果:

good
best

字符串常量池是不会存储相同内容的字符串的

  • String的string Pool是一个固定大小的Hashtable,默认值大小长度是1009。如果放进string Pool的string非常多,就会造成Hash冲突严重,从而导致链表会很长,而链表长了后直接会造成的影响就是当调用string.intern时性能会大幅下降。
  • 使用-XX:StringTablesize可设置stringTab1e的长度
  • 在jdk6中stringTable是固定的,就是1009的长度,所以如果常量池中的字符串过多就会导致效率下降很快。stringTablesize设置没有要求
  • 在jdk7中,stringTable的长度默认值是60013,
  • 在JDK8中,StringTable可以设置的最小值为1009

二、String的内存分配

在Java语言中有8种基本数据类型和一种比较特殊的非常常用的类型String。这些类型为了使它们在运行过程中速度更快、更节省内存,都提供了一种常量池的概念。 常量池就类似一个Java系统级别提供的缓存。8种基本数据类型的常量池都是系统协调的,String类型的常量池比较特殊。它的主要使用方法有两种:

  • 直接使用双引号声明出来的String对象会直接存储在常量池中。比如:String info="lori.com";
  • 如果不是用双引号声明的String对象(new出来的,或者其他方法返回的),可以使用String提供的intern()方法。

实验

1、基于JDK6

使用JDK 6用 -XX:PermSize和 -XX: MaxPermSize限制永久代的大小。

//-XX:PermSize=5M -XX:MaxPermSize=5M
public class MethodDemo {
    public static void main(String[] args) {
        // 使用Set保持着常量池引用,避免Full GC回收常量池行为
        Set<String> set = new HashSet<String>();        
        int i = 0;
        while (true) {
            System.out.println(i);
            set.add(String.valueOf(i++).intern());
        }
    }
}

211.png 如果使用JDK 7或更高版本的JDK来运行实例一,结果是不会重现JDK 6中的溢出异常,程序会执行下去

2、基于JDK7

使用JDK 7或更高版本的JDK用 -XX:MetaspaceSize=N -XX:MaxMetaspaceSize=N参数限制元空间容量执行上面代码。

//-XX:MetaspaceSize=5M -XX:MaxMetaspaceSize=5M
public class MethodDemo {
    public static void main(String[] args) {
        // 使用Set保持着常量池引用,避免Full GC回收常量池行为
        Set<String> set = new HashSet<String>();        
        int i = 0;
        while (true) {
            System.out.println(i);
            set.add(String.valueOf(i++).intern());
        }
    }
}

由于字符串常量池放在了Java堆里,程序会执行下去。

3、JDK7+

使用JDK 7或更高版本的JDK用-Xms -Xmx参数限制Java堆容量执行上面代码。

//-Xms5M -Xmx5M -XX:-UseGCOverheadLimit
public class MethodDemo {
    public static void main(String[] args) {
        // 使用Set保持着常量池引用,避免Full GC回收常量池行为
        Set<String> set = new HashSet<String>();        
        int i = 0;
        while (true) {
            System.out.println(i);
            set.add(String.valueOf(i++).intern());
        }
    }
}

212.png

213.png

214.png

4、为什么StringTable从永久代调整到堆中

  • 永久代的默认比较小
  • 永久代垃圾回收频率低
  • 堆中空间足够大,字符串可被及时回收

三、String的基本操作

Java语言规范里要求完全相同的字符串字面量,应该包含同样的Unicode字符序列(包含同一份码点序列的常量),并且必须是指向同一个String类实例。 代码示例1:验证常量池里不允许有相同的字符串

public class StringTest1 {
    public static void main(String[] args) {
        System.out.println();//2330
        System.out.println("1");//2331 个字符串
        System.out.println("2");
        System.out.println("3");
        System.out.println("4");
        System.out.println("5");
        System.out.println("6");
        System.out.println("7");
        System.out.println("8");
        System.out.println("9");
        System.out.println("10");//2340 个字符串

        //如下的字符串"1" 到 "10"不会再次加载
        System.out.println("1");//2341 
        System.out.println("2");//2341
        System.out.println("3");
        System.out.println("4");
        System.out.println("5");
        System.out.println("6");
        System.out.println("7");
        System.out.println("8");
        System.out.println("9");
        System.out.println("10");//2341
    }
}

215.png 216.png 217.png 218.png 219.png 220.png

代码示例2:

class Memory {
    public static void main(String[] args) {//line 1
        int i = 1;//line 2
        Object obj = new Object();//line 3
        Memory mem = new Memory();//line 4
        mem.foo(obj);//line 5
    }//line 9

    private void foo(Object param) {//line 6
        String str = param.toString();//line 7
        System.out.println(str);
    }//line 8
}

显示的调用toString()方法在字符串常量池当中生成调用者对应的字符串,并且返回常量池当中的地址 221.png

四、字符串拼接操作

五、intern()的使用

六、String Table的垃圾回收

七、G1中的String去重操作

举报

相关推荐

0 条评论