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
*/
浙公网安备 33010602011771号