Recommended reading:
- Ali Liang Jing on both sides: design mode + cache + Spring + virtual machine + MySQL + middleware + concurrency and other problems, all solved
- Alibaba bytes jump those big factories must ask HTTP how to learn?I suggest you read this article!
- Ant, byte, PDD social face-to-face via Java post (Distributed + Thread Security + MySQL+CAS)
String strings are one of the most common types in the system and occupy a large amount of memory in the system. Therefore, using strings efficiently can improve the performance of the system.
For string optimization, I summarized three scenarios to share during my work and learning process:
1. Optimized construction of very large strings
Authentication environment: jdk1.8
Decompilation tool: jad
1. Download decompile tool jad, Baidu Download
2. Validation
First execute an example 1 code:
public class test3 { public static void main(String[] args) { String str="ab"+"cd"+"ef"+"123"; } }
After execution, decompile with the decompile tool jad: jad-o-a-s d.java test.class
Decompiled code:
// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov. // Jad home page: http://www.kpdus.com/jad.html // Decompiler options: packimports(3) annotate // Source File Name: test.java package example; public class test { public test() { // 0 0:aload_0 // 1 1:invokespecial #1 <Method void Object()> // 2 4:return } public static void main(String args[]) { String str = "abcdef123"; // 0 0:ldc1 #2 <String "abcdef123"> // 1 2:astore_1 // 2 3:return } }
Case 2:
public class test1 { public static void main(String[] args) { String s = "abc"; String ss = "ok" + s + "xyz" + 5; System.out.println(ss); } }
After jad-o-a-s d.java test1.class is decompiled with the decompile tool jad:
// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov. // Jad home page: http://www.kpdus.com/jad.html // Decompiler options: packimports(3) annotate // Source File Name: test1.java package example; import java.io.PrintStream; public class test1 { public test1() { // 0 0:aload_0 // 1 1:invokespecial #1 <Method void Object()> // 2 4:return } public static void main(String args[]) { String s = "abc"; // 0 0:ldc1 #2 <String "abc"> // 1 2:astore_1 String ss = (new StringBuilder()).append("ok").append(s).append("xyz").append(5).toString(); // 2 3:new #3 <Class StringBuilder> // 3 6:dup // 4 7:invokespecial #4 <Method void StringBuilder()> // 5 10:ldc1 #5 <String "ok"> // 6 12:invokevirtual #6 <Method StringBuilder StringBuilder.append(String)> // 7 15:aload_1 // 8 16:invokevirtual #6 <Method StringBuilder StringBuilder.append(String)> // 9 19:ldc1 #7 <String "xyz"> // 10 21:invokevirtual #6 <Method StringBuilder StringBuilder.append(String)> // 11 24:iconst_5 // 12 25:invokevirtual #8 <Method StringBuilder StringBuilder.append(int)> // 13 28:invokevirtual #9 <Method String StringBuilder.toString()> // 14 31:astore_2 System.out.println(ss); // 15 32:getstatic #10 <Field PrintStream System.out> // 16 35:aload_2 // 17 36:invokevirtual #11 <Method void PrintStream.println(String)> // 18 39:return } }
Based on the decompilation results, you can see that the interior is actually stitched by StringBuilder.
Execute the code for example 3 again:
public class test2 { public static void main(String[] args) { String s = ""; Random rand = new Random(); for (int i = 0; i < 10; i++) { s = s + rand.nextInt(1000) + " "; } System.out.println(s); } }
After decompiling jad-o-a-s d.java test2.class with the decompile tool jad, it is found that its interior is also stitched by StringBuilder:
// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov. // Jad home page: http://www.kpdus.com/jad.html // Decompiler options: packimports(3) annotate // Source File Name: test2.java package example; import java.io.PrintStream; import java.util.Random; public class test2 { public test2() { // 0 0:aload_0 // 1 1:invokespecial #1 <Method void Object()> // 2 4:return } public static void main(String args[]) { String s = ""; // 0 0:ldc1 #2 <String ""> // 1 2:astore_1 Random rand = new Random(); // 2 3:new #3 <Class Random> // 3 6:dup // 4 7:invokespecial #4 <Method void Random()> // 5 10:astore_2 for(int i = 0; i < 10; i++) //* 6 11:iconst_0 //* 7 12:istore_3 //* 8 13:iload_3 //* 9 14:bipush 10 //* 10 16:icmpge 55 s = (new StringBuilder()).append(s).append(rand.nextInt(1000)).append(" ").toString(); // 11 19:new #5 <Class StringBuilder> // 12 22:dup // 13 23:invokespecial #6 <Method void StringBuilder()> // 14 26:aload_1 // 15 27:invokevirtual #7 <Method StringBuilder StringBuilder.append(String)> // 16 30:aload_2 // 17 31:sipush 1000 // 18 34:invokevirtual #8 <Method int Random.nextInt(int)> // 19 37:invokevirtual #9 <Method StringBuilder StringBuilder.append(int)> // 20 40:ldc1 #10 <String " "> // 21 42:invokevirtual #7 <Method StringBuilder StringBuilder.append(String)> // 22 45:invokevirtual #11 <Method String StringBuilder.toString()> // 23 48:astore_1 // 24 49:iinc 3 1 //* 25 52:goto 13 System.out.println(s); // 26 55:getstatic #12 <Field PrintStream System.out> // 27 58:aload_1 // 28 59:invokevirtual #13 <Method void PrintStream.println(String)> // 29 62:return } }
Taken together with the case study, it is found that when a string is spliced with'+', there are several internal cases:
1.'+'splices directly into constant variables, such as'ab'+'cd'+'ef'+'123', and internal compilation splices several into a single constant string.
2.'+'spliced variable-containing strings, such as case 2:'ok' + S +'xyz'+ 5, internal compilation is actually a new StringBuilder to splice through append;
3. The case 3 cycle is essentially a'+'splicing with variable strings, so StringBuilder is also created for splicing when compiling internally.
Comparing the three cases, it is found that in the third case, a new StringBuilder object is created each time a loop is made, which increases the memory of the system and, in turn, decreases the performance of the system.
Therefore, in a single-threaded environment, StringBuilder can be used explicitly to stitch strings, avoiding a new StringBuilder object per loop; in a multi-threaded environment, thread-safe StringBuffers can be used, but with lock race, StringBuffer performance is less than StringBuilder.
In this way, the string splicing is optimized.
2. How do I save memory using String.intern?
Before answering this question, you can test a piece of code:
1. First set -XX:+PrintGCDetails-Xmx6G-Xmn3G in idea to print GC log information, as shown in the following figure:
2. Execute the following example code:
public class test4 { public static void main(String[] args) { final int MAX=10000000; System.out.println("No need intern: "+notIntern(MAX)); // System.out.println("Use intern:"+intern(MAX))); } private static long notIntern(int MAX){ long start = System.currentTimeMillis(); for (int i = 0; i < MAX; i++) { int j = i % 100; String str = String.valueOf(j); } return System.currentTimeMillis() - start; } /* private static long intern(int MAX){ long start = System.currentTimeMillis(); for (int i = 0; i < MAX; i++) { int j = i % 100; String str = String.valueOf(j).intern(); } return System.currentTimeMillis() - start; }*/
GC logs not using intern:
No need intern: 354 [GC (System.gc()) [PSYoungGen: 377487K->760K(2752512K)] 377487K->768K(2758656K), 0.0009102 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [Full GC (System.gc()) [PSYoungGen: 760K->0K(2752512K)] [ParOldGen: 8K->636K(6144K)] 768K->636K(2758656K), [Metaspace: 3278K->3278K(1056768K)], 0.0051214 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] Heap PSYoungGen total 2752512K, used 23593K [0x0000000700000000, 0x00000007c0000000, 0x00000007c0000000) eden space 2359296K, 1% used [0x0000000700000000,0x000000070170a548,0x0000000790000000) from space 393216K, 0% used [0x0000000790000000,0x0000000790000000,0x00000007a8000000) to space 393216K, 0% used [0x00000007a8000000,0x00000007a8000000,0x00000007c0000000) ParOldGen total 6144K, used 636K [0x0000000640000000, 0x0000000640600000, 0x0000000700000000) object space 6144K, 10% used [0x0000000640000000,0x000000064009f2f8,0x0000000640600000) Metaspace used 3284K, capacity 4500K, committed 4864K, reserved 1056768K class space used 359K, capacity 388K, committed 512K, reserved 1048576K
According to the printed log analysis: without intern, the execution time is 354 MS and the memory consumption is 2422k;
Using GC logs from intern:
Use intern: 1515 [GC (System.gc()) [PSYoungGen: 613417K->1144K(2752512K)] 613417K->1152K(2758656K), 0.0012530 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [Full GC (System.gc()) [PSYoungGen: 1144K->0K(2752512K)] [ParOldGen: 8K->965K(6144K)] 1152K->965K(2758656K), [Metaspace: 3780K->3780K(1056768K)], 0.0079962 secs] [Times: user=0.02 sys=0.00, real=0.01 secs] Heap PSYoungGen total 2752512K, used 15729K [0x0000000700000000, 0x00000007c0000000, 0x00000007c0000000) eden space 2359296K, 0% used [0x0000000700000000,0x0000000700f5c400,0x0000000790000000) from space 393216K, 0% used [0x0000000790000000,0x0000000790000000,0x00000007a8000000) to space 393216K, 0% used [0x00000007a8000000,0x00000007a8000000,0x00000007c0000000) ParOldGen total 6144K, used 965K [0x0000000640000000, 0x0000000640600000, 0x0000000700000000) object space 6144K, 15% used [0x0000000640000000,0x00000006400f1740,0x0000000640600000) Metaspace used 3786K, capacity 4540K, committed 4864K, reserved 1056768K class space used 420K, capacity 428K, committed 512K, reserved 1048576K
Log analysis: Without intern, the execution time is 1515 MS and the memory usage is 16694 k;
To summarize, with intern, memory is less than without intern, but it saves memory while increasing time complexity.I've tried adding another 0 to MAX=10000000, and using intern can take up to 11 seconds to execute, so it's not recommended when traversing data that is too large.
Therefore, the premise of using intern is to take into account specific usage scenarios.
Here, you can confirm that using String.intern does save memory.
Next, analyze the differences between different JDK versions of intern.
In JDK1.6, the pool of string constants is in the method area, which is a permanent generation.
In JDK1.7, the string constant pool was moved to the heap.
In JDK1.8, the pool of string constants moved into metaspace, independent of the heap.
Execute the following examples at versions 1.6, 1.7, and 1.8, respectively:
public class test5 { public static void main(String[] args) { String s1=new String("ab"); s.intern(); String s2="ab"; System.out.println(s1==s2); String s3=new String("ab")+new String("cd"); s3.intern(); String s4="abcd"; System.out.println(s4==s3); } }
Version 1.6
Execution results:
fasle false
Analysis:
When the first part is executed:
1. When code compiles, the constant "ab" is created in the string constant pool first; when new is called, a String object is created in the heap, the string constant created "ab" is stored in the heap, and the String object in the heap returns a reference to s1.
2.s.intern(), where "ab" already exists in the string constant pool, no longer creates a storage copy "ab";
3.s2="ab", S2 points to "ab" in the string constant pool, and s1 points to "ab" in the heap, so they are not equal.
The diagram is as follows:
Execute Part Two:
1. Two "abcds" added by new out are stored in the heap, s3 points to "abcd" in the heap;
2. Execute s3.intern(), when "abcd" copy is stored in the string constant pool, it is found that there is no "abcd" in the constant pool, so it is stored successfully;
3.s4="abcd" points to an existing "abcd" copy in the string constant pool, while s3 points to an "abcd" in the heap. The address of the "abcd" copy is different from the "abcd" address in the heap, so it is false;
Version 1.7
false true
Execute Part One: This section is similar to jdk1.6 in that s1.intern() returns references, not copies.
Execute Part Two:
1.new String("ab")+new String("cd"), first generate "ab" and "cd" in the constant pool, then generate "ab cd" in the heap;
2. When s3.intern() is executed, the object reference to "abcd" is placed in the string constant pool, and it is found that there is no such reference in the constant pool, so it can be successfully placed.When String s4="abcd", which assigns the reference address of "abcd" in the string constant pool to s4, it is equivalent to S4 pointing to the address of "abcd" in the heap, so s3==s4 is true.
Version 1.8
false true
Referring to some blogs on the Web, in version 1.8, intern() works as follows:
If the string constant pool contains a string equivalent to the current object, the string in the constant pool is returned; if it does not exist, the string is stored in the constant pool and a reference to the string is returned.
To sum up, among the three versions, there are the following differences when using intern if no corresponding string exists in the string constant pool:
For example:
String s1=new String("ab"); s.intern();
jdk1.6: If there is no "ab" in the string constant pool, a copy of "ab" will be stored in the constant pool with an address different from the "ab" address in the heap;
jdk1.7: If there is no "ab" in the string constant pool, the object reference to "ab" will be placed in the string constant pool at the same address as the "ab" in the heap;
jdk1.8: If the string constant pool contains a string equivalent to the current object, the string in the constant pool is returned; if it does not exist, it is stored in the constant pool and a reference to the string is returned.
3. How do I use string splitting?
For simple string splitting, indexOf can be used instead of split, because the performance of split is not stable enough, for simple string splitting, indexOf can be used first.