JVM---运行时数据区-堆-字符串常量池
/** * <java.lang.String> * * 基本特性 * 字符串,使用一对 "" 表示; * * ***不可变 * 1、堆中字符串常量池 不会存储相同内容的字符串 * eg: * private static void testStringConstantPool1() { * * String s = "a"; * String ss = "a"; * * System.out.println(s == ss); // true * System.out.println(s); // a * System.out.println(ss); // a * * } * * 2、当变量指向新的字符串时,堆中字符串常量池 旧的字符串不会被移除 * eg: * private static void testStringConstantPool2() { * String s = "a"; * String ss = "a"; * ss = "hello"; * * System.out.println(s == ss); // false * System.out.println(s); // a * System.out.println(ss); // hello * } * * 3、当对旧有 堆中字符串常量池 的字符串进行操作时,旧有字符串不会被移除 * eg: * private static void testStringConstantPool3() { * String s = "a"; * String ss = "a"; * String sss = "a"; * ss += "hello"; * sss = sss.replace("a", "c"); * * System.out.println(s); // a * System.out.println(ss); // ahello * System.out.println(sss); // c * } * * <字符串常量池本质> * 字符串常量池 是一个固定大小的Hashtable,默认大小长度是1009;如果字符串常量池放任的String非常多,会造成hash冲突,导致链表很长,调用String.intern时性能大幅下降; * * 字符串常量池大小: * JDK6中,字符串常量池是固定的1009; * JDK7中,字符串常量池 默认是60013,1009是最小值; * JDK8中,字符串常量池 1009是最小值; * * 查看当前进程的大小: * jps * jinfo -flag StringTableSize 进程ID * * 设置 字符串常量池的长度: * -XX:StringTableSize * * <String内存分配> * 在Java语言中有 8种基本数据类型和String;为了使它们在运行过程中速度更快,更节省内存,都提供了 常量池的概念; * * String类型的常量池使用方式: * a,直接使用 "" 声明出来的String对象 会直接存储在 常量池; * "a"; * b,使用String的intern(); * * 存放位置: * JDK6及之前,字符串常量池 存放于 堆的永久代; * JDK7,字符串常量池 在堆中(非永久代); * JDK8及之后,字符串常量池 在堆中(无永久代); * * 字符串常量池移到 非永久代的原因: * jdk 7 将 字符串常量池 移至堆中,因为 永久代的回收效率很低,只有FullGC 的时候 才会 回收 永久代; * 在 开发中 会创建大量的字符串,回收效率太低,导致 永久代 空间不足; */
/** * <字符串操作> * 1、常量与常量的拼接结果 在常量池,原理是 编译期优化; * eg1: * private static void testPj1() { * * String s = "a" + "b"; * String ss = "ab"; * System.out.println(s == ss); // true * System.out.println(s.equals(ss)); // true * } * * 0 ldc #3 <ab> * 2 astore_0 * 3 ldc #3 <ab> * 5 astore_1 * 6 getstatic #4 <java/lang/System.out> * ... * * String s = "a" + "b"; 在javac时,已经优化为 String s = "ab"; * * eg2: * final String s1 = "a"; * final String s2 = "b"; * String s3 = "ab"; * String s4 = s1 + s2; * System.out.println(s3 == s4);// true * * 0 ldc #3 <a> * 2 astore_0 * 3 ldc #4 <b> * 5 astore_1 * 6 ldc #5 <ab> * 8 astore_2 * 9 ldc #5 <ab> * 11 astore_3 * ... * * 字符串拼接 不一定适用StringBuilder; * 如果拼接符左右2边都是 常量或常量引用,仍适用 编译期优化; * * 2、常量与变量拼接 结果在 堆中非常量池,原理是 StringBuilder; * eg: * private static void testPj2() { * * String s1 = "a"; * String s2 = "b"; * String s3 = "ab"; * String s4 = "a" + "b"; * * // 如果拼接符 前后出现变量,相对于在堆中new String() * String s5 = s1 + "b"; * String s6 = "a" + s2; * String s7 = s1 + s2; * * System.out.println(s3 == s4);// true * System.out.println(s3 == s5);// false * System.out.println(s3 == s6);// false * System.out.println(s3 == s7);// false * System.out.println(s5 == s6);// false * System.out.println(s5 == s7);// false * System.out.println(s6 == s7);// false * * // 判断字符串常量池中是否存在 "ab" 值,如果存在,返回"ab"的内存地址; * // 如果不存在,在字符串常量池中存储,返回内存地址; * String s8 = s6.intern(); * System.out.println(s3 == s8);// true * * } * * ***变量拼接的原理 * String s1 = "a"; * String s2 = "b"; * String s3 = s1 + s2; * * 0 ldc #3 <a> * 2 astore_0 * 3 ldc #4 <b> * 5 astore_1 * 6 new #5 <java/lang/StringBuilder> * 9 dup * 10 invokespecial #6 <java/lang/StringBuilder.<init>> * 13 aload_0 * 14 invokevirtual #7 <java/lang/StringBuilder.append> * 17 aload_1 * 18 invokevirtual #7 <java/lang/StringBuilder.append> * 21 invokevirtual #8 <java/lang/StringBuilder.toString> * 24 astore_2 * 25 return * * +拼接符(操作变量)的原理是: StringBuilder * * *** +与StringBuilder性能对比 * * count = 100000 * * private static void jia(int cout) { * String s = ""; * for (int i=0; i< cout; i++){ * s += "a"; // 每次循环都会创建StringBuilder,StringBuilder.toString 又会进行new String * } * } * * 耗时:6689ms * * private static void stringBuilder(int cout) { * StringBuilder stringBuilder = new StringBuilder(); // 只会创建一个StringBuilder * for (int i=0; i< cout; i++){ * stringBuilder.append("a"); // StringBuilder底层使用char[]实现,stringBuilder.append 操作时需要数组扩容,可以通过构造器预先指定容量进行优化; * } * } * * 耗时: 8ms * * 3、调用intern(),若无,会主动将常量池中还没有的字符串对象 放入池中,并返回此对象内存地址; * 若有,直接返回对象内存地址; * * <intern方法> * what * A pool of strings, initially empty, is maintained privately by the class String. * 被String类私下维护的 字符串池 初始是空的; * When the intern method is invoked, if the pool already contains a string equal to this {@code String} object as determined by the {@link #equals(Object)} method, then the string from the pool is returned. * 当intern方法被调用时,如果池中包含指定的字符串值,返回池中的字符串; * Otherwise, this {@code String} object is added to the pool and a reference to this {@code String} object is returned. * 否则,将会在池中添加字符串值 并返回 字符串值的引用; * It follows that for any two strings {@code s} and {@code t},{@code s.intern() == t.intern()} is {@code true} if and only if {@code s.equals(t)} is {@code true}. * 对于任意2个字符串,如果intern为true,则equals为true; * * 特点 * 如果内存中需要大量的字符串,使用intern方法,可以明显降低内存的大小; * * 如何保证变量s指向的是 堆中字符串常量池中的数据? * 方式1:字面量 * String s = "a"; * * 方式2:intern() * String s = new String("a").intern(); * String s = new StringBuilder("a").toString().intern(); * * <new String("a")创建几个对象> * 2个 * * eg: * new String("a"); * * 0 new #3 <java/lang/String> // 堆中创建String类型的对象 * 3 dup * 4 ldc #4 <a> // 字符串常量池中的 "a"对象 * 6 invokespecial #5 <java/lang/String.<init>> // 调用 String的构造器 进行属性赋值 * 9 pop * 10 return * * <new String("a") + new String("b")创建几个对象> * 6个 * * eg: * new String("a") + new String("b"); * * 0 new #3 <java/lang/StringBuilder> // new StringBuilder * 3 dup * 4 invokespecial #4 <java/lang/StringBuilder.<init>> * 7 new #5 <java/lang/String> // new String * 10 dup * 11 ldc #6 <a> // 字符串常量池的 "a" * 13 invokespecial #7 <java/lang/String.<init>> * 16 invokevirtual #8 <java/lang/StringBuilder.append> * 19 new #5 <java/lang/String> // new String * 22 dup * 23 ldc #9 <b> // 字符串常量池的 "b" * 25 invokespecial #7 <java/lang/String.<init>> * 28 invokevirtual #8 <java/lang/StringBuilder.append> * 31 invokevirtual #10 <java/lang/StringBuilder.toString> // StringBuilder.toString底层new String * 34 astore_0 【注意】:此时字符串常量池中没有 "ab" * 35 return * * */
图解String.intern()
1、字符串常量池预先不存在"ab"
jdk6
jdk7及后
2、字符串常量池预先存在"ab"
/** * 【字符串常量池-GC】 * 查看 * -XX:+PrintStringTableStatistics -XX:+PrintGCDetails */