JVM---运行时数据区-堆-字符串常量池

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
/**
     *      <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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
/**
     *          <字符串操作>
     *              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"

 

1
2
3
4
5
/**
     *  【字符串常量池-GC】
     *      查看
     *          -XX:+PrintStringTableStatistics -XX:+PrintGCDetails
     */

  

 

  

posted on   anpeiyong  阅读(58)  评论(0编辑  收藏  举报

相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· .NET10 - 预览版1新功能体验(一)
历史上的今天:
2021-04-28 JavaSE---泛型
2020-04-28 SpringBoot---事务扩展---事务(普通)方法调用事务方法

导航

< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5
点击右上角即可分享
微信分享提示